2024-11-21 01:24:10 -05:00
<!DOCTYPE html>
< html >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, user-scalable=no" >
< script src = "https://unpkg.com/@dashincubator/ripemd160/ripemd160.js" ></ script >
2025-11-11 19:31:31 +00:00
< script src = "https://supertestnet.github.io/bankify/super_nostr.js" ></ script >
2024-11-21 01:24:10 -05:00
< script src = "https://unpkg.com/@cmdcode/tapscript@1.4.0" ></ script >
2025-11-11 19:25:07 +00:00
< script src = "https://bundle.run/noble-secp256k1@1.2.14" ></ script >
2025-12-22 20:09:07 +00:00
< script src = "https://supertestnet.github.io/testnet_generator/chain_client.js" ></ script >
2025-11-15 20:54:48 +00:00
<!--
2025-12-22 15:37:15 +00:00
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/ripemd160.js"></script>
2025-11-11 17:36:40 +00:00
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/super_nostr.js"></script>
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/tapscript@1.5.3"></script>
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/noble-secp256k1@1.2.14"></script>
2025-12-22 20:09:44 +00:00
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/chain_client.js"></script>
2025-11-15 20:54:48 +00:00
-->
2024-11-21 01:24:10 -05:00
< style >
* {
box-sizing : border-box ;
font-size : 1.15 rem ;
font-family : Arial , sans-serif ;
}
body {
margin : 0 ;
}
. inner_html {
max-width : 800 px ;
padding : 3 rem 1 rem ;
margin : auto ;
line-height : 1.25 ;
padding : 0 ;
}
. inner_body {
margin : 3 rem 1 rem ;
word-wrap : break-word ;
}
h1 {
font-size : 2 rem ;
}
h2 , h2 * {
font-size : 1.5 rem ;
}
input , textarea {
line-height : 1.25 ;
width : 100 % ;
height : 1.8 rem ;
font-size : 1.15 rem ;
border : 1 px solid grey ;
}
textarea {
height : 10 rem ;
}
. hidden {
display : none !important ;
}
. list_container {
overflow-y : scroll ;
}
. no_blocks_msg {
height : 248 px ;
line-height : 248 px ;
text-align : center ;
}
. list_of_blocks {
display : flex ;
justify-content : center ;
}
. block {
display : inline-block ;
height : 11.5 rem ;
width : 192 px ;
margin : 1 rem ;
cursor : pointer ;
overflow : hidden ;
flex-shrink : 0 ;
}
. block : hover {
background-color : pink ;
}
. block_inner {
display : inline-block ;
position : relative ;
height : 9.5 rem ;
width : 7.5 rem ;
margin : 1.5 rem ;
}
. block_back {
border : 1 px solid black ;
height : 6 rem ;
width : 6 rem ;
float : right ;
display : inline ;
background-color : #232838 ;
border-left : 0 ;
border-bottom : 0 ;
}
. block_front {
border : 1 px solid black ;
height : 6 rem ;
width : 6 rem ;
display : flex ;
align-items : center ;
position : absolute ;
bottom : 2 rem ;
background-color : #3953c6 ;
color : white ;
text-align : center ;
}
. block_front div {
width : 100 % ;
}
2025-11-12 10:34:00 +00:00
. block_side1 {
height : 2 rem ;
background-color : #02078e ;
aspect-ratio : 1 ;
rotate : 45 deg ;
border-left : 1 px solid black ;
position : absolute ;
top : .47 rem ;
left : 0.47 rem ;
}
. block_side2 {
height : 5.8 rem ;
background-color : #02078e ;
position : absolute ;
top : 1 px ;
left : 1.5 rem ;
width : 5.9 rem ;
}
. block_side3 {
height : 2.4 rem ;
width : 1 px ;
background-color : black ;
rotate : 45 deg ;
position : absolute ;
top : -.3 rem ;
right : .85 rem ;
}
. block_side4 {
height : 2 rem ;
background-color : #02078e ;
aspect-ratio : 1 ;
rotate : 45 deg ;
border-right : 1 px solid black ;
position : absolute ;
bottom : 2.50 rem ;
right : 0.5 rem ;
}
2024-11-21 01:24:10 -05:00
. block_num {
position : absolute ;
bottom : 0 rem ;
width : 80 % ;
text-align : center ;
}
. wallet {
border : 1 px solid black ;
border-radius : 1 rem ;
padding : 1 rem ;
text-align : center ;
margin-top : 1 rem ;
}
. auto_generate_label {
display : inline-block ;
}
. wallet_btns , . broadcast_btn , . auto_generate_label ,
2025-03-17 20:48:11 -04:00
. auto_go , . broadcast_success_label , . make_sharable {
2024-11-21 01:24:10 -05:00
margin-top : 1 rem ;
}
. block_info , . tx_info_label , . addy_info_label {
display : flex ;
justify-content : space-between ;
}
. block_explorer , . tx , . input , . output ,
2025-03-30 14:11:05 -04:00
. tx_explorer , . addy_explorer , . btc_script {
2024-11-21 01:24:10 -05:00
border : 1 px solid black ;
border-radius : 1 rem ;
padding : 1 rem ;
margin-bottom : 1 rem ;
}
. close_explorer {
cursor : pointer ;
}
. explorer_block_num , . explorer_txid ,
. explorer_addy , . close_explorer {
padding : 1 rem ;
}
. explorer_block_num , . explorer_addy ,
. explorer_txid {
padding-left : 0 ;
}
. txid_link , . block_link , . addy_link {
color : blue ;
text-decoration : underline ;
cursor : pointer ;
}
2025-03-15 22:25:47 -04:00
. pink {
background-color : pink ;
}
. lightblue {
background-color : lightblue ;
}
. yellow {
background-color : yellow ;
}
. green {
background-color : lightgreen ;
}
. white {
background-color : white ;
}
. grey {
background-color : #cccccc ;
}
2025-03-30 14:11:05 -04:00
. script_arguments_label , . script_program_label {
font-weight : bold ;
}
. btc_script {
. margin-bottom : auto ;
}
2024-11-21 01:24:10 -05:00
@ media screen and ( max-width : 600px ) {
}
</ style >
2025-03-17 20:48:11 -04:00
< style >
. black-bg {
width : 100 % ;
position : fixed ;
top : 0 ;
left : 0 ;
background-color : black ;
opacity : .5 ;
width : 100 vw ;
height : 100 vh ;
}
. modal {
position : fixed ;
box-sizing : border-box ;
top : 50 % ;
left : 50 % ;
transform : translate ( -50 % , -50 % );
width : 90 % ;
max-width : 560 px ;
background-color : white ;
border-radius : 1 rem ;
padding : 20 px ;
color : black ;
text-align : center ;
word-wrap : break-word ;
}
. modal * {
color : black ;
}
. modal input , . modal textarea {
max-width : 90 % ;
}
</ style >
< script >
var modalVanish = () => {
$ ( ".black-bg" ). classList . add ( "hidden" );
$ ( ".modal" ). classList . add ( "hidden" );
}
var showModal = content => {
$ ( ".modal" ). innerHTML = `<div class="x_modal" style="position: absolute;right: 1rem;top: 0.5rem;font-size: 2rem; cursor: pointer; color: black;" onclick="modalVanish()">×</div>` ;
$ ( ".modal" ). innerHTML += `<div style="overflow-y: auto; max-height: 80vh; margin-top: 1.5rem;"> ${ content } </div>` ;
$ ( ".black-bg" ). classList . remove ( "hidden" );
$ ( ".modal" ). classList . remove ( "hidden" );
}
</ script >
2024-11-21 01:24:10 -05:00
< script >
var $ = document . querySelector . bind ( document );
var $$ = document . querySelectorAll . bind ( document );
var url_params = new URLSearchParams ( window . location . search );
var url_keys = url_params . keys ();
var $_GET = {}
for ( var key of url_keys ) $_GET [ key ] = url_params . get ( key );
2025-03-15 22:25:47 -04:00
var hash_arr = window . location . href . substring ( window . location . href . indexOf ( "#" ) ). split ( "#" );
hash_arr . splice ( 0 , 1 );
var params = {}
hash_arr . forEach ( item => {
var vals = item . split ( "=" );
params [ vals [ 0 ] ] = vals [ 1 ];
});
2024-11-21 01:24:10 -05:00
</ script >
</ head >
< body >
< div class = "list_container" >
< div class = "no_blocks_msg" > Generate a block!</ div >
< div class = "list_of_blocks" ></ div >
</ div >
< div class = "inner_html" >
< div class = "inner_body" >
< h1 > Testnet generator</ h1 >
< div class = "block_explorer hidden" >
< h2 class = "block_info" >< span class = "explorer_block_num" ></ span >< span class = "close_explorer" > × </ span ></ h2 >
< p > Transaction count: < span class = "tx_count" > 0</ span ></ p >
< p > Timestamp: < span class = "block_timestamp" > None</ span ></ p >
< p > Subsidy: < span class = "block_subsidy" > 50 btc</ span ></ p >
< p > Fees: < span class = "block_fees" > 0 btc</ span ></ p >
< p style = "font-weight: bold;" > Transactions</ p >
< div class = "txs" ></ div >
</ div >
< div class = "tx_explorer hidden" >
< h2 class = "tx_info_label" >< span class = "explorer_txid" ></ span >< span class = "close_explorer" > × </ span ></ h2 >
< p > Number of inputs: < span class = "inputs_count" > 0</ span ></ p >
< p > Number of outputs: < span class = "outputs_count" > 0</ span ></ p >
< p > Mined in block: < span class = "mined_in_block" > None</ span ></ p >
< p > Timestamp: < span class = "tx_timestamp" > None</ span ></ p >
< p > Tx size: < span class = "tx_size" > 0 bytes</ span ></ p >
< p > Tx fee: < span class = "tx_fee" > 0 btc</ span ></ p >
< p > Feerate: < span class = "tx_feerate" > 0 sats per byte</ span ></ p >
< div class = "tx_info" ></ div >
</ div >
< div class = "addy_explorer hidden" >
< h2 class = "addy_info_label" >< span class = "explorer_addy" ></ span >< span class = "close_explorer" > × </ span ></ h2 >
< p class = "full_address" ></ p >
< p > Times this address received money: < span class = "received_count" > 0</ span ></ p >
< p > Times this address sent money: < span class = "sent_count" > 0</ span ></ p >
< p > Total amount received: < span class = "total_received" > 0</ span ></ p >
< p > Total amount sent: < span class = "total_sent" > 0</ span ></ p >
< p > Address balance: < span class = "addy_balance" > 0</ span ></ p >
< div class = "addy_tx_info" ></ div >
</ div >
< div class = "stuff_thats_not_an_explorer" >
2025-03-15 22:25:47 -04:00
< div >< button class = "generate_block" disabled > Generate block</ button ></ div >
2025-03-17 20:48:11 -04:00
< div class = "options" >
< div class = "auto_gen" >
< span class = "auto_generate_label" > Auto generator: </ span >
< select class = "auto_generate" >
< option > --- Auto generate ---</ option >
< option > 1/2 second</ option >
< option > 1 second</ option >
< option > 3 seconds</ option >
< option > 5 seconds</ option >
< option > 10 seconds</ option >
< option > 30 seconds</ option >
< option > 1 minute</ option >
< option > 2 minutes</ option >
< option > 5 minutes</ option >
< option > 10 minutes</ option >
</ select >
< button class = "auto_go" disabled > Go</ button >
</ div >
< div class = "make_sharable" >
< button class = "make_sharable_testnet" > Make a sharable testnet</ button >
</ div >
2024-11-21 01:24:10 -05:00
</ div >
< div class = "wallet" >
< h2 > Your wallet</ h2 >
< p > Your address:</ p >
< p class = "your_addy" ></ p >
< div class = "balance_div" > Balance: < span class = "balance" > 0</ span > btc</ div >
< div class = "wallet_btns" >< button class = "send_btn" > Send</ button ></ div >
</ div >
< h2 class = "txs_in_mempool_label" > Transactions in mempool: < span class = "txs_in_mempool" > 0</ span ></ h2 >
< p > Enter a transaction you want to broadcast</ p >
< textarea class = "tx_broadcaster" ></ textarea >
< div class = "broadcast_btn" >< button > Broadcast</ button ></ div >
< div class = "broadcast_success_label hidden" > Success: < span class = "broadcast_success_txid txid_link" ></ span ></ div >
</ div >
</ div >
</ div >
< script >
var testnet_generator = {
blocks : {},
utxo_set : {},
mempool : [],
utxos : {},
addresses : {},
2025-12-20 16:16:03 +00:00
relays : [ "wss://no.str.cr" ],
// relays: [ "ws://127.0.0.1:6969/" ],
2025-03-17 20:48:11 -04:00
blocks_relayed : [],
2025-03-15 22:25:47 -04:00
addykind : "testnet" ,
2024-11-21 01:24:10 -05:00
hexToBytes : hex => Uint8Array . from ( hex . match ( /.{1,2}/g ). map ( byte => parseInt ( byte , 16 ) ) ),
bytesToHex : bytes => bytes . reduce ( ( str , byte ) => str + byte . toString ( 16 ). padStart ( 2 , "0" ), "" ),
2025-11-15 20:13:02 +00:00
hexToText : hex => {
var bytes = new Uint8Array ( Math . ceil ( hex . length / 2 ) );
var i ; for ( i = 0 ; i < hex . length ; i ++ ) bytes [ i ] = parseInt ( hex . substr ( i * 2 , 2 ), 16 );
var text = new TextDecoder (). decode ( bytes );
return text ;
},
2024-11-21 01:24:10 -05:00
getPrivkey : () => testnet_generator . bytesToHex ( nobleSecp256k1 . utils . randomPrivateKey () ),
getPubkey : privkey => nobleSecp256k1 . getPublicKey ( privkey , true ). substring ( 2 ),
2025-03-15 22:25:47 -04:00
getAddy : privkey => {
if ( ! privkey ) privkey = testnet_generator . getPrivkey ();
2024-11-21 01:24:10 -05:00
var pubkey = testnet_generator . getPubkey ( privkey );
2025-03-15 22:25:47 -04:00
var addy = tapscript . Address . p2tr . fromPubKey ( pubkey , testnet_generator . addykind );
2024-11-21 01:24:10 -05:00
return [ addy , privkey ];
},
intToHex : int => {
var hex = BigInt ( int ). toString ( 16 );
if ( hex . length % 2 ) hex = "0" + hex ;
return hex ;
},
reverseHexString : s => s . match ( /[a-fA-F0-9]{2}/g ). reverse (). join ( '' ),
littleEndianSignedHexToDecimal : hex => {
var rev = testnet_generator . reverseHexString ( hex );
var first_byte = rev . substring ( 0 , 2 );
first_byte_as_decimal = parseInt ( first_byte , 16 );
if ( first_byte_as_decimal < 128 ) {
return parseInt ( rev , 16 );
} else {
//get the number of bytes in the hex string
var num_of_bytes_in_hex_string = rev . length / 2 ;
//get the corresponding max X-byte number
var maxnum = ( 2 ** ( num_of_bytes_in_hex_string * 8 ) );
//halve it
var maxnum_halved = maxnum / 2 ;
//subtract the hex number from that
return maxnum_halved - parseInt ( rev , 16 );
}
},
2024-11-21 12:24:13 -05:00
hexToBinary : hex => {
var array_hex = hex . match ( /\w{2}/g );
var array_bin = [];
array_hex . forEach ( item => array_bin . push ( ( parseInt ( item , 16 ). toString ( 2 ) ). padStart ( 8 , '0' ) ) );
return array_bin . join ( "" );
},
2024-11-21 01:24:10 -05:00
isValidHex : hex => {
if ( ! hex ) return ;
var length = hex . length ;
if ( length % 2 ) return ;
try {
var bigint = BigInt ( "0x" + hex , "hex" );
} catch ( e ) {
return ;
}
var prepad = bigint . toString ( 16 );
var i ; for ( i = 0 ; i < length ; i ++ ) prepad = "0" + prepad ;
var padding = prepad . slice ( - Math . abs ( length ) );
return ( padding === hex );
},
2025-03-15 22:25:47 -04:00
isValidAddress : address => {
try {
return !! tapscript . Address . decode ( address ). script ;
} catch ( e ) { return ;}
return ;
},
2024-11-21 01:24:10 -05:00
isZeroNegative : zero => {
var isZero = zero === 0 ;
var isNegative = 1 / zero === - Infinity ;
return isNegative && isZero ;
},
waitSomeTime : num => new Promise ( resolve => setTimeout ( resolve , num ) ),
sha256 : s => {
if ( typeof s == "string" ) s = new TextEncoder (). encode ( s );
return crypto . subtle . digest ( 'SHA-256' , s ). then ( hashBuffer => {
var hashArray = Array . from ( new Uint8Array ( hashBuffer ) );
var hashHex = hashArray
. map ( bytes => bytes . toString ( 16 ). padStart ( 2 , '0' ) )
. join ( '' );
return hashHex ;
});
},
sha1 : s => {
if ( typeof s == "string" ) s = new TextEncoder (). encode ( s );
return crypto . subtle . digest ( 'SHA-1' , s ). then ( hashBuffer => {
var hashArray = Array . from ( new Uint8Array ( hashBuffer ) );
var hashHex = hashArray
. map ( bytes => bytes . toString ( 16 ). padStart ( 2 , '0' ) )
. join ( '' );
return hashHex ;
});
},
rmd160 : s => {
if ( typeof s == "string" ) s = new TextEncoder (). encode ( s );
var hash = RIPEMD160 . create ();
hash . update ( new Uint8Array ( s ) );
return testnet_generator . bytesToHex ( hash . digest () );
},
getVin : ( txid , vout , amnt , addy ) => ({
txid ,
vout ,
prevout : {
value : amnt ,
2025-03-15 22:25:47 -04:00
scriptPubKey : testnet_generator . isValidAddress ( addy ) ? tapscript . Address . toScriptPubKey ( addy ) : addy ,
2024-11-21 01:24:10 -05:00
},
}),
getVout : ( amnt , addy ) => ({
value : amnt ,
2025-03-15 22:25:47 -04:00
scriptPubKey : testnet_generator . isValidAddress ( addy ) ? tapscript . Address . toScriptPubKey ( addy ) : addy ,
2024-11-21 01:24:10 -05:00
}),
satsToBitcoin : sats => {
var btc = String ( sats ). padStart ( 8 , "0" ). slice ( 0 , - 8 ) + "." + String ( sats ). padStart ( 8 , "0" ). slice ( - 8 );
if ( btc . endsWith ( "00000" ) ) {
btc = btc . substring ( 0 , btc . length - 5 );
var i ; for ( i = 0 ; i < 5 ; i ++ ) {
if ( btc . endsWith ( "0" ) ) btc = btc . substring ( 0 , btc . length - 1 );
}
if ( btc . endsWith ( "." ) ) btc = btc . substring ( 0 , btc . length - 1 );
if ( ! btc ) btc = 0 ;
}
return btc ;
},
showTx : ( txhex , idx ) => {
2025-03-30 14:51:05 -04:00
var unique = testnet_generator . getPrivkey ();
2024-11-21 01:24:10 -05:00
if ( idx === undefined ) idx = 1 ;
2025-03-15 22:25:47 -04:00
var background_color = "pink" ;
if ( idx % 2 ) background_color = "lightblue" ;
2024-11-21 01:24:10 -05:00
var txs_html = `` ;
var tx = tapscript . Tx . decode ( txhex );
var txid = tapscript . Tx . util . getTxid ( tx );
var display_fee = !! idx ? "" : "hidden" ;
var tx_html = `
2025-03-30 14:51:05 -04:00
<div class="tx ${ background_color } tx_ ${ txid } unique_ ${ unique } ">
2025-03-15 22:25:47 -04:00
<p>Tx: <span class="txnum"> ${ idx } </span></p>
2024-11-21 01:24:10 -05:00
<div>Txid: <span class="txid txid_link" data-txhex=" ${ txhex } " data-idx=" ${ idx } "> ${ txid } </span></div>
<div class=" ${ display_fee } ">Fee: <span class="fee">0</span> btc</div>
<div class="inputs"></div>
<div class="outputs"></div>
</div>
` ;
var div = document . createElement ( 'div' );
div . innerHTML = tx_html ;
var inputs_html = `` ;
var outputs_html = `` ;
tx . vin . forEach ( ( input , index ) => {
if ( ! index ) inputs_html = `<p style="text-align: center;">Inputs</p>` ;
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
if ( ! idx ) var from_addy = "" ;
else var from_addy = testnet_generator . utxo_set [ input_id ]. addy ;
2025-03-15 22:25:47 -04:00
try {
from_addy = tapscript . Address . fromScriptPubKey ( from_addy , testnet_generator . addykind );
} catch ( e ) {}
2024-11-21 01:24:10 -05:00
if ( ! idx ) var amnt_sent = "" ;
else var amnt_sent = testnet_generator . utxo_set [ input_id ]. amnt ;
var input_txid = input [ "txid" ];
var input_vout = input [ "vout" ];
var input_idx = 0 ;
if ( ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` in testnet_generator . utxo_set ) input_idx = testnet_generator . utxo_set [ ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ][ "idx" ];
if ( ! idx ) var input_txhex = "" ;
else var input_txhex = testnet_generator . utxo_set [ ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ][ "txhex" ];
2025-03-15 22:25:47 -04:00
var vin_bg_color = "yellow" ;
if ( index % 2 ) vin_bg_color = "green" ;
if ( ! idx ) inputs_html = inputs_html + `<div class="input ${ vin_bg_color } ">Coinbase</div>` ;
2024-11-21 01:24:10 -05:00
else inputs_html = inputs_html + `
2025-03-15 22:25:47 -04:00
<div class="input ${ vin_bg_color } ">
2024-11-21 01:24:10 -05:00
<p>"From" address: <span class="from_address addy_link"> ${ from_addy } </span></p>
<p>Amount sent from this address in this tx: <span class="amnt_sent"> ${ testnet_generator . satsToBitcoin ( amnt_sent ) } btc</span></p>
<p>Tx that originally created this input: <span class="input_txid txid_link" data-txhex=" ${ input_txhex } " data-idx=" ${ input_idx } "> ${ input_txid } </span></p>
<p>Vout of that tx: <span class="input_vout"> ${ input [ "vout" ] } </span></p>
2025-03-30 14:51:05 -04:00
<p><button class="view_script" onclick="testnet_generator.viewScript( ' ${ input_txid } _ ${ input [ "vout" ] } ', ' ${ txid } _ ${ index } ', ' ${ unique } ' );">View bitcoin script</button></p>
2025-03-30 14:11:05 -04:00
<div class="btc_script hidden">
<p class="script_arguments_label">Arguments</p>
2025-03-30 14:51:05 -04:00
<div class="script_arguments"></div>
2025-03-30 14:11:05 -04:00
<p class="script_program_label">Program</p>
2025-03-30 14:51:05 -04:00
<div class="script_program"></div>
2025-03-30 14:11:05 -04:00
</div>
2024-11-21 01:24:10 -05:00
</div>
` ;
});
div . getElementsByClassName ( "inputs" )[ 0 ]. innerHTML = inputs_html ;
tx . vout . forEach ( ( output , index ) => {
if ( ! index ) outputs_html = `<p style="text-align: center;">Outputs</p>` ;
var output_id = ` ${ txid } _ ${ index } ` ;
var addy = testnet_generator . utxo_set [ output_id ]. addy ;
2025-03-15 22:25:47 -04:00
try {
addy = tapscript . Address . fromScriptPubKey ( addy , testnet_generator . addykind );
} catch ( e ) {}
2024-11-21 01:24:10 -05:00
var amnt = testnet_generator . utxo_set [ output_id ]. amnt ;
2025-03-15 22:25:47 -04:00
var vout_bg_color = "white" ;
if ( index % 2 ) vout_bg_color = "grey" ;
2024-11-21 01:24:10 -05:00
outputs_html = outputs_html + `
2025-03-15 22:25:47 -04:00
<div class="output ${ vout_bg_color } ">
2025-02-25 17:52:48 -05:00
<p>Vout: <span class="which_vout_is_this"> ${ index } </span></p>
2024-11-21 01:24:10 -05:00
<p>"To" address: <span class="to_address addy_link"> ${ addy } </span></p>
<p>Amount received by this address in this tx: <span class="amnt_received"> ${ testnet_generator . satsToBitcoin ( amnt ) } btc</span></p>
</div>
` ;
});
div . getElementsByClassName ( "outputs" )[ 0 ]. innerHTML = outputs_html ;
var sum_of_inputs = 0 ;
tx . vin . forEach ( input => {
if ( ! idx ) return ;
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
var amnt = testnet_generator . utxo_set [ input_id ]. amnt ;
sum_of_inputs = sum_of_inputs + amnt ;
});
var sum_of_outputs = 0 ;
tx . vout . forEach ( output => sum_of_outputs = sum_of_outputs + Number ( output [ "value" ] ) );
if ( ! idx ) sum_of_outputs = 0 ;
div . getElementsByClassName ( "fee" )[ 0 ]. innerText = testnet_generator . satsToBitcoin ( sum_of_inputs - sum_of_outputs );
txs_html = txs_html + div . innerHTML ;
return txs_html ;
},
2025-03-17 20:48:11 -04:00
showSoloTx : ( txid , txhex , idx , block_mined_if_coinbase ) => {
2024-11-21 01:24:10 -05:00
$ ( '.explorer_txid' ). innerText = `Tx ${ txid . substring ( 0 , 4 ) } ... ${ txid . substring ( txid . length - 4 ) } ` ;
$ ( '.tx_info' ). innerHTML = testnet_generator . showTx ( txhex , idx );
$$ ( '.close_explorer' ). forEach ( close_btn => {
close_btn . onclick = () => {
showPage ( 'stuff_thats_not_an_explorer' );
}
});
var tx = tapscript . Tx . decode ( txhex );
var inputs_count = tx . vin . length ;
$ ( '.inputs_count' ). innerText = inputs_count ;
var outputs_count = tx . vout . length ;
$ ( '.outputs_count' ). innerText = outputs_count ;
if ( ! isNaN ( testnet_generator . utxo_set [ ` ${ txid } _0` ]. block_created ) ) {
var mined_in_block = testnet_generator . utxo_set [ ` ${ txid } _0` ]. block_created ;
$ ( '.mined_in_block' ). innerHTML = ` ${ mined_in_block } [<span class="block_link" data-blocknum=" ${ mined_in_block } ">view block</span>]` ;
$ ( '.block_link' ). onclick = e => { testnet_generator . showBlockDetails ( e . target . getAttribute ( "data-blocknum" ) )}
var blockhash = testnet_generator . intToHex ( mined_in_block ). padStart ( 64 , "0" );
var timestamp = testnet_generator . blocks [ blockhash ][ "timestamp" ];
$ ( '.tx_timestamp' ). innerText = String ( new Date ( timestamp * 1000 ) );
} else {
var mined_in_block = "not mined yet" ;
2025-03-17 20:48:11 -04:00
if ( block_mined_if_coinbase ) mined_in_block = block_mined_if_coinbase ;
2024-11-21 01:24:10 -05:00
$ ( '.mined_in_block' ). innerHTML = ` ${ mined_in_block } ` ;
}
2025-11-11 19:31:31 +00:00
var tx_size = tapscript . Tx . util . getTxSize ( txhex ). vsize ;
2024-11-21 01:24:10 -05:00
$ ( '.tx_size' ). innerText = ` ${ tx_size } bytes` ;
var sum_of_inputs = 0 ;
tx . vin . forEach ( input => {
if ( ! idx ) return ;
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
var amnt = testnet_generator . utxo_set [ input_id ]. amnt ;
sum_of_inputs = sum_of_inputs + amnt ;
});
var sum_of_outputs = 0 ;
tx . vout . forEach ( output => sum_of_outputs = sum_of_outputs + Number ( output [ "value" ] ) );
if ( ! idx ) sum_of_outputs = 0 ;
var tx_fee_in_sats = sum_of_inputs - sum_of_outputs ;
$ ( '.tx_fee' ). innerText = testnet_generator . satsToBitcoin ( tx_fee_in_sats );
$ ( '.tx_feerate' ). innerText = ` ${ ( tx_fee_in_sats / tx_size ). toFixed ( 2 ) } sats per byte` ;
window . scrollTo ( 0 , 0 );
$$ ( '.txid_link' ). forEach ( item => {
item . onclick = e => {
var txid = e . target . innerText ;
var txhex = e . target . getAttribute ( "data-txhex" );
var idx = Number ( e . target . getAttribute ( "data-idx" ) );
showPage ( 'tx_explorer' );
2025-03-17 20:48:11 -04:00
testnet_generator . showSoloTx ( txid , txhex , idx , block_mined_if_coinbase );
2024-11-21 01:24:10 -05:00
}
});
2025-03-30 14:57:55 -04:00
$$ ( '.addy_link' ). forEach ( item => {
item . onclick = e => {
var addy = e . target . innerText ;
showPage ( 'addy_explorer' );
testnet_generator . showAddyInfo ( addy );
}
});
2024-11-21 01:24:10 -05:00
},
2025-03-15 22:25:47 -04:00
showAddyInfo : addy => {
2024-11-21 01:24:10 -05:00
$ ( '.explorer_addy' ). innerText = ` ${ addy . substring ( 0 , 4 ) } ... ${ addy . substring ( addy . length - 4 ) } ` ;
$ ( '.full_address' ). innerText = addy ;
2025-03-15 22:25:47 -04:00
var spky = addy !== "51024e73" ? tapscript . Script . encode ( tapscript . Address . toScriptPubKey ( addy ) ). hex . substring ( 2 ) : "51024e73" ;
$ ( '.received_count' ). innerText = testnet_generator . addresses [ spky ]. received . length ;
$ ( '.sent_count' ). innerText = testnet_generator . addresses [ spky ]. sent . length ;
2024-11-21 01:24:10 -05:00
var sent = [];
var total_sent = 0 ;
var total_sent_to_self = 0 ;
2025-03-15 22:25:47 -04:00
testnet_generator . addresses [ spky ]. sent . forEach ( input_id => {
2024-11-21 01:24:10 -05:00
var txhex = testnet_generator . utxo_set [ input_id . substring ( 0 , 65 ) + "0" ]. txhex ;
sent . push ( [ txhex , 1 , undefined ] );
var tx = tapscript . Tx . decode ( txhex );
var input_i_seek = Number ( input_id . substring ( 65 ) );
var input = tx . vin [ input_i_seek ];
var utxo_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
var recipients = [];
tx . vout . forEach ( ( vout , index ) => {
2025-03-15 22:25:47 -04:00
var recipient = vout [ "scriptPubKey" ];
if ( recipient !== spky ) return ;
2024-11-21 01:24:10 -05:00
total_sent_to_self = total_sent_to_self + Number ( vout [ "value" ] );
});
var txinfo = testnet_generator . utxo_set [ utxo_id ];
total_sent = total_sent + txinfo . amnt ;
});
$ ( '.total_sent' ). innerText = ` ${ testnet_generator . satsToBitcoin ( total_sent - total_sent_to_self ) } btc` ;
var received = [];
var total_received = 0 ;
2025-03-15 22:25:47 -04:00
testnet_generator . addresses [ spky ]. received . forEach ( output_id => {
2024-11-21 01:24:10 -05:00
var txinfo = testnet_generator . utxo_set [ output_id ];
received . push ( [ txinfo . txhex , txinfo . idx , txinfo . block_created ] );
total_received = total_received + txinfo . amnt ;
});
$ ( '.total_received' ). innerText = ` ${ testnet_generator . satsToBitcoin ( total_received - total_sent_to_self ) } btc` ;
$ ( '.addy_balance' ). innerText = ` ${ testnet_generator . satsToBitcoin ( ( total_received - total_sent_to_self ) - ( total_sent - total_sent_to_self ) ) } btc` ;
var txs_html = `<p style="text-align: center; font-weight: bold;">Transactions where this address received money</p>` ;
received . forEach ( tx => {
var [ txhex , idx , blocknum ] = tx ;
txs_html = txs_html + testnet_generator . showTx ( txhex , idx , blocknum );
});
txs_html = txs_html + `<p style="text-align: center; font-weight: bold;">Transactions where this address sent money</p>` ;
sent . forEach ( tx => {
var [ txhex , idx , blocknum ] = tx ;
txs_html = txs_html + testnet_generator . showTx ( txhex , idx , blocknum );
});
$ ( '.addy_tx_info' ). innerHTML = txs_html ;
$$ ( '.close_explorer' ). forEach ( close_btn => {
close_btn . onclick = () => {
showPage ( 'stuff_thats_not_an_explorer' );
}
});
window . scrollTo ( 0 , 0 );
$$ ( '.txid_link' ). forEach ( item => {
item . onclick = e => {
var txid = e . target . innerText ;
var txhex = e . target . getAttribute ( "data-txhex" );
var idx = Number ( e . target . getAttribute ( "data-idx" ) );
showPage ( 'tx_explorer' );
2025-03-17 20:48:11 -04:00
var block_mined_if_coinbase = null ;
//TODO: fix the block_mined_if_coinbase value
var block_mined_if_coinbase = null ;
if ( idx === 0 ) block_mined_if_coinbase = 5 ;
testnet_generator . showSoloTx ( txid , txhex , idx , block_mined_if_coinbase );
2024-11-21 01:24:10 -05:00
}
});
},
showBlockDetails : num => {
var blockhash = testnet_generator . intToHex ( num ). padStart ( 64 , "0" );
var timestamp = testnet_generator . blocks [ blockhash ][ "timestamp" ];
var txs = testnet_generator . blocks [ blockhash ][ "txs" ];
showPage ( 'block_explorer' );
$ ( '.explorer_block_num' ). innerText = `Block ${ num } ` ;
$$ ( '.close_explorer' ). forEach ( close_btn => {
close_btn . onclick = () => {
showPage ( 'stuff_thats_not_an_explorer' );
}
});
$ ( '.tx_count' ). innerText = txs . length ;
$ ( '.block_timestamp' ). innerText = String ( new Date ( timestamp * 1000 ) );
$ ( '.block_fees' ). innerText = ` ${ testnet_generator . satsToBitcoin ( testnet_generator . blocks [ blockhash ][ "fees" ] ) } btc` ;
var txs_html = `` ;
txs . forEach ( ( txhex , idx ) => {
txs_html = txs_html + testnet_generator . showTx ( txhex , idx , num );
});
$ ( '.txs' ). innerHTML = txs_html ;
$$ ( '.txid_link' ). forEach ( item => {
item . onclick = e => {
var txid = e . target . innerText ;
var txhex = e . target . getAttribute ( "data-txhex" );
var idx = Number ( e . target . getAttribute ( "data-idx" ) );
showPage ( 'tx_explorer' );
testnet_generator . showSoloTx ( txid , txhex , idx , num );
}
});
$$ ( '.addy_link' ). forEach ( item => {
item . onclick = e => {
var addy = e . target . innerText ;
showPage ( 'addy_explorer' );
testnet_generator . showAddyInfo ( addy );
}
});
},
2025-03-15 22:25:47 -04:00
processExistingBlocks : async () => {
$ ( '.no_blocks_msg' ). innerText = "syncing..." ;
var privkey = null ;
if ( params . hasOwnProperty ( "privkey" ) ) privkey = params . privkey ;
var pubkey = privkey ? testnet_generator . getPubkey ( privkey ) : params . pubkey ;
var relay = testnet_generator . relays [ 0 ];
var num_of_blocks_expected = 1 ;
var events = await super_nostr . getEvents ( relay , null , [ pubkey ], [ 10426 ], null , null , 1 );
if ( events . length ) num_of_blocks_expected = Number ( events [ 0 ]. content );
2025-03-17 20:48:11 -04:00
else return alert ( 'error: the block headers could not be synced, so the blocks cannot be synced either. refresh the page to try again' );
2025-03-15 22:25:47 -04:00
$ ( '.no_blocks_msg' ). innerText = `synced 0 out of ${ num_of_blocks_expected } blocks` ;
2025-03-17 20:48:11 -04:00
var outerLoop = async ( since = null ) => {
var since_to_use_in_outer_loop = since ;
var syncLoop = async ( since = null ) => {
var ids = null ;
var authors = [ pubkey ];
var kinds = [ 60426 ];
var until = null ;
var limit = 15 ;
var all_events = [];
var innerLoop = async since => {
var events = await super_nostr . getEvents ( relay , ids , authors , kinds , until , since , limit );
var events_already_known = 0 ;
events . forEach ( event => {
var event_ids = [];
all_events . forEach ( evt => event_ids . push ( evt . id ) );
if ( event_ids . includes ( event . id ) ) {
events_already_known = events_already_known + 1 ;
return ;
}
all_events . push ( event );
});
$ ( '.no_blocks_msg' ). innerText = `synced ${ all_events . length } out of ${ num_of_blocks_expected } blocks` ;
if ( ! events . length || events_already_known === events . length ) return ;
if ( all_events . length === num_of_blocks_expected ) {
$ ( '.no_blocks_msg' ). innerText = `synced ${ num_of_blocks_expected } out of ${ num_of_blocks_expected } blocks` ;
await testnet_generator . waitSomeTime ( 200 );
2025-03-15 22:25:47 -04:00
return ;
}
2025-03-17 20:48:11 -04:00
var since = events [ 0 ]. created_at - 1 ;
since_to_use_in_outer_loop = since ;
await testnet_generator . waitSomeTime ( 1 );
await innerLoop ( since );
2025-03-15 22:25:47 -04:00
}
await innerLoop ( since );
2025-03-17 20:48:11 -04:00
return all_events ;
}
var all_events = await syncLoop ( since );
if ( ! all_events . length && $ ( '.generate_block' ) ) $ ( '.generate_block' ). disabled = false ;
all_events . sort ( ( a , b ) => parseInt ( a . tags [ 0 ][ 1 ], 16 ) - parseInt ( b . tags [ 0 ][ 1 ], 16 ) );
var i ; for ( i = 0 ; i < all_events . length ; i ++ ) {
var data = all_events [ i ];
var first_block_id = data . tags [ 0 ][ 1 ];
var blocks = JSON . parse ( data . content );
var j ; for ( j = 0 ; j < blocks . length ; j ++ ) {
var block_data = blocks [ j ];
var blocknum = parseInt ( first_block_id , 16 ) + j ;
var block_id = testnet_generator . intToHex ( blocknum ). padStart ( 64 , "0" );
if ( Object . keys ( testnet_generator . blocks ). includes ( block_id ) ) continue ;
testnet_generator . blocks_relayed . push ( block_id );
testnet_generator . makeBlock ( block_id , block_data );
}
}
if ( $ ( '.generate_block' ) ) $ ( '.generate_block' ). disabled = false ;
if ( params . hasOwnProperty ( "pubkey" ) ) {
await testnet_generator . waitSomeTime ( 5_000 );
await outerLoop ( since_to_use_in_outer_loop );
} else {
testnet_generator . relayBlocks ();
2024-11-21 01:24:10 -05:00
}
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
outerLoop ();
},
validateTx : async ( txhex , allow_zero_fee , parent_tx ) => {
var tx = null ;
try {
tx = tapscript . Tx . decode ( txhex );
} catch ( e ) {}
if ( ! tx || typeof tx != "object" ) return 'err 0, that tx was invalid for unknown reasons' ;
try {
2025-03-15 22:25:47 -04:00
var sum_of_inputs = 0 ;
tx . vin . forEach ( input => {
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
2025-03-17 20:48:11 -04:00
var input_to_work_with = testnet_generator . utxo_set [ input_id ];
if ( ! input_to_work_with && !! parent_tx ) {
var txid_of_parent = tapscript . Tx . util . getTxid ( parent_tx );
if ( txid_of_parent === input [ "txid" ] ) {
var parent_tx_to_work_with = tapscript . Tx . decode ( parent_tx );
if ( parent_tx_to_work_with . vout . length < input [ "vout" ] ) throw ( 'input does not exist' );
input_to_work_with = parent_tx_to_work_with . vout [ input [ "vout" ] ];
}
}
var amnt = input_to_work_with . hasOwnProperty ( "amnt" ) ? input_to_work_with . amnt : Number ( input_to_work_with . value );
2025-03-15 22:25:47 -04:00
sum_of_inputs = sum_of_inputs + amnt ;
});
var sum_of_outputs = 0 ;
2025-03-17 20:48:11 -04:00
tx . vout . forEach ( output => sum_of_outputs = sum_of_outputs + Number ( output [ "value" ] ) );
var tx_fee_in_sats = sum_of_inputs - sum_of_outputs ;
2025-11-15 23:47:02 +00:00
if ( tx_fee_in_sats < 0 ) return `that tx had more money in its outputs than its inputs, which is not allowed -- no printing money! (specifically, that tx had ${ sum_of_inputs } sats in its inputs and ${ sum_of_outputs } sats in its outputs)` ;
2025-11-11 19:31:31 +00:00
var tx_size = tapscript . Tx . util . getTxSize ( txhex ). vsize ;
2025-03-17 20:48:11 -04:00
var feerate = Number ( ( tx_fee_in_sats / tx_size ). toFixed ( 2 ) );
} catch ( e ) {
console . log ( tx );
return 'err 1, that tx was invalid for unknown reasons' ;
}
2025-11-15 23:47:02 +00:00
if ( feerate < 0 ) return `nice try, attempting to print money via tx fees! But it didn't work, your tx is invalid` ;
2025-03-17 20:48:11 -04:00
if ( feerate === 0 && ! allow_zero_fee ) return 'that tx paid no fee' ;
if ( feerate < 1 && ! allow_zero_fee ) return 'that tx paid a fee between 0 and 1 sat per byte, which is not allowed' ;
var input_that_isnt_real = - 1 ;
var input_with_insufficient_witness_length = - 1 ;
var input_with_too_many_witness_items = - 1 ;
var input_with_invalid_sig = - 1 ;
var input_whose_stack_is_too_small = - 1 ;
var input_with_improperly_formatted_sig = - 1 ;
var input_with_improperly_formatted_element = - 1 ;
var input_with_improperly_formatted_element = - 1 ;
var input_whose_script_failed_a_verify_opcode = - 1 ;
var input_with_op_nop = - 1 ;
var input_whose_script_left_multiple_elements_on_the_stack = - 1 ;
var input_whose_script_ended_on_false = - 1 ;
var input_with_if_branch_preceded_by_something_other_than_1_or_0 = - 1 ;
var input_with_script_containing_op_return = - 1 ;
var input_whose_script_treats_a_string_as_an_integer = - 1 ;
var input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack = - 1 ;
var input_whose_script_failed_CLTV_due_to_not_enough_time_passed = - 1 ;
var input_whose_script_uses_a_negative_number_for_CLTV_or_CSV = - 1 ;
var input_whose_locktime_value_does_not_match_the_CLTV_value = - 1 ;
var input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out = - 1 ;
var input_whose_script_failed_CSV = - 1 ;
var input_whose_script_failed_relative_timelock = - 1 ;
var i ; for ( i = 0 ; i < tx . vin . length ; i ++ ) {
var input = tx . vin [ i ];
var index = i ;
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
var input_exists = false ;
if ( input_id in testnet_generator . utxo_set ) input_exists = true ;
else input_exists = false ;
var item = testnet_generator . utxo_set [ input_id ];
if ( item && item . spent ) input_exists = false ;
var input_to_work_with = null ;
if ( ! item && parent_tx ) {
var txid_of_parent = tapscript . Tx . util . getTxid ( parent_tx );
if ( txid_of_parent === input [ "txid" ] ) {
var parent_tx_to_work_with = tapscript . Tx . decode ( parent_tx );
if ( parent_tx_to_work_with . vout . length < input [ "vout" ] ) {
input_that_isnt_real = index ;
break ;
}
input_to_work_with = parent_tx_to_work_with . vout [ input [ "vout" ] ];
input_to_work_with . addy = input_to_work_with . scriptPubKey ;
input_to_work_with . amnt = Number ( input_to_work_with . value );
input_to_work_with . block_created = Object . keys ( testnet_generator . blocks ). length - 1 ;
try {
input_to_work_with . addy = tapscript . Address . fromScriptPubKey ( input_to_work_with . addy );
} catch ( e ) {}
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
}
if ( ! input_exists && ! input_to_work_with ) {
input_that_isnt_real = index ;
break ;
}
if ( item ) input_to_work_with = item ;
var addy = input_to_work_with . addy ;
var amnt_to_use_in_moments = input_to_work_with . amnt ;
try {
addy = tapscript . Address . fromScriptPubKey ( addy , testnet_generator . addykind );
} catch ( e ) {}
var scpk = testnet_generator . isValidAddress ( addy ) ? tapscript . Address . toScriptPubKey ( addy ) : addy ;
tx . vin [ i ]. prevout = {
value : amnt_to_use_in_moments ,
scriptPubKey : scpk ,
}
}
if ( input_that_isnt_real >= 0 ) return `that tx was invalid because input ${ input_that_isnt_real } tries to spend a utxo that does not exist or is already spent by another transaction in the mempool` ;
//note: locktime is encoded as little endian
var locktime = tx . locktime ;
var blockheight = Object . keys ( testnet_generator . blocks ). length - 1 ;
if ( locktime < 500000000 && locktime > blockheight ) return `that tx was invalid because its locktime is set to a value greater than the current blockheight -- wait longer! The current blockheight is ${ blockheight } and your locktime is set to ${ locktime } so you need to wait til then` ;
var current_block = Object . keys ( testnet_generator . blocks ). sort ()[ Object . keys ( testnet_generator . blocks ). sort (). length - 1 ];
var current_blocktime = testnet_generator . blocks [ current_block ]. timestamp ;
if ( locktime >= 500000000 && locktime > current_blocktime ) return `that tx was invalid because its locktime is set to a value greater than the median timestamp of the last 11 blocks (actually in this implementation I don't do the median stuff, I just check the latest block's timestamp) -- wait longer! The current timestamp is ${ current_blocktime } (which is ${ String ( new Date ( current_blocktime * 1000 ) ) } ) and your locktime is set to ${ locktime } (which is ${ String ( new Date ( locktime * 1000 ) ) } ) so you need to wait til then` ;
var i ; for ( i = 0 ; i < tx . vin . length ; i ++ ) {
var input = tx . vin [ i ];
var index = i ;
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
// var input_to_work_with_here = input_to_work_with ? input_to_work_with : item;
// var addy = input_to_work_with_here.addy;
var addy = input . prevout . scriptPubKey ;
var type = "p2tr" ;
if ( addy !== "51024e73" ) {
addy = testnet_generator . isValidAddress ( addy ) ? addy : tapscript . Address . fromScriptPubKey ( addy , testnet_generator . addykind );
var type = tapscript . Address . decode ( addy ). type ;
}
var sequence_binary = testnet_generator . hexToBinary ( input . sequence );
var first_bit = Number ( sequence_binary [ 0 ] );
var tenth_bit = Number ( sequence_binary [ 9 ] );
var sequence = testnet_generator . littleEndianSignedHexToDecimal ( input . sequence );
var sequence_value = null ;
if ( ! first_bit ) {
//the "disable flag" is not set so we are about to enforce the sequence number as a relative locktime
var mined_in_block = input_to_work_with . block_created ;
var current_blockheight = Object . keys ( testnet_generator . blocks ). length - 1 ;
sequence_value = input . sequence . substring ( 4 );
if ( sequence_value . substring ( 0 , 2 ) != "00" ) {
sequence_value = parseInt ( sequence_value , 16 );
} else {
sequence_value = parseInt ( sequence_value . substring ( 2 ), 16 );
}
if ( ! tenth_bit ) {
//the "type" bit is not set so this is a block-based relative timelock rather
//than a timestamp-based relative timelock
if ( ! mined_in_block && parseInt ( sequence_binary , 2 ) > 0 ) {
input_whose_script_failed_relative_timelock = index ;
break ;
} else if ( mined_in_block ) {
var num_of_blocks_since_parent = current_blockheight - mined_in_block ;
if ( num_of_blocks_since_parent > 0 ) num_of_blocks_since_parent = num_of_blocks_since_parent + 1 ;
if ( num_of_blocks_since_parent < sequence_value ) {
input_whose_script_failed_relative_timelock = index ;
break ;
}
}
} else {
//the "type" bit is set so this is a timestmap-based relative timelock rather
//than a block-based relative timelock
if ( ! mined_in_block && parseInt ( sequence_binary , 2 ) > 0 ) {
input_whose_script_failed_relative_timelock = index ;
break ;
} else if ( mined_in_block ) {
var id_of_parent_block = testnet_generator . intToHex ( mined_in_block ). padStart ( 64 , "0" );
var mined_at_timestamp = testnet_generator . blocks [ id_of_parent_block ]. timestamp ;
var time_to_wait = sequence_value * 512 ;
var now = Math . floor ( Date . now () / 1000 );
var time_since_parent = now - mined_at_timestamp ;
if ( time_since_parent < time_to_wait ) {
input_whose_script_failed_relative_timelock = index ;
break ;
}
}
}
}
if ( type === "p2tr" ) {
if ( addy === "51024e73" ) {
//p2a addresses can't have witness items
if ( input . witness . length ) {
input_with_too_many_witness_items = index ;
break ;
}
continue ;
}
//ensure at least one element exists on the witness stack
if ( ! input . witness . length ) {
console . log ( input , addy );
input_with_insufficient_witness_length = index ;
break ;
}
//if only one element exists on the witness stack, treat it as a sig and check that it is valid for the taproot address when treated as a pubkey
if ( input . witness . length === 1 ) {
var sig = input . witness [ 0 ];
2025-11-12 10:17:44 +00:00
var sig_length_is_good = false ;
if ( sig . length === 128 || sig . length === 130 || ! sig . length ) sig_length_is_good = true ;
if ( sig . length === 130 && sig . substring ( 128 ) != "01" && sig . substring ( 128 ) != "02" && sig . substring ( 128 ) != "03" && sig . substring ( 128 ) != "81" && sig . substring ( 128 ) != "82" && sig . substring ( 128 ) != "83" ) sig_length_is_good = false ;
if ( ! sig_length_is_good || ( sig . length && ! testnet_generator . isValidHex ( sig ) ) ) {
input_with_improperly_formatted_sig = index ;
2025-11-15 01:38:27 +00:00
return 'that tx was invalid because it had an improperly formatted sig' ;
2025-11-12 10:17:44 +00:00
}
if ( ! sig . length ) {
2025-11-15 01:38:27 +00:00
input_with_improperly_formatted_sig = index ;
return 'that tx was invalid because it had an improperly formatted sig' ;
2025-11-12 10:17:44 +00:00
} else {
var sigflag = 1 ; //sighash_all
if ( sig . length === 130 && sig . substring ( 128 ) === "02" ) sigflag = 2 ; //sighash_none
if ( sig . length === 130 && sig . substring ( 128 ) === "03" ) sigflag = 3 ; //sighash_single
if ( sig . length === 130 && sig . substring ( 128 ) === "81" ) sigflag = 128 | 1 ; //sighash_all | anyone_can_pay
if ( sig . length === 130 && sig . substring ( 128 ) === "82" ) sigflag = 128 | 2 ; //sighash_none | anyone_can_pay
if ( sig . length === 130 && sig . substring ( 128 ) === "83" ) sigflag = 128 | 3 ; //sighash_single | anyone_can_pay
if ( sig . length === 130 ) sig = sig . substring ( 0 , 128 );
if ( sigflag != 1 ) var sighash = tapscript . Signer . taproot . hash ( tx , index , { sigflag }). hex ;
2025-11-15 03:25:53 +00:00
else var sighash = tapscript . Signer . taproot . hash ( tx , index ). hex ;
tx . vin . forEach ( ( vin , index ) => tx . vin [ index ]. prevout . value = Number ( vin . prevout . value ) );
tx . vout . forEach ( ( vout , index ) => tx . vout [ index ]. value = Number ( vout . value ) );
2025-11-12 10:17:44 +00:00
var pubkey = tapscript . Address . decode ( addy ). script [ 1 ];
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
if ( ! sig_is_valid ) {
input_with_invalid_sig = index ;
2025-11-15 03:25:53 +00:00
return `that tx was invalid because input ${ i } had a sig that failed an implicit verify opcode (the stack only had a single sig on it, and in that case the sig must validate, which is what op_verify ensures)` ;
2025-11-12 10:17:44 +00:00
}
}
2025-03-17 20:48:11 -04:00
} else {
//if there are multiple elements on the witness stack, treat the second-to-last element as the script and execute it with the elements prior to that as params
var script = input . witness [ input . witness . length - 2 ];
var params = [];
input . witness . forEach ( ( element , element_index ) => {
if ( element === "OP_0" || element === "OP_FALSE" || element === "" ) element = 0 ;
if ( element === "OP_1" || element === "OP_TRUE" ) element = 1 ;
if ( element_index < input . witness . length - 2 ) params . push ( element );
});
var script_as_asm = tapscript . Script . fmt . toAsm ( script );
var tapleaf = tapscript . Tap . encodeScript ( script_as_asm );
var ifs_stack = [ true ];
var alt_stack = [];
console . log ( "params:" );
console . log ( params );
console . log ( "script:" );
console . log ( script_as_asm );
var exeloop = async () => {
var element = script_as_asm [ 0 ];
console . log ( element );
if ( ! ifs_stack . includes ( false ) && ! element . startsWith ( "OP_" ) ) {
if ( ! testnet_generator . isValidHex ( element ) && element !== "" ) {
input_with_improperly_formatted_element = index ;
return ;
}
//if the element is 0, push 0
if ( element === "" ) element = 0 ;
//convert hex values less than 4 bytes to the corresponding number
if ( element !== 0 && element . length / 2 < 5 ) element = testnet_generator . littleEndianSignedHexToDecimal ( element );
params . push ( element );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_CHECKSIG" ) {
// console.log( "params:" );
// console.log( JSON.parse( JSON.stringify( params ) ) );
// console.log( "script:" );
// console.log( JSON.parse( JSON.stringify( script_as_asm ) ) );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var pubkey = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var sig = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
// console.log( "pubkey:" );
// console.log( pubkey );
// console.log( "sig:" );
// console.log( sig );
var sig_length_is_good = false ;
if ( sig . length === 128 || sig . length === 130 || ! sig . length ) sig_length_is_good = true ;
if ( sig . length === 130 && sig . substring ( 128 ) != "01" && sig . substring ( 128 ) != "02" && sig . substring ( 128 ) != "03" && sig . substring ( 128 ) != "81" && sig . substring ( 128 ) != "82" && sig . substring ( 128 ) != "83" ) sig_length_is_good = false ;
2025-11-12 10:17:44 +00:00
if ( ! sig_length_is_good || ( sig . length && ! testnet_generator . isValidHex ( sig ) ) ) {
input_with_improperly_formatted_sig = index ;
return ;
}
2025-03-17 20:48:11 -04:00
if ( ! sig . length ) {
params . push ( 0 );
} else {
var sigflag = 1 ; //sighash_all
2025-04-13 13:35:51 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "02" ) sigflag = 2 ; //sighash_none
if ( sig . length === 130 && sig . substring ( 128 ) === "03" ) sigflag = 3 ; //sighash_single
2025-04-13 03:57:57 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "81" ) sigflag = 128 | 1 ; //sighash_all | anyone_can_pay
2025-04-13 13:35:51 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "82" ) sigflag = 128 | 2 ; //sighash_none | anyone_can_pay
if ( sig . length === 130 && sig . substring ( 128 ) === "83" ) sigflag = 128 | 3 ; //sighash_single | anyone_can_pay
2025-03-17 20:48:11 -04:00
if ( sig . length === 130 ) sig = sig . substring ( 0 , 128 );
if ( sigflag != 1 ) var sighash = tapscript . Signer . taproot . hash ( tx , index , { extension : tapleaf , sigflag }). hex ;
else var sighash = tapscript . Signer . taproot . hash ( tx , index , { extension : tapleaf } ). hex ;
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
if ( sig_is_valid ) {
params . push ( 1 );
}
else {
input_with_invalid_sig = index ;
return ;
}
}
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_CHECKSIGVERIFY" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var pubkey = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var sig = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
var sig_length_is_good = false ;
if ( sig . length === 128 || sig . length === 130 || ! sig . length ) sig_length_is_good = true ;
if ( sig . length === 130 && sig . substring ( 128 ) != "01" && sig . substring ( 128 ) != "02" && sig . substring ( 128 ) != "03" && sig . substring ( 128 ) != "81" && sig . substring ( 128 ) != "82" && sig . substring ( 128 ) != "83" ) sig_length_is_good = false ;
2025-11-12 10:17:44 +00:00
if ( ! sig_length_is_good || ( sig . length && ! testnet_generator . isValidHex ( sig ) ) ) {
input_with_improperly_formatted_sig = index ;
return ;
}
2025-03-17 20:48:11 -04:00
if ( ! sig . length ) {
input_whose_script_failed_a_verify_opcode = index ;
return ;
} else {
var sigflag = 1 ; //sighash_all
2025-04-13 13:35:51 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "02" ) sigflag = 2 ; //sighash_none
if ( sig . length === 130 && sig . substring ( 128 ) === "03" ) sigflag = 3 ; //sighash_single
2025-04-13 03:57:57 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "81" ) sigflag = 128 | 1 ; //sighash_all | anyone_can_pay
2025-04-13 13:35:51 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "82" ) sigflag = 128 | 2 ; //sighash_none | anyone_can_pay
if ( sig . length === 130 && sig . substring ( 128 ) === "83" ) sigflag = 128 | 3 ; //sighash_single | anyone_can_pay
2025-03-17 20:48:11 -04:00
if ( sig . length === 130 ) sig = sig . substring ( 0 , 128 );
if ( sigflag != 1 ) var sighash = tapscript . Signer . taproot . hash ( tx , index , { extension : tapleaf , sigflag }). hex ;
else var sighash = tapscript . Signer . taproot . hash ( tx , index , { extension : tapleaf } ). hex ;
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
if ( ! sig_is_valid ) {
input_whose_script_failed_a_verify_opcode = index ;
return ;
}
}
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_CHECKSIGADD" ) {
// console.log( "params:" );
// console.log( params );
// console.log( "script:" );
// console.log( script_as_asm );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var pubkey = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_to_add_to = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var sig = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
var sig_length_is_good = false ;
if ( sig . length === 128 || sig . length === 130 || ! sig . length ) sig_length_is_good = true ;
if ( sig . length === 130 && sig . substring ( 128 ) != "01" && sig . substring ( 128 ) != "02" && sig . substring ( 128 ) != "03" && sig . substring ( 128 ) != "81" && sig . substring ( 128 ) != "82" && sig . substring ( 128 ) != "83" ) sig_length_is_good = false ;
2025-11-12 10:17:44 +00:00
if ( ! sig_length_is_good || ( sig . length && ! testnet_generator . isValidHex ( sig ) ) ) {
input_with_improperly_formatted_sig = index ;
return ;
}
2025-03-17 20:48:11 -04:00
// console.log( "num_to_add_to:", num_to_add_to, "pubkey:", pubkey, "sig:", sig );
if ( ! sig . length ) {
params . push ( num_to_add_to );
} else {
var sigflag = 1 ; //sighash_all
2025-04-13 13:35:51 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "02" ) sigflag = 2 ; //sighash_none
if ( sig . length === 130 && sig . substring ( 128 ) === "03" ) sigflag = 3 ; //sighash_single
2025-04-13 03:57:57 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "81" ) sigflag = 128 | 1 ; //sighash_all | anyone_can_pay
2025-04-13 13:35:51 -06:00
if ( sig . length === 130 && sig . substring ( 128 ) === "82" ) sigflag = 128 | 2 ; //sighash_none | anyone_can_pay
if ( sig . length === 130 && sig . substring ( 128 ) === "83" ) sigflag = 128 | 3 ; //sighash_single | anyone_can_pay
2025-03-17 20:48:11 -04:00
if ( sig . length === 130 ) sig = sig . substring ( 0 , 128 );
if ( sigflag != 1 ) var sighash = tapscript . Signer . taproot . hash ( tx , index , { extension : tapleaf , sigflag }). hex ;
else var sighash = tapscript . Signer . taproot . hash ( tx , index , { extension : tapleaf } ). hex ;
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
// console.log( "sig is valid, right?", sig_is_valid, "pubkey:", pubkey, "sighash:", sighash, "sig:", sig );
if ( sig_is_valid ) {
// console.log( 'I will increment this:', num_to_add_to );
num_to_add_to = num_to_add_to + 1 ;
params . push ( num_to_add_to );
// console.log( 'done, now it is this:', num_to_add_to );
} else {
input_with_invalid_sig = index ;
return ;
}
}
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_0" || element === "OP_FALSE" || element === "" ) {
params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_1" || element === "OP_TRUE" ) {
params . push ( 1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_2" ) {
params . push ( 2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_3" ) {
params . push ( 3 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_4" ) {
params . push ( 4 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_5" ) {
params . push ( 5 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_6" ) {
params . push ( 6 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_7" ) {
params . push ( 7 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_8" ) {
params . push ( 8 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_9" ) {
params . push ( 9 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_10" ) {
params . push ( 10 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_11" ) {
params . push ( 11 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_12" ) {
params . push ( 12 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_13" ) {
params . push ( 13 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_14" ) {
params . push ( 14 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_15" ) {
params . push ( 15 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_16" ) {
params . push ( 16 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_EQUAL" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( item_2 === item_1 ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_EQUALVERIFY" ) {
// console.log( "params:" );
// console.log( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( item_2 !== item_1 ) {
input_whose_script_failed_a_verify_opcode = index ;
return ;
}
// console.log( "params:" );
// console.log( params );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_GREATERTHAN" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( num_2 > num_1 ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_LESSTHAN" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( num_2 < num_1 ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_GREATERTHANOREQUAL" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
// console.log( num_2, num_1, "the first number should be greater than or equal to the second number" );
if ( num_2 >= num_1 ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_LESSTHANOREQUAL" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( num_2 <= num_1 ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_SHA1" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_hash = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
else {
item_to_hash = item_to_hash . toString ( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash ;
var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
}
var hashed = await testnet_generator . sha1 ( as_bytes );
params . push ( hashed );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_SHA256" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_hash = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
else {
item_to_hash = item_to_hash . toString ( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash ;
var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
}
var hashed = await testnet_generator . sha256 ( as_bytes );
params . push ( hashed );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_HASH256" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_hash = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
else {
item_to_hash = item_to_hash . toString ( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash ;
var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
}
var hashed = await testnet_generator . sha256 ( as_bytes );
var as_bytes = testnet_generator . hexToBytes ( hashed );
hashed = await testnet_generator . sha256 ( as_bytes );
params . push ( hashed );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_RIPEMD160" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_hash = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
else {
item_to_hash = item_to_hash . toString ( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash ;
var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
}
var hashed = testnet_generator . rmd160 ( as_bytes );
params . push ( hashed );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_HASH160" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_hash = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
else {
item_to_hash = item_to_hash . toString ( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash ;
var as_bytes = testnet_generator . hexToBytes ( item_to_hash );
}
var hashed = await testnet_generator . sha256 ( as_bytes );
var as_bytes = testnet_generator . hexToBytes ( hashed );
hashed = testnet_generator . rmd160 ( as_bytes );
params . push ( hashed );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_SWAP" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_1 );
params . push ( item_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_DROP" ) {
// console.log( "before acting:" );
// console.log( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
// console.log( "results:" );
// console.log( params );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_2DROP" ) {
// console.log( "before acting:" );
// console.log( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
// console.log( "results:" );
// console.log( params );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_1NEGATE" ) {
params . push ( - 1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_NOP" ) {
input_with_op_nop = index ;
return ;
}
else if ( element === "OP_IF" ) {
// console.log( "params:" );
// console.log( params );
//add a boolean to the ifs_stack that is false if the previous element
//was something other than OP_1 or OP_TRUE, or if any element in
//the ifs_stack is false (because that means we are in an if_branch
//that should not be executed) -- otherwise true
if ( ifs_stack . includes ( false ) ) {
ifs_stack . push ( false );
} else {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_check = params [ params . length - 1 ];
// console.log( "item_to_check:", item_to_check );
if ( item_to_check !== 1 && item_to_check !== 0 ) {
input_with_if_branch_preceded_by_something_other_than_1_or_0 = index ;
return ;
}
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( item_to_check !== 1 ) ifs_stack . push ( false );
else ifs_stack . push ( true );
}
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( element === "OP_NOTIF" ) {
// console.log( "params:" );
// console.log( params );
if ( ifs_stack . includes ( false ) ) {
ifs_stack . push ( false );
} else {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_check = params [ params . length - 1 ];
// console.log( "item_to_check:", item_to_check );
if ( item_to_check !== 1 && item_to_check !== 0 ) {
input_with_if_branch_preceded_by_something_other_than_1_or_0 = index ;
return ;
}
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( item_to_check !== 0 ) ifs_stack . push ( false );
else ifs_stack . push ( true );
}
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( element === "OP_ELSE" ) {
//flip the last element in the ifs_stack
ifs_stack [ ifs_stack . length - 1 ] = ! ifs_stack [ ifs_stack . length - 1 ];
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( element === "OP_ENDIF" ) {
// console.log( "params:" );
// console.log( params );
//drop the last element in the ifs_stack
ifs_stack . pop ();
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_VERIFY" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_check = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( item_to_check !== 1 ) {
input_whose_script_failed_a_verify_opcode = index ;
return ;
}
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_RETURN" ) {
input_with_script_containing_op_return = index ;
return ;
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_0NOTEQUAL" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_check = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof item_to_check !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( item_to_check === 0 ) params . push ( 0 );
else params . push ( 1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_TOALTSTACK" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_push = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
alt_stack . unshift ( item_to_push );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_FROMALTSTACK" ) {
if ( ! alt_stack . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_pull = alt_stack [ 0 ];
params . push ( item_to_pull );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_IFDUP" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_dup = params [ params . length - 1 ];
var copy = JSON . parse ( JSON . stringify ( item_to_dup ) );
if ( item_to_dup !== 0 ) params . push ( copy );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_DEPTH" ) {
params . push ( params . length );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_NIP" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_OVER" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
var copy = JSON . parse ( JSON . stringify ( item_to_dup ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_2 );
params . push ( item_1 );
params . push ( copy );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_PICK" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_to_pick = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof num_to_pick !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( num_to_pick > 2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( num_to_pick < - 2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( params . length < num_to_pick ) {
input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack = index ;
return ;
}
var item_to_dup = params [ params . length - ( num_to_pick + 1 ) ];
var copy = JSON . parse ( JSON . stringify ( item_to_dup ) );
params . push ( copy );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_ROLL" ) {
// console.log( "before acting:" );
// console.log( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var num_to_pick = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof num_to_pick !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( num_to_pick > 2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( num_to_pick < - 2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( params . length < num_to_pick ) {
input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack = index ;
return ;
}
var item_to_move = params [ params . length - ( num_to_pick + 1 ) ];
var copy = JSON . parse ( JSON . stringify ( item_to_move ) );
// console.log( "results:" );
// console.log( params );
params . splice ( params . length - ( num_to_pick + 1 ), 1 );
params . push ( copy );
// console.log( params );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_ROT" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_3 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_2 );
params . push ( item_3 );
params . push ( item_1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_TUCK" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
var copy = JSON . parse ( JSON . stringify ( item_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( copy );
params . push ( item_1 );
params . push ( item_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_DUP" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_dup = params [ params . length - 1 ];
var copy = JSON . parse ( JSON . stringify ( item_to_dup ) );
params . push ( copy );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_2DUP" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_dup_1 = params [ params . length - 1 ];
var copy_1 = JSON . parse ( JSON . stringify ( item_to_dup_1 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_dup_2 = params [ params . length - 1 ];
var copy_2 = JSON . parse ( JSON . stringify ( item_to_dup_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( copy_2 );
params . push ( copy_1 );
params . push ( copy_2 );
params . push ( copy_1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_3DUP" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_dup_1 = params [ params . length - 1 ];
var copy_1 = JSON . parse ( JSON . stringify ( item_to_dup_1 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_dup_2 = params [ params . length - 1 ];
var copy_2 = JSON . parse ( JSON . stringify ( item_to_dup_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_to_dup_3 = params [ params . length - 1 ];
var copy_3 = JSON . parse ( JSON . stringify ( item_to_dup_3 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( copy_3 );
params . push ( copy_2 );
params . push ( copy_1 );
params . push ( copy_3 );
params . push ( copy_2 );
params . push ( copy_1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_2OVER" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_4 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_3 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
var copy_2 = JSON . parse ( JSON . stringify ( item_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
var copy_1 = JSON . parse ( JSON . stringify ( item_1 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_1 );
params . push ( item_2 );
params . push ( item_3 );
params . push ( item_4 );
params . push ( copy_1 );
params . push ( copy_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_2ROT" ) {
// console.log( "before acting:" );
// console.log( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_6 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_5 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_4 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_3 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_3 );
params . push ( item_4 );
params . push ( item_5 );
params . push ( item_6 );
params . push ( item_1 );
params . push ( item_2 );
// console.log( "results:" );
// console.log( params );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_2SWAP" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_4 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_3 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_3 );
params . push ( item_4 );
params . push ( item_1 );
params . push ( item_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_SIZE" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var item = params [ params . length - 1 ];
if ( typeof item === "number" ) {
if ( item >= - 2147483647 && item < 2147483648 ) var size = 4 ;
if ( item >= - 8388607 && item < 8388608 ) var size = 3 ;
if ( item >= - 32767 && item < 32768 ) var size = 2 ;
if ( item >= - 127 && item < 128 ) var size = 1 ;
if ( item === 0 ) var size = 0 ;
} else {
var size = item . length / 2 ;
}
params . push ( size );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_1ADD" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( input + 1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_1SUB" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( input - 1 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_NEGATE" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( - input );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_ABS" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( input < 0 || ( input === 0 && testnet_generator . isZeroNegative ( input ) ) ) params . push ( - input );
else params . push ( input );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_NOT" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( input === 0 ) var output = 1 ;
else var output = 0 ;
params . push ( output );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_ADD" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( input_1 + input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_SUB" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( input_1 - input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_BOOLAND" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( input_1 !== 1 ) input_1 = 0 ;
if ( input_2 !== 1 ) input_2 = 0 ;
params . push ( input_1 && input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_BOOLOR" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( input_1 !== 1 ) input_1 = 0 ;
if ( input_2 !== 1 ) input_2 = 0 ;
params . push ( input_1 | input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_NUMEQUAL" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( input_1 === input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_NUMEQUALVERIFY" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( input_1 !== input_2 ) {
input_whose_script_failed_a_verify_opcode = index ;
return ;
}
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_NUMNOTEQUAL" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( input_1 !== input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_MIN" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( Math . min ( input_1 , input_2 ) );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_MAX" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_1 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input_2 = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
params . push ( Math . max ( input_1 , input_2 ) );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_WITHIN" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var min = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof min !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var max = params [ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof max !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( input >= min && input < max ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_CHECKLOCKTIMEVERIFY" ) {
// console.log( "before acting:" );
// console.log( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( input > locktime ) {
input_whose_script_failed_CLTV_due_to_not_enough_time_passed = index ;
return ;
}
if ( input < 0 || ( input === 0 && testnet_generator . isZeroNegative ( input ) ) ) {
input_whose_script_uses_a_negative_number_for_CLTV_or_CSV = index ;
return ;
}
if ( input >= 500000000 && locktime < 500000000 || locktime >= 500000000 && input < 500000000 ) {
input_whose_locktime_value_does_not_match_the_CLTV_value = index ;
return ;
}
if ( sequence === - 2147483647 ) {
input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out = index ;
return ;
}
// console.log( "results:" );
// console.log( params );
}
else if ( ! ifs_stack . includes ( false ) && element === "OP_CHECKSEQUENCEVERIFY" ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index ;
return ;
}
var input = params [ params . length - 1 ];
if ( typeof input !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index ;
return ;
}
if ( first_bit ) {
input_whose_script_failed_CSV = index ;
return ;
}
if ( sequence_value < input ) {
input_whose_script_failed_CSV = index ;
return ;
}
if ( input < 0 || ( input === 0 && testnet_generator . isZeroNegative ( input ) ) ) {
input_whose_script_uses_a_negative_number_for_CLTV_or_CSV = index ;
return ;
}
if ( input >= 500000000 && locktime < 500000000 || locktime >= 500000000 && input < 500000000 ) {
input_whose_locktime_value_does_not_match_the_CLTV_value = index ;
return ;
}
if ( sequence === "ffffffff" ) {
input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out = index ;
return ;
}
}
else console . log ( "the previous opcode or element was unsupported or we are in an if_branch that isn't supposed to execute" );
script_as_asm . splice ( 0 , 1 );
if ( script_as_asm . length ) await exeloop ();
}
await exeloop ();
console . log ( 'done' );
if ( params . length != 1 ) {
input_whose_script_left_multiple_elements_on_the_stack = index ;
console . log ( params );
break ;
}
if ( params [ 0 ] !== 1 ) {
input_whose_script_ended_on_false = index ;
console . log ( params );
break ;
}
}
}
}
//TODO: ensure there are never more than 1000 stack elements
//including the elements on the alt_stack
//TODO: ensure stack elements are 520 bytes or fewer
//TODO: ensure outputs are valid
//TODO: ensure outputs sum to less than or equal to inputs
//TODO: if the tx is non-standard, warn the user but give an option to anull standardness rules
if ( input_whose_script_failed_CLTV_due_to_not_enough_time_passed >= 0 ) return `that tx was invalid because input ${ input_whose_script_failed_CLTV_due_to_not_enough_time_passed } has a script containing OP_CHECKLOCKTIMEVERIFY and not enough time passed or the locktime field was set lower than is required` ;
if ( input_whose_script_uses_a_negative_number_for_CLTV_or_CSV >= 0 ) return `that tx was invalid because input ${ input_whose_script_uses_a_negative_number_for_CLTV_or_CSV } has a script containing OP_CHECKLOCKTIMEVERIFY and tried to pass a negative number to it` ;
if ( input_whose_locktime_value_does_not_match_the_CLTV_value >= 0 ) return `that tx was invalid because input ${ input_whose_locktime_value_does_not_match_the_CLTV_value } has a locktime value does not match the value passed to OP_CHECKLOCKTIMEVERIFY in the script -- i.e. one of them a blockheight and the other is a unix timestamp` ;
if ( input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out >= 0 ) return `that tx was invalid because input ${ input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out } uses OP_CHECKLOCKTIMEVERIFY in the script but its sequence number is set to 0xffffffff -- this is not allowed, try setting the sequence number to 0xfffffffd or less` ;
if ( input_whose_script_failed_CSV >= 0 ) return `that tx was invalid because input ${ input_whose_script_failed_CSV } uses OP_CHECKSEQUENCEVERIFY in the script but its value is lower than the sequence number of that input` ;
if ( input_with_if_branch_preceded_by_something_other_than_1_or_0 >= 0 ) return `that tx was invalid because input ${ input_with_if_branch_preceded_by_something_other_than_1_or_0 } had an if_branch that was preceded by something other than a 1 or a 0` ;
if ( input_with_insufficient_witness_length >= 0 ) return `that tx was invalid because input ${ input_with_insufficient_witness_length } needed at least one element on the witness stack and it wasn't there` ;
if ( input_with_invalid_sig >= 0 ) return `that tx was invalid because input ${ input_with_invalid_sig } had an invalid sig` ;
if ( input_whose_stack_is_too_small >= 0 ) return `that tx was invalid because input ${ input_whose_stack_is_too_small } ran into an opcode that required more elements on the stack than were available there` ;
if ( input_with_improperly_formatted_sig >= 0 ) return `that tx was invalid because input ${ input_with_improperly_formatted_sig } ran into an opcode that required a signature, and the signature passed to it was improperly formatted` ;
if ( input_with_improperly_formatted_element >= 0 ) return `that tx was invalid because input ${ input_with_improperly_formatted_element } had a non-opcode element on the witness stack that was formatted as something other than a hex string` ;
if ( input_whose_script_failed_a_verify_opcode >= 0 ) return `that tx was invalid because the script for input ${ input_whose_script_failed_a_verify_opcode } had an opcode with "verify" in it (one of these: OP_CHECKSIGVERIFY, OP_CHECKMULTISIGVERIFY, OP_EQUALVERIFY, OP_NUMEQUALVERIFY, or OP_VERIFY) and "verify" failed` ;
if ( input_with_op_nop >= 0 ) return `that tx was invalid because the script for input ${ input_with_op_nop } had an op_nop opcode, which is non-standard` ;
if ( input_whose_script_left_multiple_elements_on_the_stack >= 0 ) return `that tx was invalid because input ${ input_whose_script_left_multiple_elements_on_the_stack } left multiple elements on the stack, and at the end of script execution there is only supposed to be 1 element left on the stack` ;
if ( input_whose_script_ended_on_false >= 0 ) return `that tx was invalid because input ${ input_whose_script_ended_on_false } ended with a value other than 1 on the stack` ;
if ( input_with_script_containing_op_return >= 0 ) return `that tx was invalid because input ${ input_with_script_containing_op_return } ran into an op_return while executing the script` ;
if ( input_whose_script_treats_a_string_as_an_integer >= 0 ) return `that tx was invalid because input ${ input_whose_script_treats_a_string_as_an_integer } has a script that tried to treat a string as an integer` ;
if ( input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack >= 0 ) return `that tx was invalid because input ${ input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack } has a script that tried to get a nonexistent element from the stack` ;
if ( input_whose_script_failed_relative_timelock >= 0 ) return `that tx was invalid because input ${ input_whose_script_failed_relative_timelock } has a non-zero relative timelock in its sequence number, which means the tx must wait a certain amount of time after that input utxo gets mined, and not enough time has passed` ;
},
broadcast : async ( txhex , skip_validation ) => {
if ( ! skip_validation ) {
if ( ! txhex || typeof txhex === "object" ) txhex = $ ( '.tx_broadcaster' ). value ;
var tx_array = txhex . split ( "," );
2025-11-10 01:07:41 +00:00
if ( tx_array . length > 2 ) return `too many txs in this package -- only 2 are allowed` ;
2025-03-17 20:48:11 -04:00
if ( tx_array . length > 1 ) {
var allow_zero_fee = true ;
var first_tx_is_invalid = await testnet_generator . validateTx ( tx_array [ 0 ], allow_zero_fee );
2025-11-10 01:07:41 +00:00
if ( first_tx_is_invalid ) return first_tx_is_invalid ;
2025-03-17 20:48:11 -04:00
var allow_zero_fee = false ;
var second_tx_is_invalid = await testnet_generator . validateTx ( tx_array [ 1 ], allow_zero_fee , tx_array [ 0 ] );
2025-11-10 01:07:41 +00:00
if ( second_tx_is_invalid ) return second_tx_is_invalid ;
2025-03-17 20:48:11 -04:00
var parent_tx = tapscript . Tx . decode ( tx_array [ 0 ] );
var sum_of_parent_inputs = 0 ;
var all_parents_confirmed = true ;
parent_tx . vin . forEach ( input => {
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
var input_to_work_with = testnet_generator . utxo_set [ input_id ];
if ( input_to_work_with . block_created === undefined ) all_parents_confirmed = false ;
var amnt = input_to_work_with . amnt ;
sum_of_parent_inputs = sum_of_parent_inputs + amnt ;
});
2025-11-10 01:07:41 +00:00
if ( ! all_parents_confirmed ) return 'you cannot broadcast this package because it tries to spend an unconfirmed output, which is not allowed' ;
2025-03-17 20:48:11 -04:00
var sum_of_parent_outputs = 0 ;
parent_tx . vout . forEach ( output => sum_of_parent_outputs = sum_of_parent_outputs + Number ( output [ "value" ] ) );
var parent_tx_fee_in_sats = sum_of_parent_inputs - sum_of_parent_outputs ;
2025-11-11 19:31:31 +00:00
var parent_tx_size = tapscript . Tx . util . getTxSize ( tx_array [ 0 ] ). vsize ;
2025-03-17 20:48:11 -04:00
var child_tx = tapscript . Tx . decode ( tx_array [ 1 ] );
var sum_of_child_inputs = 0 ;
child_tx . vin . forEach ( input => {
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
var input_to_work_with = testnet_generator . utxo_set [ input_id ];
if ( ! input_to_work_with && !! parent_tx ) {
var txid_of_parent = tapscript . Tx . util . getTxid ( parent_tx );
if ( txid_of_parent === input [ "txid" ] ) {
input_to_work_with = parent_tx . vout [ input [ "vout" ] ];
}
}
var amnt = input_to_work_with . hasOwnProperty ( "amnt" ) ? input_to_work_with . amnt : Number ( input_to_work_with . value );
sum_of_child_inputs = sum_of_child_inputs + amnt ;
});
var sum_of_child_outputs = 0 ;
child_tx . vout . forEach ( output => sum_of_child_outputs = sum_of_child_outputs + Number ( output [ "value" ] ) );
var child_tx_fee_in_sats = sum_of_child_inputs - sum_of_child_outputs ;
2025-11-11 19:31:31 +00:00
var child_tx_size = tapscript . Tx . util . getTxSize ( tx_array [ 1 ] ). vsize ;
2025-03-17 20:48:11 -04:00
var total_fee = parent_tx_fee_in_sats + child_tx_fee_in_sats ;
var total_size = parent_tx_size + child_tx_size ;
var effective_fee_rate = total_fee / total_size ;
2025-11-10 01:07:41 +00:00
if ( effective_fee_rate < 1 ) return 'the effective fee rate was less than 1 sat per byte, try again with a child that pays a larger fee' ;
2025-03-17 20:48:11 -04:00
var skip_validation = true ;
2025-11-10 01:07:41 +00:00
var txid = await testnet_generator . broadcast ( tapscript . Tx . encode ( parent_tx ). hex , skip_validation );
2025-03-17 20:48:11 -04:00
testnet_generator . broadcast ( tapscript . Tx . encode ( child_tx ). hex , skip_validation );
2025-11-10 01:07:41 +00:00
return txid ;
2025-03-17 20:48:11 -04:00
}
var reason_why_tx_is_invalid = await testnet_generator . validateTx ( txhex );
2025-11-10 01:07:41 +00:00
if ( reason_why_tx_is_invalid ) return reason_why_tx_is_invalid ;
2025-03-17 20:48:11 -04:00
}
2025-12-22 19:40:56 +00:00
if ( privkey ) {
var event = await super_nostr . prepEvent ( privkey , "" , 40427 );
2025-12-22 19:46:07 +00:00
super_nostr . sendEvent ( event , testnet_generator . relays [ 0 ] );
2025-12-22 19:40:56 +00:00
}
2025-03-17 20:48:11 -04:00
testnet_generator . mempool . push ( txhex );
var txid = tapscript . Tx . util . getTxid ( txhex );
$ ( '.broadcast_success_label' ). classList . remove ( "hidden" );
$ ( '.broadcast_success_txid' ). innerText = txid ;
$ ( '.broadcast_success_txid' ). setAttribute ( "data-txhex" , txhex );
$ ( '.broadcast_success_txid' ). setAttribute ( "data-idx" , 1 );
$ ( '.broadcast_success_txid' ). onclick = () => {
showPage ( 'tx_explorer' );
testnet_generator . showSoloTx ( txid , txhex , 1 );
};
testnet_generator . processTxhex ( txhex );
2025-11-10 01:07:41 +00:00
return txid ;
2025-03-17 20:48:11 -04:00
},
showBlock : ( num , count ) => {
var div = document . createElement ( "div" );
div . innerHTML = `
<div class="block">
<div class="block_inner">
<div class="block_back"></div>
2025-11-12 10:34:00 +00:00
<div class="block_side1"></div>
<div class="block_side2"></div>
<div class="block_side3"></div>
<div class="block_side4"></div>
2025-03-17 20:48:11 -04:00
<div class="block_front"><div>tx count:<br> ${ count } </div></div>
<div class="block_num"> ${ num } </div>
</div>
</div>
` ;
var block = div . firstElementChild ;
if ( $ ( '.block' ) ) {
var blockwidth = 229 ;
var desired_width = Math . max ( ( blockwidth * ( $$ ( '.block' ). length + 1 ) ), window . innerWidth );
$ ( '.list_of_blocks' ). style . width = desired_width + "px" ;
$ ( '.list_container' ). scrollTo ({
top : 0 ,
left : $ ( '.list_of_blocks' ). offsetWidth ,
behavior : "smooth" ,
2025-03-15 22:25:47 -04:00
});
2025-03-17 20:48:11 -04:00
}
$ ( '.no_blocks_msg' ). classList . add ( "hidden" );
block . onclick = () => { testnet_generator . showBlockDetails ( num );}
$ ( '.list_of_blocks' ). append ( block );
},
calcBalance : () => {
var bal = 0 ;
var my_utxos = {}
Object . keys ( testnet_generator . utxo_set ). forEach ( utxo_id => {
if ( testnet_generator . utxo_set [ utxo_id ]. spent ) return ;
var utxo = testnet_generator . utxo_set [ utxo_id ];
var real_spky = utxo [ "addy" ];
var addy = tapscript . Address . toScriptPubKey ( $ ( '.your_addy' ). innerText );
var expected_spky = tapscript . Script . encode ( addy ). hex . substring ( 2 );
if ( real_spky === expected_spky ) {
bal = bal + utxo [ "amnt" ];
my_utxos [ utxo_id ] = {
utxo_id ,
txid : utxo [ "txid" ],
vout : utxo [ "vout" ],
amnt : utxo [ "amnt" ],
addy : utxo [ "addy" ],
skey : $ ( '.your_addy' ). getAttribute ( "data-privkey" ),
}
}
2024-11-21 01:24:10 -05:00
});
2025-03-17 20:48:11 -04:00
testnet_generator . utxos = my_utxos ;
$ ( '.balance' ). innerText = testnet_generator . satsToBitcoin ( bal );
},
calcMempool : () => $ ( '.txs_in_mempool' ). innerText = testnet_generator . mempool . length ,
processTxhex : ( txhex , idx = undefined , blocknum = undefined ) => {
var tx = tapscript . Tx . decode ( txhex );
var txid = tapscript . Tx . util . getTxid ( tx );
var i ; for ( i = 0 ; i < tx . vin . length ; i ++ ) {
var input = tx . vin [ i ];
var index = i ;
var input_exists = false ;
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
if ( testnet_generator . utxo_set . hasOwnProperty ( input_id ) ) {
if ( testnet_generator . utxo_set [ input_id ]. spent ) continue ;
testnet_generator . utxo_set [ input_id ]. spent = true ;
var addy = testnet_generator . utxo_set [ input_id ]. addy ;
var spky = tapscript . Script . encode ( addy ). hex . substring ( 2 );
testnet_generator . addresses [ spky ][ "sent" ]. push ( ` ${ txid } _ ${ i } ` );
}
}
var outputs_not_to_add = [];
tx . vout . forEach ( ( output , vout ) => {
if ( testnet_generator . utxo_set . hasOwnProperty ( ` ${ txid } _ ${ vout } ` ) ) {
outputs_not_to_add . push ( ` ${ txid } _ ${ vout } ` );
return ;
}
testnet_generator . utxo_set [ ` ${ txid } _ ${ vout } ` ] = {
txid ,
vout ,
amnt : Number ( output [ "value" ] ),
addy : output . scriptPubKey ,
spent : false ,
txhex ,
idx ,
block_created : blocknum ,
block_spent : undefined ,
}
2025-03-15 22:25:47 -04:00
});
2025-03-17 20:48:11 -04:00
var i ; for ( i = 0 ; i < tx . vout . length ; i ++ ) {
var output = tx . vout [ i ];
var addy = output . scriptPubKey ;
var output_id = ` ${ txid } _ ${ i } ` ;
if ( outputs_not_to_add . includes ( output_id ) ) continue ;
if ( ! ( addy in testnet_generator . addresses ) ) testnet_generator . addresses [ addy ] = {
sent : [],
received : [],
}
testnet_generator . addresses [ addy ][ "received" ]. push ( output_id );
}
testnet_generator . calcBalance ();
testnet_generator . calcMempool ();
},
makeBlock : async ( block_id , block_data ) => {
if ( ! block_id ) {
var num = 0 ;
if ( $ ( '.block_num' ) ) num = Number ( $$ ( '.block_num' )[ $$ ( '.block_num' ). length - 1 ]. innerText ) + 1 ;
var generated_txid = testnet_generator . intToHex ( num ). padStart ( 64 , "0" );
var vout = 0 ;
var amnt = 50 * 100_000_000 ;
var fees_from_mempool = 0 ;
testnet_generator . mempool . forEach ( ( tx , idx ) => {
var sum_of_inputs = 0 ;
tx = tapscript . Tx . decode ( tx );
var txid = tapscript . Tx . util . getTxid ( tx );
tx . vin . forEach ( input => {
var input_id = ` ${ input [ "txid" ] } _ ${ input [ "vout" ] } ` ;
var amnt = testnet_generator . utxo_set [ input_id ]. amnt ;
sum_of_inputs = sum_of_inputs + amnt ;
testnet_generator . utxo_set [ input_id ]. spent = true ;
testnet_generator . utxo_set [ input_id ]. block_spent = num ;
});
var sum_of_outputs = 0 ;
tx . vout . forEach ( ( output , index ) => {
sum_of_outputs = sum_of_outputs + Number ( output [ "value" ] );
testnet_generator . utxo_set [ ` ${ txid } _ ${ index } ` ] = {
txid ,
vout : index ,
amnt : Number ( output [ "value" ] ),
addy : output [ "scriptPubKey" ],
spent : false ,
txhex : tapscript . Tx . encode ( tx ). hex ,
idx : idx + 1 ,
block_created : num ,
block_spent : undefined ,
}
});
fees_from_mempool = fees_from_mempool + sum_of_inputs - sum_of_outputs ;
});
amnt = amnt + fees_from_mempool ;
var timestamp = Math . floor ( Date . now () / 1000 );
} else {
var generated_txid = block_id ;
var fees_from_mempool = block_data . fees ;
var amnt = 50 * 100_000_000 + fees_from_mempool ;
var vout = 0 ;
var num = parseInt ( generated_txid , 16 );
var timestamp = block_data . timestamp ;
}
2025-03-15 22:25:47 -04:00
var privkey = $ ( '.your_addy' ). getAttribute ( "data-privkey" );
2025-03-17 20:48:11 -04:00
var to_pubkey = testnet_generator . getPubkey ( privkey );
var to_addy = tapscript . Address . p2tr . fromPubKey ( to_pubkey , testnet_generator . addykind );
var from_pubkey = "00" . repeat ( 32 );
var from_addy = tapscript . Address . p2tr . fromPubKey ( from_pubkey , testnet_generator . addykind );
if ( ! block_id ) {
var txdata = tapscript . Tx . create ({
vin : [ testnet_generator . getVin ( generated_txid , vout , amnt , from_addy )],
vout : [ testnet_generator . getVout ( amnt , to_addy )],
});
var txid = tapscript . Tx . util . getTxid ( txdata );
var txs = [ tapscript . Tx . encode ( txdata ). hex ];
txs = [ ... txs , ... testnet_generator . mempool ];
testnet_generator . mempool = [];
} else {
var txs = block_data . txs ;
}
testnet_generator . blocks [ generated_txid ] = {
timestamp ,
fees : fees_from_mempool ,
txs ,
}
var spky = tapscript . Script . encode ( tapscript . Address . toScriptPubKey ( to_addy ) ). hex . substring ( 2 );
txs . forEach ( ( txhex , idx ) => testnet_generator . processTxhex ( txhex , idx , num ) );
testnet_generator . calcBalance ();
testnet_generator . showBlock ( num , testnet_generator . blocks [ generated_txid ]. txs . length );
testnet_generator . calcMempool ();
},
relayBlocks : async () => {
if ( Object . keys ( testnet_generator . blocks ). length <= testnet_generator . blocks_relayed . length ) {
console . log ( 'no need to relay any blocks --' , Object . keys ( testnet_generator . blocks ). length , 'out of' , testnet_generator . blocks_relayed . length , 'blocks relayed' );
await testnet_generator . waitSomeTime ( 2_000 );
testnet_generator . relayBlocks ();
return ;
}
var last_block_relayed = testnet_generator . blocks_relayed [ testnet_generator . blocks_relayed . length - 1 ];
var all_blocks = Object . keys ( testnet_generator . blocks );
var blocks_to_relay = [];
if ( ! last_block_relayed ) {
if ( all_blocks . length > 15 ) all_blocks . length = 15 ;
blocks_to_relay . push ( ... all_blocks );
} else {
Object . keys ( testnet_generator . blocks ). forEach ( block_id => {
var num_of_last_block_relayed = parseInt ( last_block_relayed , 16 );
var blocknum = parseInt ( block_id , 16 );
if ( blocknum > num_of_last_block_relayed ) blocks_to_relay . push ( block_id );
});
}
var first_block_id = blocks_to_relay [ 0 ];
var data_for_nostr = [];
blocks_to_relay . forEach ( block_id => data_for_nostr . push ( testnet_generator . blocks [ block_id ] ) );
data_for_nostr = JSON . stringify ( data_for_nostr );
console . log ( 'relaying these blocks:' );
console . log ( data_for_nostr );
var privkey = $ ( '.your_addy' ). getAttribute ( "data-privkey" );
var event = await super_nostr . prepEvent ( privkey , data_for_nostr , 60426 , [ [ "e" , first_block_id ] ] );
2025-03-15 22:25:47 -04:00
super_nostr . sendEvent ( event , testnet_generator . relays [ 0 ] );
var event2 = await super_nostr . prepEvent ( privkey , String ( Object . keys ( testnet_generator . blocks ). length ), 10426 );
super_nostr . sendEvent ( event2 , testnet_generator . relays [ 0 ] );
2025-03-17 20:48:11 -04:00
testnet_generator . blocks_relayed . push ( ... blocks_to_relay );
await testnet_generator . waitSomeTime ( 2_000 );
testnet_generator . relayBlocks ();
},
2025-03-30 14:51:05 -04:00
viewScript : async ( txid_and_vout_of_utxo_being_spent , txid_and_vout_in_which_it_is_spent , unique ) => {
2025-03-30 14:11:05 -04:00
var vout_of_utxo_being_spent = Number ( txid_and_vout_of_utxo_being_spent . split ( "_" )[ 1 ] );
var txid_in_which_it_is_spent = txid_and_vout_in_which_it_is_spent . split ( "_" )[ 0 ];
var vout_in_which_it_is_spent = Number ( txid_and_vout_in_which_it_is_spent . split ( "_" )[ 1 ] );
var spubkey_of_utxo_being_spent = tapscript . Tx . decode ( testnet_generator . utxo_set [ txid_and_vout_of_utxo_being_spent ]. txhex ). vout [ vout_of_utxo_being_spent ]. scriptPubKey ;
var witness_of_utxo_being_spent = tapscript . Tx . decode ( testnet_generator . utxo_set [ ` ${ txid_in_which_it_is_spent } _0` ]. txhex ). vin [ vout_in_which_it_is_spent ]. witness ;
if ( ! witness_of_utxo_being_spent . length ) {
var script_arguments = [];
var script_program = tapscript . Script . decode ( spubkey_of_utxo_being_spent );
}
if ( witness_of_utxo_being_spent . length === 1 ) {
var script_arguments = [ witness_of_utxo_being_spent [ 0 ] ];
var script_program = [
tapscript . Script . decode ( spubkey_of_utxo_being_spent )[ 1 ],
"OP_CHECKSIG" ,
];
}
if ( witness_of_utxo_being_spent . length > 1 ) {
var script_arguments = JSON . parse ( JSON . stringify ( witness_of_utxo_being_spent ) );
script_arguments . length = script_arguments . length - 2 ;
var script_program = tapscript . Script . decode ( witness_of_utxo_being_spent [ witness_of_utxo_being_spent . length - 2 ] );
}
2025-03-30 14:51:05 -04:00
var script_div = $$ ( `.unique_ ${ unique } .input .btc_script` )[ vout_in_which_it_is_spent ];
var button = $$ ( `.unique_ ${ unique } .input .view_script` )[ vout_in_which_it_is_spent ];
2025-03-30 14:11:05 -04:00
if ( script_div . classList . contains ( "hidden" ) ) {
script_div . classList . remove ( "hidden" );
button . innerText = "Hide bitcoin script" ;
script_div . getElementsByClassName ( "script_arguments" )[ 0 ]. innerHTML = `<p class="script_element"> ${ script_arguments . join ( `</p><p class="script_element">` ) } </p>` ;
script_div . getElementsByClassName ( "script_program" )[ 0 ]. innerHTML = `<p class="script_element"> ${ script_program . join ( `</p><p class="script_element">` ) } </p>` ;
} else {
script_div . classList . add ( "hidden" );
button . innerText = "Show bitcoin script" ;
}
},
2024-11-21 01:24:10 -05:00
}
2025-11-15 20:13:02 +00:00
if ( params . hasOwnProperty ( "relays" ) ) testnet_generator . relays = JSON . parse ( testnet_generator . hexToText ( params . relays ) );
var sendMoney = async ( destino , amnt , feerate ) => {
try {
if ( ! destino ) destino = prompt ( `enter the address you want to send money to` );
if ( ! destino ) return ;
if ( ! amnt ) amnt = Number ( prompt ( `enter an amount of sats you want to send there` ) );
if ( ! amnt ) return ;
if ( ! feerate ) feerate = Number ( prompt ( `enter the feerate you want to use` ) );
if ( ! feerate ) return ;
var txfee = 300 * feerate ;
var input_ids = getUtxos ( amnt + txfee );
if ( input_ids === "unable" ) return `you don't have enough money, consider generating some blocks` ;
var inputs = [];
input_ids . forEach ( utxo_id => inputs . push ( testnet_generator . utxos [ utxo_id ] ) );
var vin = [];
inputs . forEach ( input => vin . push ( testnet_generator . getVin ( input [ "txid" ], input [ "vout" ], input [ "amnt" ], input [ "addy" ] ) ) );
var txdata = tapscript . Tx . create ({
vin ,
vout : [ testnet_generator . getVout ( amnt , destino )],
});
var sum_of_inputs = 0 ;
inputs . forEach ( input => sum_of_inputs = sum_of_inputs + input [ "amnt" ] );
var change = sum_of_inputs - amnt - txfee ;
var change_addy = $ ( '.your_addy' ). innerText ;
if ( change > 330 ) txdata . vout . push ( testnet_generator . getVout ( change , change_addy ) );
var i ; for ( i = 0 ; i < txdata . vin . length ; i ++ ) {
var privkey = inputs [ i ][ "skey" ];
var sig = tapscript . Signer . taproot . sign ( privkey , txdata , i ). hex ;
txdata . vin [ i ]. witness = [ sig ];
}
var txhex = tapscript . Tx . encode ( txdata ). hex ;
var error = await testnet_generator . broadcast ( txhex );
if ( error . length !== 64 || ! testnet_generator . isValidHex ( error ) ) return error ;
//highlight the mempool count label
$ ( '.txs_in_mempool_label' ). style . backgroundColor = "pink" ;
setTimeout ( () => {
$ ( '.txs_in_mempool_label' ). style . transition = "all 1s ease-in" ;
$ ( '.txs_in_mempool_label' ). style . backgroundColor = "white" ;
}, 10 );
testnet_generator . calcMempool ();
return txhex ;
} catch ( e ) {
return 'unknown error' ;
2024-11-21 01:24:10 -05:00
}
}
var getUtxos = amount => {
var utxo_set = [];
Object . keys ( testnet_generator . utxos ). forEach ( utxo_id => utxo_set . push ( testnet_generator . utxos [ utxo_id ] ) );
var set_to_return = [];
var sum_so_far = 0 ;
var new_utxo_set = JSON . parse ( JSON . stringify ( utxo_set ) );
var unable = false ;
var loop = () => {
if ( ! new_utxo_set . length ) {
unable = true ;
return ;
}
var rand = Math . floor ( Math . random () * new_utxo_set . length );
set_to_return . push ( new_utxo_set [ rand ][ "utxo_id" ] );
sum_so_far = sum_so_far + new_utxo_set [ rand ][ "amnt" ];
new_utxo_set . splice ( rand , 1 );
if ( sum_so_far < amount + 330 ) loop ();
}
loop ();
if ( unable ) return `unable` ;
return set_to_return ;
}
2025-03-17 20:48:11 -04:00
var showAddress = privkey => {
if ( privkey ) var [ addy , privkey ] = testnet_generator . getAddy ( privkey );
else var [ addy , privkey ] = testnet_generator . getAddy ();
$ ( '.your_addy' ). innerText = addy ;
$ ( '.your_addy' ). setAttribute ( "data-privkey" , privkey );
}
2025-11-15 20:13:02 +00:00
$ ( '.send_btn' ). onclick = async () => {
var txhex_or_error = await sendMoney ();
if ( ! testnet_generator . isValidHex ( txhex_or_error ) ) alert ( txhex_or_error );
}
2025-03-15 22:25:47 -04:00
var privkey = params . hasOwnProperty ( "privkey" ) ? params . privkey : undefined ;
showAddress ( privkey );
2025-03-17 20:48:11 -04:00
$ ( '.generate_block' ). onclick = () => { testnet_generator . makeBlock ();}
2025-03-15 22:25:47 -04:00
if ( ! privkey ) $ ( '.generate_block' ). disabled = false ;
if ( params . hasOwnProperty ( "pubkey" ) ) {
testnet_generator . processExistingBlocks ();
$ ( '.generate_block' ). remove ();
2025-03-17 20:48:11 -04:00
$ ( '.options' ). remove ();
2025-03-15 22:25:47 -04:00
$ ( '.wallet' ). style . display = "none" ;
$ ( '.txs_in_mempool_label' ). style . display = "none" ;
}
2025-12-22 20:09:07 +00:00
$ ( '.tx_broadcaster' ). value = "" ;
2025-11-10 01:07:41 +00:00
$ ( '.broadcast_btn' ). onclick = async () => {
var txhex = $ ( '.tx_broadcaster' ). value ;
2025-12-22 20:09:07 +00:00
if ( params . pubkey ) {
var txid_or_error = await chain_client . commander ( [ params . pubkey , testnet_generator . relays [ 0 ] ], "broadcast" , txhex );
if ( txid_or_error . length !== 64 || ! testnet_generator . isValidHex ( txid_or_error ) ) return alert ( txid_or_error );
} else {
var error = await testnet_generator . broadcast ( txhex );
if ( error . length !== 64 || ! testnet_generator . isValidHex ( error ) ) return alert ( error );
}
2025-11-10 01:07:41 +00:00
}
2025-03-17 20:48:11 -04:00
if ( $ ( '.auto_generate' ) ) $ ( '.auto_generate' ). onchange = () => {
autogen_value = $ ( '.auto_generate' ). value ;
if ( autogen_value . includes ( "---" ) ) $ ( '.auto_go' ). disabled = true ;
else $ ( '.auto_go' ). removeAttribute ( "disabled" );
2024-11-21 01:24:10 -05:00
}
2025-03-17 20:48:11 -04:00
var autogen_num = null ;
var autogo = false ;
if ( $ ( '.auto_go' ) ) $ ( '.auto_go' ). onclick = () => {
if ( $ ( '.auto_go' ). innerText === "Go" ) {
if ( $ ( '.auto_go' ). disabled ) return ;
autogen_value = $ ( '.auto_generate' ). value ;
var num = 500 ;
if ( autogen_value === "1 second" ) num = 1_000 ;
if ( autogen_value === "3 seconds" ) num = 3_000 ;
if ( autogen_value === "5 seconds" ) num = 5_000 ;
if ( autogen_value === "10 seconds" ) num = 10_000 ;
if ( autogen_value === "30 seconds" ) num = 30_000 ;
if ( autogen_value === "1 minute" ) num = 60_000 ;
if ( autogen_value === "2 minutes" ) num = 120_000 ;
if ( autogen_value === "5 minutes" ) num = 300_000 ;
if ( autogen_value === "10 minutes" ) num = 600_000 ;
autogen_num = num ;
autogo = true ;
autoLoop ();
$ ( '.auto_go' ). innerText = "Stop" ;
} else {
autogo = false ;
$ ( '.auto_go' ). innerText = "Go" ;
2025-03-15 22:25:47 -04:00
}
}
2025-03-17 20:48:11 -04:00
var autoLoop = async () => {
testnet_generator . makeBlock ();
var keep_going = autogo ;
await testnet_generator . waitSomeTime ( autogen_num );
if ( ! autogo ) keep_going = false ;
if ( keep_going ) autoLoop ();
}
var showPage = page => {
$ ( '.block_explorer' ). classList . add ( "hidden" );
$ ( '.tx_explorer' ). classList . add ( "hidden" );
$ ( '.addy_explorer' ). classList . add ( "hidden" );
$ ( '.stuff_thats_not_an_explorer' ). classList . add ( "hidden" );
$ ( `. ${ page } ` ). classList . remove ( "hidden" );
}
( async ()=>{
if ( privkey ) {
var private_url = window . location . protocol + "//" + window . location . hostname + window . location . pathname + `#privkey= ${ privkey } ` ;
var sharable_url = window . location . protocol + "//" + window . location . hostname + window . location . pathname + `#pubkey= ${ testnet_generator . getPubkey ( privkey ) } ` ;
if ( params . hasOwnProperty ( "first_time" ) ) {
window . onload = () => {
var html = `
<p>Save this and do not share it -- it is how you can return to this testnet to keep making blocks:</p>
<p><a href=" ${ private_url } " target="_blank"> ${ private_url } </a></p>
<p>Share this:</p>
<p><a href=" ${ sharable_url } " target="_blank"> ${ sharable_url } </a></p>
` ;
showModal ( html );
}
} else {
window . onload = () => {
$ ( '.make_sharable_testnet' ). innerText = 'Show sharable info' ;
$ ( '.make_sharable_testnet' ). onclick = () => {
var html = `
<p>Save this and do not share it -- it is how you can return to this testnet to keep making blocks:</p>
<p><a href=" ${ private_url } " target="_blank"> ${ private_url } </a></p>
<p>Share this:</p>
<p><a href=" ${ sharable_url } " target="_blank"> ${ sharable_url } </a></p>
` ;
showModal ( html );
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
}
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
var pubkey = testnet_generator . getPubkey ( privkey );
var events = await super_nostr . getEvents ( testnet_generator . relays [ 0 ], null , [ pubkey ], [ 10426 ], null , null , 1 );
if ( ! events . length ) {
var event = await super_nostr . prepEvent ( privkey , String ( Object . keys ( testnet_generator . blocks ). length ), 10426 );
super_nostr . sendEvent ( event , testnet_generator . relays [ 0 ] );
}
await testnet_generator . waitSomeTime ( 1_000 );
testnet_generator . processExistingBlocks ();
2025-11-10 01:07:41 +00:00
var listenFunction = async socket => {
var subId = super_nostr . bytesToHex ( crypto . getRandomValues ( new Uint8Array ( 8 ) ) );
var filter = {}
filter . kinds = [ 4 ];
2025-11-12 11:43:20 +00:00
filter [ "#p" ] = [ pubkey ];
2025-11-10 01:07:41 +00:00
filter . since = Math . floor ( Date . now () / 1000 );
var subscription = [ "REQ" , subId , filter ];
socket . send ( JSON . stringify ( subscription ) );
}
var handleFunction = async message => {
var [ type , subId , event ] = JSON . parse ( message . data );
if ( ! event || event === true ) return ;
2025-11-10 02:27:55 +00:00
var relay = testnet_generator . relays [ 0 ];
var recipient = event . pubkey ;
var content = await super_nostr . alt_decrypt ( privkey , recipient , event . content );
2025-11-15 01:38:27 +00:00
console . log ( content );
2025-11-10 01:07:41 +00:00
var json = JSON . parse ( content );
if ( json . msg_type === "broadcast" ) {
var txhex = json . msg_value ;
var txid_or_error = await testnet_generator . broadcast ( txhex );
var message = JSON . stringify ({
msg_type : "broadcast_reply" ,
msg_value : txid_or_error ,
});
2025-11-10 02:27:55 +00:00
var emsg = await super_nostr . alt_encrypt ( privkey , recipient , message );
var event = await super_nostr . prepEvent ( privkey , emsg , 4 , [ [ "p" , recipient ] ] );
super_nostr . sendEvent ( event , relay );
}
if ( json . msg_type === "utxos" ) {
var address = json . msg_value ;
var spub = tapscript . Script . encode ( tapscript . Address . toScriptPubKey ( address ) ). hex . substring ( 2 );
var utxos = [];
if ( testnet_generator . addresses . hasOwnProperty ( spub ) ) {
2025-12-22 14:56:10 +00:00
testnet_generator . addresses [ spub ][ "received" ]. forEach ( utxo => {
var utxo_info = testnet_generator . utxo_set [ utxo ];
if ( utxo_info . spent ) return ;
var txid = utxo_info . txid ;
var vout = utxo_info . vout ;
var value = utxo_info . amnt ;
if ( isNaN ( utxo_info . block_created ) ) {
var status = {
confirmed : false ,
2025-11-10 02:27:55 +00:00
}
2025-12-22 14:56:10 +00:00
} else {
var block_hash = testnet_generator . intToHex ( utxo_info . block_created ). padStart ( 64 , "0" );
var status = {
confirmed : true ,
block_height : utxo_info . block_created ,
block_hash ,
block_time : testnet_generator . blocks [ block_hash ]. timestamp ,
}
}
utxos . push ({ txid , vout , status , value });
2025-11-10 02:27:55 +00:00
});
}
var message = JSON . stringify ({
msg_type : "utxos_reply" ,
msg_value : utxos ,
});
2025-11-11 17:36:40 +00:00
var emsg = await super_nostr . alt_encrypt ( privkey , recipient , message );
var event = await super_nostr . prepEvent ( privkey , emsg , 4 , [ [ "p" , recipient ] ] );
super_nostr . sendEvent ( event , relay );
}
2025-11-15 20:13:02 +00:00
if ( json . msg_type === "rawtx" ) {
2025-11-11 17:36:40 +00:00
var txid = json . msg_value ;
var txhex = 'tx not found' ;
if ( testnet_generator . utxo_set . hasOwnProperty ( ` ${ txid } _0` ) ) {
var { block_created , idx } = testnet_generator . utxo_set [ ` ${ txid } _0` ];
if ( isNaN ( block_created ) ) {
testnet_generator . mempool . every ( tx => {
if ( tapscript . Tx . util . getTxid ( tx ) !== txid ) return true ;
txhex = tx ;
});
} else {
var block_hash = testnet_generator . intToHex ( block_created ). padStart ( 64 , "0" );
txhex = testnet_generator . blocks [ block_hash ]. txs [ idx ];
}
}
var message = JSON . stringify ({
2025-11-15 20:13:02 +00:00
msg_type : "rawtx_reply" ,
2025-11-11 17:36:40 +00:00
msg_value : txhex ,
});
2025-11-15 20:13:02 +00:00
var emsg = await super_nostr . alt_encrypt ( privkey , recipient , message );
var event = await super_nostr . prepEvent ( privkey , emsg , 4 , [ [ "p" , recipient ] ] );
super_nostr . sendEvent ( event , relay );
}
if ( json . msg_type === "genblock" ) {
var txid_or_error = await testnet_generator . makeBlock ();
var blockheight = Object . keys ( testnet_generator . blocks ). length - 1 ;
var blockhash = testnet_generator . intToHex ( blockheight ). padStart ( 64 , "0" );
var message = JSON . stringify ({
msg_type : "genblock_reply" ,
msg_value : blockhash ,
});
2025-11-15 20:46:04 +00:00
var emsg = await super_nostr . alt_encrypt ( privkey , recipient , message );
var event = await super_nostr . prepEvent ( privkey , emsg , 4 , [ [ "p" , recipient ] ] );
super_nostr . sendEvent ( event , relay );
}
if ( json . msg_type === "send" ) {
var { destino , amnt , feerate } = json . msg_value ;
var txhex_or_error = await sendMoney ( destino , amnt , feerate );
var message = JSON . stringify ({
msg_type : "send_reply" ,
msg_value : txhex_or_error ,
});
2025-11-10 01:07:41 +00:00
var emsg = await super_nostr . alt_encrypt ( privkey , recipient , message );
var event = await super_nostr . prepEvent ( privkey , emsg , 4 , [ [ "p" , recipient ] ] );
2025-12-21 19:14:21 +00:00
super_nostr . sendEvent ( event , relay );
}
if ( json . msg_type === "blockheight" ) {
var blockheight = Object . keys ( testnet_generator . blocks ). length ;
var message = JSON . stringify ({
msg_type : "blockheight_reply" ,
msg_value : blockheight ,
});
var emsg = await super_nostr . alt_encrypt ( privkey , recipient , message );
var event = await super_nostr . prepEvent ( privkey , emsg , 4 , [ [ "p" , recipient ] ] );
2025-11-10 01:07:41 +00:00
super_nostr . sendEvent ( event , relay );
}
}
var connection_id = await super_nostr . newPermanentConnection ( testnet_generator . relays [ 0 ], listenFunction , handleFunction );
2025-12-20 16:15:36 +00:00
var event = await super_nostr . prepEvent ( privkey , "" , 54091 , [ [ "e" , params . say_when_connected ] ] );
super_nostr . sendEvent ( event , testnet_generator . relays [ 0 ] );
2025-11-10 01:07:41 +00:00
console . log ( 'connected!' , connection_id );
console . log ( 'users can talk to me at this pubkey:' );
console . log ( pubkey );
console . log ( 'and this relay:' );
console . log ( testnet_generator . relays [ 0 ] );
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
if ( ! $ ( '.auto_generate' ) ) {
window . scrollTo ( 0 , 0 );
return ;
}
$ ( '.auto_generate' ). value = "--- Auto generate ---" ;
$ ( '.auto_go' ). disabled = true ;
$ ( '.tx_broadcaster' ). value = "" ;
window . scrollTo ( 0 , 0 );
})();
</ script >
< script >
2025-11-10 01:07:41 +00:00
var runScript = async () => {
2024-11-21 01:24:10 -05:00
var keypairs = [ testnet_generator . getAddy (), testnet_generator . getAddy (), testnet_generator . getAddy ()];
var script = [
"OP_3" ,
2024-11-21 15:18:48 -05:00
"OP_CHECKSEQUENCEVERIFY" ,
2024-11-21 01:24:10 -05:00
"OP_DROP" ,
];
keypairs . forEach ( ( keypair , idx ) => {
if ( ! idx ) script . push ( testnet_generator . getPubkey ( keypair [ 1 ] ), "OP_CHECKSIG" );
else script . push ( testnet_generator . getPubkey ( keypair [ 1 ] ), "OP_CHECKSIGADD" );
});
script . push ( "OP_1" , "OP_GREATERTHANOREQUAL" );
console . log ( "script:" );
console . log ( script );
var tree = [ tapscript . Tap . encodeScript ( script )];
var tapleaf = tree [ 0 ];
var backup_pubkey = "ab" . repeat ( 32 );
var [ tpubkey , cblock ] = tapscript . Tap . getPubKey ( backup_pubkey , { target : tapleaf });
var addy = tapscript . Address . p2tr . fromPubKey ( tpubkey , 'testnet' );
console . log ( 'send money to this address:' );
console . log ( addy );
2025-11-10 01:07:41 +00:00
var txhex = await sendMoney ();
2024-11-21 01:24:10 -05:00
if ( ! testnet_generator . isValidHex ( txhex ) ) return ;
var txid = tapscript . Tx . util . getTxid ( txhex );
var vout = 0 ;
var amnt = Number ( prompt ( `enter the amount you sent` ) );
// var txid = prompt( `enter the txid` );
// var vout = Number( prompt( `and the vout` ) );
// var amnt = Number( prompt( `and the amnt` ) );
var txdata = tapscript . Tx . create ({
vin : [ testnet_generator . getVin ( txid , vout , amnt , addy )],
vout : [ testnet_generator . getVout ( amnt - 500 , $ ( '.your_addy' ). innerText )],
});
2024-11-21 15:18:48 -05:00
txdata . vin [ 0 ]. sequence = 3 ;
2024-11-21 01:24:10 -05:00
txdata . vin . forEach ( ( input , index ) => {
// var sig_0 = tapscript.Signer.taproot.sign( keypairs[ 0 ][ 1 ], txdata, 0, { extension: tapleaf }).hex;
// sig_0 = ( BigInt( '0x' + sig_0 ) - BigInt( '0x01' ) ).toString( 16 );
// if ( sig_0.length % 2 ) sig_0 = "0" + sig_0;
var sig_0 = "" ;
var sig_1 = tapscript . Signer . taproot . sign ( keypairs [ 1 ][ 1 ], txdata , 0 , { extension : tapleaf }). hex ;
var sighash = tapscript . Signer . taproot . hash ( txdata , 0 , { extension : tapleaf }). hex ;
// var sig_2 = tapscript.Signer.taproot.sign( keypairs[ 2 ][ 1 ], txdata, 0, { extension: tapleaf }).hex;
var sig_2 = "" ;
// console.log( 44, "pubkey:", tapscript.Address.toScriptPubKey( keypairs[ 1 ][ 0 ] )[ 1 ], "sighash:", sighash, "sig:", sig_1 );
txdata . vin [ index ]. witness = [ sig_2 , sig_1 , sig_0 , script , cblock ];
});
console . log ( tapscript . Tx . encode ( txdata ). hex );
}
2025-03-15 22:25:47 -04:00
var useP2A = privkey_1 => {
if ( ! privkey_1 ) {
privkey_1 = testnet_generator . getPrivkey ();
var pubkey_1 = testnet_generator . getPubkey ( privkey_1 );
var address_1 = tapscript . Address . fromScriptPubKey ( [ 1 , pubkey_1 ], testnet_generator . addykind );
console . log ( `send money to this address:` );
console . log ( address_1 );
console . log ( `then run this command:` );
console . log ( `useP2A( " ${ privkey_1 } " );` );
alert ( `follow the instructions in your browser console` );
return ;
}
var pubkey_1 = testnet_generator . getPubkey ( privkey_1 );
var privkey_2 = testnet_generator . getPrivkey ();
var pubkey_2 = testnet_generator . getPubkey ( privkey_2 );
var address_1 = tapscript . Address . fromScriptPubKey ( [ 1 , pubkey_1 ], testnet_generator . addykind );
var address_2 = tapscript . Address . fromScriptPubKey ( [ 1 , pubkey_2 ], testnet_generator . addykind );
var txid = prompt ( `enter the txid by which you sent money to the address` );
if ( ! txid ) return ;
var vout = Number ( prompt ( `and the vout` ) );
var amnt = Number ( prompt ( `and the amount` ) );
var destino = $ ( '.your_addy' ). innerText ;
var using_p2a = true ;
var tx_a = tapscript . Tx . create ({
version : 3 ,
vin : [{
txid ,
vout ,
prevout : {
value : amnt ,
scriptPubKey : tapscript . Address . toScriptPubKey ( address_1 ),
}
}],
vout : [{
value : amnt ,
scriptPubKey : tapscript . Address . toScriptPubKey ( address_2 ),
}],
});
if ( using_p2a ) {
tx_a . vout . unshift ({
value : 240 ,
scriptPubKey : "51024e73" ,
});
tx_a . vout [ 1 ]. value = amnt - 240 ;
}
var sig_a = tapscript . Signer . taproot . sign ( privkey_1 , tx_a , 0 ). hex ;
tx_a . vin [ 0 ]. witness = [ sig_a ];
//create tx_b, which pays double fees to send from address_2 to anywhere
//ensure tx_b is a v3 transaction
var txid_a = tapscript . Tx . util . getTxid ( tx_a );
var tx_b = tapscript . Tx . create ({
version : 3 ,
vin : [{
txid : txid_a ,
vout : 0 ,
prevout : {
value : amnt ,
scriptPubKey : tapscript . Address . toScriptPubKey ( address_2 ),
}
}],
vout : [{
value : amnt - 1000 ,
scriptPubKey : tapscript . Address . toScriptPubKey ( destino ),
}],
});
if ( using_p2a ) {
tx_b . vin . unshift ({
txid : txid_a ,
vout : 0 ,
prevout : {
value : 240 ,
scriptPubKey : "51024e73" ,
}
});
tx_b . vin [ 1 ]. vout = 1 ;
tx_b . vin [ 1 ]. prevout . value = amnt - 240 ;
}
var txid_b = tapscript . Tx . util . getTxid ( tx_b );
var input_to_sign = 0 ;
if ( using_p2a ) input_to_sign = 1 ;
var sig_b = tapscript . Signer . taproot . sign ( privkey_2 , tx_b , input_to_sign ). hex ;
tx_b . vin [ input_to_sign ]. witness = [ sig_b ];
var txs = [ tapscript . Tx . encode ( tx_a ). hex , tapscript . Tx . encode ( tx_b ). hex ];
console . log ( txs . join ( "," ) );
}
2025-03-17 20:48:11 -04:00
if ( $ ( '.make_sharable_testnet' ) ) $ ( '.make_sharable_testnet' ). onclick = () => {
if ( window . location . href . includes ( "#" ) ) return ;
var conf = confirm ( 'are you sure you want to make a sharable testnet? The one you have now will get deleted and you will have to restart' );
if ( ! conf ) return ;
var privkey = $ ( '.your_addy' ). getAttribute ( "data-privkey" );
window . location . href += `#privkey= ${ privkey } #first_time=true` ;
window . location . reload ();
}
2025-03-15 22:25:47 -04:00
</ script >
2025-03-17 20:48:11 -04:00
< div class = "black-bg hidden" onclick = "modalVanish();" ></ div >
< div class = "modal hidden" ></ div >
2024-11-21 01:24:10 -05:00
</ body >
</ html >