}), ..Default::default() } .encipher(), ), ..Default::default() }); server.mine_blocks(1); let id = RuneId { height: 2, index: 1, }; assert_eq!( server.index.runes().unwrap(), [( id, RuneEntry { etching: txid, rune, supply: u128::max_value(), symbol: Some('$'), timestamp: 2, ..Default::default() } )] ); assert_eq!( server.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); server.assert_response_regex( format!("/rune/{rune}"), StatusCode::OK, format!( r".*<title>Rune AAAAAAAAAAAAA</title>.* <h1>AAAAAAAAAAAAA</h1> <iframe .* src=/preview/{txid}i0></iframe> <dl> <dt>id</dt> <dd>2/1</dd> <dt>number</dt> <dd>0</dd> <dt>timestamp</dt> <dd><time>1970-01-01 00:00:02 UTC</time></dd> <dt>etching block height</dt> <dd><a href=/block/2>2</a></dd> #45,161,862text Timestamp, WalletTxInfo, }, jsonrpc_core::{IoHandler, Value}, jsonrpc_http_server::{CloseHandle, ServerBuilder}, serde::{Deserialize, Serialize}, server::Server, state::State, std::{ collections::{BTreeMap, BTreeSet, HashMap}, sync::{Arc, Mutex, MutexGuard}, thread, time::Duration, }, }; mod api; mod server; mod state; pub fn builder() -> Builder { Builder { fail_lock_unspent: false, network: Network::Bitcoin, version: 240000, } } pub struct Builder { fail_lock_unspent: bool, network: Network, version: usize, } impl Builder { pub fn fail_lock_unspent(self, fail_lock_unspent: bool) -> Self { Self { fail_lock_unspent, ..self } } pub fn network(self, network: Network) -> Self { Self { network, ..self } } pub fn version(self, version: usize) -> Self { Self { version, ..self } } pub fn build(self) -> Handle { let state = Arc::new(Mutex::new(State::new( self.network, self.version, self.fail_lock_unspent,#45,161,110text server.assert_response_regex( format!("/preview/{inscription_id}"), StatusCode::OK, format!(r".*<audio .*>\s*<source src=/content/{inscription_id}>.*"), ); } #[test] fn font_preview() { let server = TestServer::new_with_regtest(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0, inscription("font/ttf", "hello").to_witness())], ..Default::default() }); let inscription_id = InscriptionId { txid, index: 0 }; server.mine_blocks(1); server.assert_response_regex( format!("/preview/{inscription_id}"), StatusCode::OK, format!(r".*src: url\(/content/{inscription_id}\).*"), ); } #[test] fn pdf_preview() { let server = TestServer::new_with_regtest(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[( 1, 0, 0, inscription("application/pdf", "hello").to_witness(),#45,161,080textuse super::*; #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewAudioHtml { pub(crate) inscription_id: InscriptionId, } #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewCodeHtml { pub(crate) inscription_id: InscriptionId, pub(crate) language: media::Language, } #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewFontHtml { pub(crate) inscription_id: InscriptionId, } #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewImageHtml { pub(crate) inscription_id: InscriptionId, } #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewMarkdownHtml { pub(crate) inscription_id: InscriptionId, } #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewModelHtml { pub(crate) inscription_id: InscriptionId, } #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewPdfHtml { pub(crate) inscription_id: InscriptionId, } #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewTextHtml { pub(crate) inscription_id: InscriptionId, } #[derive(boilerplate::Boilerplate)] #45,161,068text Timestamp, WalletTxInfo, }, jsonrpc_core::{IoHandler, Value}, jsonrpc_http_server::{CloseHandle, ServerBuilder}, serde::{Deserialize, Serialize}, server::Server, state::State, std::{ collections::{BTreeMap, BTreeSet, HashMap}, sync::{Arc, Mutex, MutexGuard}, thread, time::Duration, }, }; mod api; mod server; mod state; pub fn builder() -> Builder { Builder { fail_lock_unspent: false, network: Network::Bitcoin, version: 240000, } } pub struct Builder { fail_lock_unspent: bool, network: Network, version: usize, } impl Builder { pub fn fail_lock_unspent(self, fail_lock_unspent: bool) -> Self { Self { fail_lock_unspent, ..self } } pub fn network(self, network: Network) -> Self { Self { network, ..self } } pub fn version(self, version: usize) -> Self { Self { version, ..self } } pub fn build(self) -> Handle { let state = Arc::new(Mutex::new(State::new( self.network, self.version, self.fail_lock_unspent,#45,159,679text #[test] fn unknown_preview() { let server = TestServer::new_with_regtest(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0, inscription("text/foo", "hello").to_witness())], ..Default::default() }); server.mine_blocks(1); server.assert_response_csp( format!("/preview/{}", InscriptionId { txid, index: 0 }), StatusCode::OK, "default-src 'self'", fs::read_to_string("templates/preview-unknown.html").unwrap(), ); } #[test] fn video_preview() { let server = TestServer::new_with_regtest(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0, inscription("video/webm", "hello").to_witness())], ..Default::default() }); let inscription_id = InscriptionId { txid, index: 0 }; server.mine_blocks(1); server.assert_response_regex( format!("/preview/{inscription_id}"), StatusCode::OK,#45,022,930textsmall sub-features, and implement them one at a time. Figuring out how to break down a larger PR into smaller PRs where each can be merged is an art form well-worth practicing. The hard part is that each PR must itself be an improvement. I strive to follow this advice myself, and am always better off when I do. Small changes are fast to write, review, and merge, which is much more fun than laboring over a single giant PR that takes forever to write, review, and merge. Small changes don't take much time, so if you need to stop working on a small change, you won't have wasted much time as compared to a larger change that represents many hours of work. Getting a PR in quickly improves the project a little bit immediately, instead of having to wait a long time for larger improvement. Small changes are less likely to accumulate merge conflict. As the Athenians said: *The fast commit what they will, the slow merge what they must.* Get help -------- If you're stuck for more than 15 minutes, ask for help, like a Rust Discord, #45,021,734text <dd><a class=monospace href=/output/1{64}:1>1{64}:1</a></dd> <dt>offset</dt> <dd>0</dd> <dt>ethereum teleburn address</dt> <dd>0xa1DfBd1C519B9323FD7Fd8e498Ac16c2E502F059</dd> </dl> " .unindent() ); } #[test] fn with_rune() { assert_regex_match!( InscriptionHtml { genesis_fee: 1, inscription: inscription("text/plain;charset=utf-8", "HELLOWORLD"), inscription_id: inscription_id(1), inscription_number: 1, satpoint: satpoint(1, 0), rune: Some(Rune(0)), ..Default::default() }, " <h1>Inscription 1</h1> .* <dl> .* <dt>rune</dt> <dd><a href=/rune/A>A</a></dd> </dl> " .unindent() ); } #[test] fn with_content_encoding() { assert_regex_match!( InscriptionHtml { genesis_fee: 1, inscription: Inscription { content_encoding: Some("br".into()), ..inscription("text/plain;charset=utf-8", "HELLOWORLD") #44,992,588text You should add a label for the recipient address, a label like `alice address for inscription #123` would be ideal. Once you have checked the transaction is a safe transaction using the checks above, and you are confident to send it you can click `Create Transaction`. ![](images/sending_03.png) Here again you can double check that your transaction looks safe, and once you are confident you can click `Finalize Transaction for Signing`. ![](images/sending_04.png) Here you can triple check everything before hitting `Sign`. ![](images/sending_05.png) And then actually you get very very last chance to check everything before hitting `Broadcast Transaction`. Once you broadcast the transaction it is sent to the bitcoin network, and starts being propagated into the mempool. ![](images/sending_06.png) If you want to track the status of your transaction you can copy the `Transaction Id (Txid)` and paste that into [mempool.space](https://mempool.space) Once the transaction has confirmed you can check the inscription page on [ordinals.com](https://ordinals.com) to validate that it has moved to the new output location and address. #44,991,095text #[test] fn first() { assert_regex_match!( SatHtml { sat: Sat(0), satpoint: None, blocktime: Blocktime::confirmed(0), inscriptions: Vec::new(), }, " <h1>Sat 0</h1> <dl> <dt>decimal</dt><dd>0.0</dd> <dt>degree</dt><dd>0°0′0″0‴</dd> <dt>percentile</dt><dd>0%</dd> <dt>name</dt><dd>nvtdijuwxlp</dd> <dt>cycle</dt><dd>0</dd> <dt>epoch</dt><dd>0</dd> <dt>period</dt><dd>0</dd> <dt>block</dt><dd><a href=/block/0>0</a></dd> <dt>offset</dt><dd>0</dd> <dt>rarity</dt><dd><span class=mythic>mythic</span></dd> <dt>timestamp</dt><dd><time>1970-01-01 00:00:00 UTC</time></dd> </dl> .* prev <a class=next href=/sat/1>next</a> .* " .unindent() ); } #[test] fn last() { assert_regex_match!( SatHtml { sat: Sat(2099999997689999), satpoint: None, blocktime: Blocktime::confirmed(0), #44,990,517text self.outputs[0].1 ); } } } Ok(self) } fn add_value(mut self) -> Result<Self> { let estimated_fee = self.estimate_fee(); let min_value = match self.target { Target::Postage => self.outputs.last().unwrap().0.script_pubkey().dust_value(), Target::Value(value) | Target::ExactPostage(value) => value, }; let total = min_value .checked_add(estimated_fee) .ok_or(Error::ValueOverflow)?; if let Some(mut deficit) = total.checked_sub(self.outputs.last().unwrap().1) { while deficit > Amount::ZERO { let additional_fee = self.fee_rate.fee(Self::ADDITIONAL_INPUT_VBYTES); let needed = deficit .checked_add(additional_fee) .ok_or(Error::ValueOverflow)?; let (utxo, value) = self.select_cardinal_utxo(needed, false)?; let benefit = value .checked_sub(additional_fee) .ok_or(Error::NotEnoughCardinalUtxos)?; self.inputs.push(utxo); self.outputs.last_mut().unwrap().1 += value; #44,986,666text .outputs .last_mut() .expect("No output to deduct fee from"); assert!( total_output_amount.checked_sub(fee).unwrap() > Amount::from_sat(sat_offset), "invariant: deducting fee does not consume sat", ); assert!( *last_output_amount >= fee, "invariant: last output can pay fee: {} {}", *last_output_amount, fee, ); *last_output_amount -= fee; self } /// Estimate the size in virtual bytes of the transaction under construction. /// We initialize wallets with taproot descriptors only, so we know that all /// inputs are taproot key path spends, which allows us to know that witnesses /// will all consist of single Schnorr signatures. fn estimate_vbytes(&self) -> usize { Self::estimate_vbytes_with( self.inputs.len(), self .outputs .iter() .map(|(address, _amount)| address) .cloned() .collect(), ) } fn estimate_vbytes_with(inputs: usize, outputs: Vec<Address>) -> usize { #44,985,412text <link rel=stylesheet href=/static/index.css> <link rel=stylesheet href=/static/modern-normalize.css> <script src=/static/index.js defer></script> </head> <body> <header> <nav> <a href=/ title=home>Ordinals<sup>alpha</sup></a> .* <a href=/clock title=clock>.*</a> <a href=/rare.txt title=rare>.*</a> .* <form action=/search method=get> <input type=text .*> <input class=icon type=image .*> </form> </nav> </header> <main> <h1>Foo</h1> </main> </body> </html> " ); } #[test] fn page_mainnet() { assert_regex_match!( Foo.page(Arc::new(PageConfig { chain: Chain::Mainnet, csp_origin: None, domain: None, index_sats: true, })), r".*<nav>\s*<a href=/ title=home>Ordinals<sup>alpha</sup></a>.*" ); } #[test] fn page_no_sat_index() { assert_regex_match!( Foo.page(Arc::new(PageConfig { chain: Chain::Mainnet, csp_origin: None, domain: None, #44,985,407text