├── .gitattributes ├── examples ├── alert.html ├── h264.mp4 ├── top-navigation-is-blocked.html ├── external-resources-are-blocked.html └── navigation-to-external-pages-is-blocked.html ├── src ├── bin │ └── main.rs ├── templates │ ├── rare.rs │ ├── runes.rs │ ├── preview.rs │ ├── range.rs │ ├── iframe.rs │ ├── input.rs │ ├── rune.rs │ ├── children.rs │ ├── inscriptions.rs │ └── home.rs ├── runes │ ├── edict.rs │ ├── etching.rs │ ├── error.rs │ ├── rune_id.rs │ ├── varint.rs │ └── pile.rs ├── page_config.rs ├── subcommand │ ├── index │ │ ├── update.rs │ │ └── export.rs │ ├── epochs.rs │ ├── wallet │ │ ├── receive.rs │ │ ├── restore.rs │ │ ├── outputs.rs │ │ ├── create.rs │ │ ├── balance.rs │ │ ├── transactions.rs │ │ ├── cardinals.rs │ │ └── inscriptions.rs │ ├── parse.rs │ ├── teleburn.rs │ ├── index.rs │ ├── supply.rs │ ├── subsidy.rs │ ├── decode.rs │ ├── server │ │ ├── accept_json.rs │ │ └── error.rs │ ├── traits.rs │ ├── info.rs │ ├── find.rs │ └── preview.rs ├── wallet.rs ├── deserialize_from_str.rs ├── arguments.rs ├── blocktime.rs ├── tally.rs ├── decimal.rs ├── config.rs ├── index │ └── rtx.rs ├── degree.rs ├── fee_rate.rs ├── outgoing.rs ├── representation.rs ├── chain.rs ├── subcommand.rs ├── teleburn.rs ├── sat_point.rs └── height.rs ├── image.png ├── .prettierignore ├── deploy ├── bitcoin.conf ├── checkout ├── save-ord-dev-state ├── bitcoind.service ├── ord.service └── setup ├── static ├── favicon.png ├── preview-markdown.css ├── preview-pdf.css ├── preview-audio.css ├── preview-code.css ├── preview-text.js ├── preview-video.css ├── preview-markdown.js ├── preview-text.css ├── favicon.svg ├── preview-pdf.js ├── index.js └── preview-code.js ├── templates ├── rare.txt ├── preview-unknown.html ├── runes.html ├── range.html ├── preview-audio.html ├── preview-code.html ├── preview-video.html ├── preview-markdown.html ├── preview-pdf.html ├── preview-text.html ├── inscriptions.html ├── preview-model.html ├── home.html ├── children.html ├── input.html ├── preview-image.html ├── inscriptions-block.html ├── sat.html ├── transaction.html ├── block.html ├── page.html ├── rune.html ├── output.html └── inscription.html ├── docs ├── language-picker.css ├── rtl.css ├── src │ ├── guides │ │ ├── collecting │ │ │ └── images │ │ │ │ ├── sending_01.png │ │ │ │ ├── sending_02.png │ │ │ │ ├── sending_03.png │ │ │ │ ├── sending_04.png │ │ │ │ ├── sending_05.png │ │ │ │ ├── sending_06.png │ │ │ │ ├── wallet_setup_01.png │ │ │ │ ├── wallet_setup_02.png │ │ │ │ ├── wallet_setup_03.png │ │ │ │ ├── wallet_setup_04.png │ │ │ │ ├── wallet_setup_05.png │ │ │ │ ├── wallet_setup_06.png │ │ │ │ ├── wallet_setup_07.png │ │ │ │ ├── wallet_setup_08.png │ │ │ │ ├── troubleshooting_01.png │ │ │ │ ├── troubleshooting_02.png │ │ │ │ ├── validating_viewing_01.png │ │ │ │ └── validating_viewing_02.png │ │ ├── batch-inscribing.md │ │ ├── collecting.md │ │ ├── reindexing.md │ │ ├── moderation.md │ │ ├── teleburning.md │ │ └── testing.md │ ├── guides.md │ ├── bounty │ │ ├── 0.md │ │ ├── 1.md │ │ ├── 2.md │ │ └── 3.md │ ├── bounties.md │ ├── donate.md │ ├── SUMMARY.md │ ├── inscriptions │ │ ├── recursion.md │ │ ├── pointer.md │ │ ├── metadata.md │ │ └── provenance.md │ ├── digital-artifacts.md │ └── introduction.md └── book.toml ├── rustfmt.toml ├── .editorconfig ├── .gitignore ├── tests ├── version.rs ├── wallet.rs ├── supply.rs ├── wallet │ ├── receive.rs │ ├── cardinals.rs │ ├── balance.rs │ ├── outputs.rs │ ├── restore.rs │ ├── transactions.rs │ ├── sats.rs │ └── create.rs ├── expected.rs ├── parse.rs ├── traits.rs ├── subsidy.rs ├── epochs.rs ├── list.rs ├── core.rs ├── decode.rs ├── find.rs ├── info.rs └── lib.rs ├── CONTRIBUTING ├── benchmark ├── run └── checkout ├── logo-bw.svg ├── logo-wb.svg ├── bin ├── forbid ├── benchmark ├── install-bitcoin-core-linux ├── flamegraph ├── package └── graph ├── contrib ├── initialize-opendime └── raw │ └── justfile ├── fuzz ├── fuzz_targets │ ├── varint_encode.rs │ ├── varint_decode.rs │ ├── runestone_decipher.rs │ └── transaction_builder.rs └── Cargo.toml ├── ord.yaml ├── shell.nix ├── Vagrantfile ├── test-bitcoincore-rpc └── Cargo.toml ├── quickstart └── macos ├── batch.yaml ├── .github └── workflows │ └── release.yaml ├── Cargo.toml └── install.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | -------------------------------------------------------------------------------- /examples/alert.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | ord::main(); 3 | } 4 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/image.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # ignore everything 2 | /* 3 | # except docs 4 | !/docs 5 | -------------------------------------------------------------------------------- /deploy/bitcoin.conf: -------------------------------------------------------------------------------- 1 | datadir=/var/lib/bitcoind 2 | maxmempool=1024 3 | txindex=1 4 | -------------------------------------------------------------------------------- /examples/h264.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/examples/h264.mp4 -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/static/favicon.png -------------------------------------------------------------------------------- /examples/top-navigation-is-blocked.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /templates/rare.txt: -------------------------------------------------------------------------------- 1 | sat satpoint 2 | %% for (sat, satpoint) in &self.0 { 3 | {{sat}} {{satpoint}} 4 | %% } 5 | -------------------------------------------------------------------------------- /examples/external-resources-are-blocked.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/navigation-to-external-pages-is-blocked.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/templates/rare.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Boilerplate)] 4 | pub(crate) struct RareTxt(pub(crate) Vec<(Sat, SatPoint)>); 5 | -------------------------------------------------------------------------------- /docs/language-picker.css: -------------------------------------------------------------------------------- 1 | #language-list { 2 | left: auto; 3 | right: 10px; 4 | } 5 | 6 | #language-list a { 7 | color: inherit; 8 | } 9 | -------------------------------------------------------------------------------- /docs/rtl.css: -------------------------------------------------------------------------------- 1 | /* rtl.css */ 2 | 3 | body { 4 | direction: rtl; 5 | } 6 | 7 | .rtl-header { 8 | direction: rtl; 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/sending_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/sending_02.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/sending_03.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/sending_04.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/sending_05.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/sending_06.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_02.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_03.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_04.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_05.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_06.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_07.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_08.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/troubleshooting_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/troubleshooting_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/troubleshooting_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/troubleshooting_02.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | max_width = 100 3 | newline_style = "Unix" 4 | tab_spaces = 2 5 | use_field_init_shorthand = true 6 | use_try_shorthand = true 7 | -------------------------------------------------------------------------------- /templates/preview-unknown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/validating_viewing_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/validating_viewing_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/validating_viewing_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alpha-labs-btc/ord/HEAD/docs/src/guides/collecting/images/validating_viewing_02.png -------------------------------------------------------------------------------- /templates/runes.html: -------------------------------------------------------------------------------- 1 |

Runes

2 | 7 | -------------------------------------------------------------------------------- /static/preview-markdown.css: -------------------------------------------------------------------------------- 1 | html { 2 | color: #98a3ad; 3 | background-color: #131516; 4 | max-width: 900px; 5 | margin: 0 auto; 6 | font-family: system-ui, sans-serif; 7 | } 8 | -------------------------------------------------------------------------------- /src/runes/edict.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)] 4 | pub struct Edict { 5 | pub id: u128, 6 | pub amount: u128, 7 | pub output: u128, 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.redb 2 | /.idea/ 3 | /.vagrant 4 | /docs/build 5 | /fuzz/artifacts 6 | /fuzz/corpus 7 | /fuzz/coverage 8 | /fuzz/target 9 | /ord.log 10 | /target 11 | /test-times.txt 12 | /tmp 13 | -------------------------------------------------------------------------------- /tests/version.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn version_flag_prints_version() { 5 | CommandBuilder::new("--version") 6 | .stdout_regex("ord .*\n") 7 | .run_and_extract_stdout(); 8 | } 9 | -------------------------------------------------------------------------------- /src/page_config.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone)] 4 | pub(crate) struct PageConfig { 5 | pub(crate) chain: Chain, 6 | pub(crate) domain: Option, 7 | pub(crate) index_sats: bool, 8 | } 9 | -------------------------------------------------------------------------------- /docs/src/guides.md: -------------------------------------------------------------------------------- 1 | Ordinal Theory Guides 2 | ===================== 3 | 4 | See the table of contents for a list of guides, including a guide to the 5 | explorer, a guide for sat hunters, and a guide to inscriptions. 6 | -------------------------------------------------------------------------------- /static/preview-pdf.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | height: 100%; 7 | margin: 0; 8 | } 9 | 10 | canvas { 11 | height: 100%; 12 | width: 100%; 13 | object-fit: contain; 14 | } 15 | -------------------------------------------------------------------------------- /static/preview-audio.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | align-items: center; 7 | display: flex; 8 | height: 100%; 9 | margin: 0; 10 | } 11 | 12 | audio { 13 | width: 100%; 14 | } 15 | -------------------------------------------------------------------------------- /src/subcommand/index/update.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) fn run(options: Options) -> SubcommandResult { 4 | let index = Index::open(&options)?; 5 | 6 | index.update()?; 7 | 8 | Ok(Box::new(Empty {})) 9 | } 10 | -------------------------------------------------------------------------------- /tests/wallet.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | mod balance; 4 | mod cardinals; 5 | mod create; 6 | mod inscribe; 7 | mod inscriptions; 8 | mod outputs; 9 | mod receive; 10 | mod restore; 11 | mod sats; 12 | mod send; 13 | mod transactions; 14 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Unless you explicitly state otherwise, any contribution intentionally 5 | submitted for inclusion in the work by you shall be licensed as in 6 | LICENSE, without any additional terms or conditions. 7 | -------------------------------------------------------------------------------- /templates/range.html: -------------------------------------------------------------------------------- 1 |

Sat range {{self.start}}–{{self.end}}

2 |
3 |
value
{{self.end.n() - self.start.n()}}
4 |
first
{{self.start.n()}}
5 |
6 | -------------------------------------------------------------------------------- /benchmark/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | systemctl stop ord-dev 6 | 7 | rm -rf /var/lib/ord-dev 8 | 9 | journalctl --unit ord-dev --rotate 10 | 11 | journalctl --unit ord-dev --vacuum-time 1s 12 | 13 | ./bin/update-dev-server 14 | -------------------------------------------------------------------------------- /static/preview-code.css: -------------------------------------------------------------------------------- 1 | @import url('https://cdn.jsdelivr.net/npm/highlight.js@11.8.0/styles/atom-one-dark.min.css'); 2 | 3 | html { 4 | color: #98a3ad; 5 | background-color: #131516; 6 | max-width: 900px; 7 | font-family: system-ui, sans-serif; 8 | } 9 | -------------------------------------------------------------------------------- /logo-bw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /logo-wb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /static/preview-text.js: -------------------------------------------------------------------------------- 1 | let pre = document.querySelector('body > pre'); 2 | let { width, height } = pre.getBoundingClientRect(); 3 | let columns = width / 16; 4 | let rows = height / 16; 5 | pre.style.fontSize = `min(${95/columns}vw, ${95/rows}vh)`; 6 | pre.style.opacity = 1; 7 | -------------------------------------------------------------------------------- /static/preview-video.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: #131516; 3 | height: 100%; 4 | } 5 | 6 | body { 7 | align-items: center; 8 | display: flex; 9 | height: 100%; 10 | margin: 0; 11 | } 12 | 13 | video { 14 | height: 100%; 15 | width: 100%; 16 | } 17 | -------------------------------------------------------------------------------- /bin/forbid: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | which rg > /dev/null 6 | 7 | ! rg \ 8 | --glob '!bin/forbid' \ 9 | --glob '!docs/src/bounty/frequency.tsv' \ 10 | --glob '!docs/po/*' \ 11 | --ignore-case \ 12 | 'dbg!|fixme|todo|xxx' \ 13 | . 14 | -------------------------------------------------------------------------------- /src/templates/runes.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Boilerplate)] 4 | pub(crate) struct RunesHtml { 5 | pub(crate) entries: Vec<(RuneId, RuneEntry)>, 6 | } 7 | 8 | impl PageContent for RunesHtml { 9 | fn title(&self) -> String { 10 | "Runes".to_string() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/wallet.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) struct Wallet { 4 | _private: (), 5 | } 6 | 7 | impl Wallet { 8 | pub(crate) fn load(options: &Options) -> Result { 9 | options.bitcoin_rpc_client_for_wallet_command(false)?; 10 | 11 | Ok(Self { _private: () }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/runes/etching.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)] 4 | pub struct Etching { 5 | pub(crate) divisibility: u8, 6 | pub(crate) limit: Option, 7 | pub(crate) rune: Rune, 8 | pub(crate) symbol: Option, 9 | pub(crate) term: Option, 10 | } 11 | -------------------------------------------------------------------------------- /static/preview-markdown.js: -------------------------------------------------------------------------------- 1 | import { marked } from 'https://cdn.jsdelivr.net/npm/marked@9/+esm' 2 | 3 | const inscription = document.documentElement.dataset.inscription; 4 | const response = await fetch(`/content/${inscription}`); 5 | const markdown = await response.text(); 6 | document.body.innerHTML = marked.parse(markdown); 7 | -------------------------------------------------------------------------------- /templates/preview-audio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/preview-code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /templates/preview-video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /bin/benchmark: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | rm -rf tmp/benchmark 6 | mkdir -p tmp/benchmark 7 | 8 | INDEX_SNAPSHOT=$1 9 | HEIGHT_LIMIT=$2 10 | 11 | cp $INDEX_SNAPSHOT tmp/benchmark/index.redb 12 | 13 | cargo build --release 14 | 15 | time ./target/release/ord --data-dir tmp/benchmark --height-limit $HEIGHT_LIMIT index 16 | -------------------------------------------------------------------------------- /templates/preview-markdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /benchmark/checkout: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | REV=$1 6 | 7 | if [[ ! -d ord ]]; then 8 | git clone https://github.com/ordinals/ord.git 9 | fi 10 | 11 | cd ord 12 | 13 | git fetch --all --prune 14 | git checkout master 15 | git reset --hard origin/master 16 | git checkout `git rev-parse origin/$REV` 17 | ./benchmark/run 18 | -------------------------------------------------------------------------------- /templates/preview-pdf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /contrib/initialize-opendime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | until [ -f /Volumes/OPENDIME/README.txt ]; do 6 | sleep 1 7 | done 8 | 9 | dd if=/dev/urandom of=/Volumes/OPENDIME/entro.bin bs=1024 count=256 10 | 11 | until [ -f /Volumes/OPENDIME/address.txt ]; do 12 | sleep 1 13 | done 14 | 15 | cat /Volumes/OPENDIME/address.txt | tr -d '\r\n' 16 | -------------------------------------------------------------------------------- /tests/supply.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::supply::Output}; 2 | 3 | #[test] 4 | fn genesis() { 5 | assert_eq!( 6 | CommandBuilder::new("supply").run_and_deserialize_output::(), 7 | Output { 8 | supply: 2099999997690000, 9 | first: 0, 10 | last: 2099999997689999, 11 | last_mined_in_block: 6929999 12 | } 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/varint_encode.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use {libfuzzer_sys::fuzz_target, ord::runes::varint}; 4 | 5 | fuzz_target!(|input: u128| { 6 | let mut encoded = Vec::new(); 7 | varint::encode_to_vec(input, &mut encoded); 8 | let (decoded, length) = varint::decode(&encoded).unwrap(); 9 | assert_eq!(length, encoded.len()); 10 | assert_eq!(decoded, input); 11 | }); 12 | -------------------------------------------------------------------------------- /ord.yaml: -------------------------------------------------------------------------------- 1 | # Example Config 2 | 3 | # use username `bar` and password `foo` for bitcoind RPC calls 4 | bitcoin_rpc_user: bar 5 | bitcoin_rpc_pass: foo 6 | 7 | # prevent `ord server` from serving the content of the inscriptions below 8 | hidden: 9 | - 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 10 | - 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0 11 | -------------------------------------------------------------------------------- /bin/install-bitcoin-core-linux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | version=24.0.1 6 | 7 | wget \ 8 | -O bitcoin.tar.gz \ 9 | https://bitcoincore.org/bin/bitcoin-core-$version/bitcoin-$version-x86_64-linux-gnu.tar.gz 10 | 11 | tar \ 12 | -xzvf bitcoin.tar.gz \ 13 | -C /usr/local/bin \ 14 | --strip-components 2 \ 15 | bitcoin-$version/bin/{bitcoin-cli,bitcoind} 16 | -------------------------------------------------------------------------------- /src/subcommand/epochs.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 4 | pub struct Output { 5 | pub starting_sats: Vec, 6 | } 7 | 8 | pub(crate) fn run() -> SubcommandResult { 9 | let mut starting_sats = Vec::new(); 10 | for sat in Epoch::STARTING_SATS { 11 | starting_sats.push(sat); 12 | } 13 | 14 | Ok(Box::new(Output { starting_sats })) 15 | } 16 | -------------------------------------------------------------------------------- /deploy/checkout: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | BRANCH=$1 6 | CHAIN=$2 7 | DOMAIN=$3 8 | 9 | if [[ ! -d ord ]]; then 10 | git clone https://github.com/ordinals/ord.git 11 | fi 12 | 13 | cd ord 14 | 15 | git fetch origin 16 | git checkout -B $BRANCH 17 | git reset --hard origin/$BRANCH 18 | 19 | COMMIT=$(git rev-parse --short HEAD) 20 | 21 | ./deploy/setup $CHAIN $DOMAIN $BRANCH $COMMIT 22 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz); 3 | nixpkgs = import { overlays = [ moz_overlay ]; }; 4 | in 5 | with nixpkgs; 6 | stdenv.mkDerivation { 7 | name = "ord-shell"; 8 | buildInputs = [ 9 | just 10 | nixpkgs.latest.rustChannels.stable.rust 11 | openssl 12 | pkg-config 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /tests/wallet/receive.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::wallet::receive::Output}; 2 | 3 | #[test] 4 | fn receive() { 5 | let rpc_server = test_bitcoincore_rpc::spawn(); 6 | create_wallet(&rpc_server); 7 | 8 | let output = CommandBuilder::new("wallet receive") 9 | .rpc_server(&rpc_server) 10 | .run_and_deserialize_output::(); 11 | 12 | assert!(output.address.is_valid_for_network(Network::Bitcoin)); 13 | } 14 | -------------------------------------------------------------------------------- /src/subcommand/wallet/receive.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Deserialize, Serialize)] 4 | pub struct Output { 5 | pub address: Address, 6 | } 7 | 8 | pub(crate) fn run(options: Options) -> SubcommandResult { 9 | let address = options 10 | .bitcoin_rpc_client_for_wallet_command(false)? 11 | .get_new_address(None, Some(bitcoincore_rpc::json::AddressType::Bech32m))?; 12 | 13 | Ok(Box::new(Output { address })) 14 | } 15 | -------------------------------------------------------------------------------- /templates/preview-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
{{self.text}}
11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/subcommand/parse.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Parse { 5 | #[arg(help = "Parse .")] 6 | object: Object, 7 | } 8 | 9 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 10 | pub struct Output { 11 | pub object: Object, 12 | } 13 | 14 | impl Parse { 15 | pub(crate) fn run(self) -> SubcommandResult { 16 | Ok(Box::new(Output { 17 | object: self.object, 18 | })) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/inscriptions.html: -------------------------------------------------------------------------------- 1 |

Inscriptions

2 |
3 | %% for id in &self.inscriptions { 4 | {{Iframe::thumbnail(*id)}} 5 | %% } 6 |
7 |
8 | %% if let Some(prev) = self.prev { 9 | 10 | %% } else { 11 | prev 12 | %% } 13 | %% if let Some(next) = self.next { 14 | 15 | %% } else { 16 | next 17 | %% } 18 |
19 | -------------------------------------------------------------------------------- /static/preview-text.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: #131516; 3 | color: white; 4 | font-size: 16px; 5 | height: 100%; 6 | line-height: 1; 7 | } 8 | 9 | body { 10 | display: grid; 11 | grid-template: 1fr / 1fr; 12 | height: 100%; 13 | margin: 0; 14 | place-items: center; 15 | } 16 | 17 | pre { 18 | margin: 0; 19 | } 20 | 21 | body > * { 22 | grid-column: 1 / 1; 23 | grid-row: 1 / 1; 24 | } 25 | 26 | body > pre { 27 | opacity: 0; 28 | } 29 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/varint_decode.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use {libfuzzer_sys::fuzz_target, ord::runes::varint}; 4 | 5 | fuzz_target!(|input: &[u8]| { 6 | let mut i = 0; 7 | 8 | while i < input.len() { 9 | let Ok((decoded, length)) = varint::decode(&input[i..]) else { 10 | break; 11 | }; 12 | let mut encoded = Vec::new(); 13 | varint::encode_to_vec(decoded, &mut encoded); 14 | assert_eq!(encoded, &input[i..i + length]); 15 | i += length; 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /src/deserialize_from_str.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) struct DeserializeFromStr(pub(crate) T); 4 | 5 | impl<'de, T: FromStr> Deserialize<'de> for DeserializeFromStr 6 | where 7 | T::Err: Display, 8 | { 9 | fn deserialize(deserializer: D) -> Result 10 | where 11 | D: Deserializer<'de>, 12 | { 13 | Ok(Self( 14 | FromStr::from_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)?, 15 | )) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /static/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Ordinal Theory Handbook" 3 | language = "en" 4 | src = "src" 5 | 6 | [build] 7 | build-dir = "build" 8 | create-missing = false 9 | extra-watch-dirs = ["po"] 10 | 11 | [output.html] 12 | cname = "docs.ordinals.com" 13 | default-theme = "coal" 14 | git-repository-url = "https://github.com/ordinals/ord" 15 | preferred-dark-theme = "coal" 16 | additional-css = ["language-picker.css"] 17 | 18 | [output.linkcheck] 19 | 20 | [preprocessor.gettext] 21 | after = ["links"] 22 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/bullseye64" 3 | 4 | config.vm.provider "virtualbox" do |v| 5 | v.memory = 1024 * 4 6 | end 7 | 8 | config.vm.network "private_network", ip: "192.168.56.4" 9 | 10 | config.vm.provision "shell" do |s| 11 | s.inline = "" 12 | Dir.glob("#{Dir.home}/.ssh/*.pub").each do |path| 13 | key = File.read(path).strip 14 | s.inline << "echo '#{key}' >> /root/.ssh/authorized_keys\n" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/subcommand/teleburn.rs: -------------------------------------------------------------------------------- 1 | use {super::*, crate::teleburn}; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Teleburn { 5 | #[arg(help = "Generate teleburn addresses for inscription .")] 6 | destination: InscriptionId, 7 | } 8 | 9 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 10 | pub struct Output { 11 | pub ethereum: teleburn::Ethereum, 12 | } 13 | 14 | impl Teleburn { 15 | pub(crate) fn run(self) -> SubcommandResult { 16 | Ok(Box::new(Output { 17 | ethereum: self.destination.into(), 18 | })) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/preview-model.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/subcommand/wallet/restore.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Restore { 5 | #[arg(help = "Restore wallet from ")] 6 | mnemonic: Mnemonic, 7 | #[arg( 8 | long, 9 | default_value = "", 10 | help = "Use when deriving wallet" 11 | )] 12 | pub(crate) passphrase: String, 13 | } 14 | 15 | impl Restore { 16 | pub(crate) fn run(self, options: Options) -> SubcommandResult { 17 | initialize_wallet(&options, self.mnemonic.to_seed(self.passphrase))?; 18 | Ok(Box::new(Empty {})) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/subcommand/index.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | mod export; 4 | mod update; 5 | 6 | #[derive(Debug, Parser)] 7 | pub(crate) enum IndexSubcommand { 8 | #[command(about = "Write inscription numbers and ids to a tab-separated file")] 9 | Export(export::Export), 10 | #[command(about = "Update the index", alias = "run")] 11 | Update, 12 | } 13 | 14 | impl IndexSubcommand { 15 | pub(crate) fn run(self, options: Options) -> SubcommandResult { 16 | match self { 17 | Self::Export(export) => export.run(options), 18 | Self::Update => update::run(options), 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/subcommand/supply.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 4 | pub struct Output { 5 | pub supply: u64, 6 | pub first: u64, 7 | pub last: u64, 8 | pub last_mined_in_block: u64, 9 | } 10 | 11 | pub(crate) fn run() -> SubcommandResult { 12 | let mut last = 0; 13 | 14 | loop { 15 | if Height(last + 1).subsidy() == 0 { 16 | break; 17 | } 18 | last += 1; 19 | } 20 | 21 | Ok(Box::new(Output { 22 | supply: Sat::SUPPLY, 23 | first: 0, 24 | last: Sat::SUPPLY - 1, 25 | last_mined_in_block: last, 26 | })) 27 | } 28 | -------------------------------------------------------------------------------- /src/subcommand/wallet/outputs.rs: -------------------------------------------------------------------------------- 1 | use {super::*, crate::wallet::Wallet}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct Output { 5 | pub output: OutPoint, 6 | pub amount: u64, 7 | } 8 | 9 | pub(crate) fn run(options: Options) -> SubcommandResult { 10 | let index = Index::open(&options)?; 11 | index.update()?; 12 | 13 | let mut outputs = Vec::new(); 14 | for (output, amount) in index.get_unspent_outputs(Wallet::load(&options)?)? { 15 | outputs.push(Output { 16 | output, 17 | amount: amount.to_sat(), 18 | }); 19 | } 20 | 21 | Ok(Box::new(outputs)) 22 | } 23 | -------------------------------------------------------------------------------- /src/runes/error.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum Error { 5 | Script(script::Error), 6 | Varint, 7 | } 8 | 9 | impl From for Error { 10 | fn from(error: script::Error) -> Self { 11 | Self::Script(error) 12 | } 13 | } 14 | 15 | impl Display for Error { 16 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 17 | match self { 18 | Self::Script(err) => write!(f, "failed to parse script: {err}"), 19 | Self::Varint => write!(f, "varint over maximum value"), 20 | } 21 | } 22 | } 23 | 24 | impl std::error::Error for Error {} 25 | -------------------------------------------------------------------------------- /tests/expected.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug)] 4 | pub(crate) enum Expected { 5 | String(String), 6 | Regex(Regex), 7 | } 8 | 9 | impl Expected { 10 | pub(crate) fn regex(pattern: &str) -> Self { 11 | Self::Regex(Regex::new(&format!("^(?s){pattern}$")).unwrap()) 12 | } 13 | 14 | pub(crate) fn assert_match(&self, output: &str) { 15 | match self { 16 | Self::String(string) => pretty_assert_eq!(output, string), 17 | Self::Regex(regex) => assert!( 18 | regex.is_match(output), 19 | "regex:\n{regex}\ndid not match output:\n{output}", 20 | ), 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /deploy/save-ord-dev-state: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # Stops, copies index and exports state of running instance 5 | systemctl stop ord 6 | 7 | cd /var/lib/ord 8 | 9 | mkdir -p /var/lib/ord/$REVISION 10 | 11 | # Still have to manually set --index-sats or --heigh-limit 12 | # --height-limit 100000 \ 13 | /usr/local/bin/ord --bitcoin-data-dir /var/lib/bitcoind \ 14 | --data-dir /var/lib/ord \ 15 | --index-sats \ 16 | index export 17 | 18 | mv inscription_number_to_id.tsv ./$REVISION/inscription_number_to_id-$BRANCH-$COMMIT.tsv 19 | 20 | mv index.redb ./$REVISION/index-$BRANCH-$COMMIT.redb 21 | 22 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | %% for (i, hash) in self.blocks.iter().enumerate() { 2 | %% if let Some(inscription_ids) = &self.featured_blocks.get(hash) { 3 |
4 |

Block {{ self.last - i as u64 }}

5 |
6 | %% for id in *inscription_ids { 7 | {{ Iframe::thumbnail(*id) }} 8 | %% } 9 |
10 |
11 | %% } else { 12 | %% if i == self.featured_blocks.len() { 13 |
    14 | %% } 15 |
  1. {{ hash }}
  2. 16 | %% } 17 | %% } 18 |
19 | -------------------------------------------------------------------------------- /src/subcommand/index/export.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Export { 5 | #[arg( 6 | long, 7 | default_value = "inscription_number_to_id.tsv", 8 | help = " file to write to" 9 | )] 10 | tsv: String, 11 | #[arg(long, help = "Whether to include addresses in export")] 12 | include_addresses: bool, 13 | } 14 | 15 | impl Export { 16 | pub(crate) fn run(self, options: Options) -> SubcommandResult { 17 | let index = Index::open(&options)?; 18 | 19 | index.update()?; 20 | index.export(&self.tsv, self.include_addresses)?; 21 | 22 | Ok(Box::new(Empty {})) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contrib/raw/justfile: -------------------------------------------------------------------------------- 1 | create INPUT_TXID INPUT_VOUT OUTPUT_DESTINATION OUTPUT_AMOUNT: 2 | #!/usr/bin/env bash 3 | 4 | set -euxo pipefail 5 | 6 | bitcoin-cli createrawtransaction \ 7 | '[ 8 | { 9 | "txid": "{{INPUT_TXID}}", 10 | "vout": {{INPUT_VOUT}} 11 | } 12 | ]' \ 13 | '[ 14 | { 15 | "{{OUTPUT_DESTINATION}}": {{OUTPUT_AMOUNT}} 16 | } 17 | ]' \ 18 | > raw.hex 19 | 20 | sign WALLET_NAME: 21 | bitcoin-cli -rpcwallet={{WALLET_NAME}} signrawtransactionwithwallet `cat raw.hex` > signed.json 22 | 23 | send: 24 | bitcoin-cli sendrawtransaction `cat signed.json | jq '.hex' --raw-output` 25 | -------------------------------------------------------------------------------- /templates/children.html: -------------------------------------------------------------------------------- 1 |

Inscription {{ self.parent_number }} Children

2 | %% if self.children.is_empty() { 3 |

No children

4 | %% } else { 5 |
6 | %% for id in &self.children { 7 | {{ Iframe::thumbnail(*id) }} 8 | %% } 9 |
10 |
11 | %% if let Some(prev_page) = &self.prev_page { 12 | 13 | %% } else { 14 | prev 15 | %% } 16 | %% if let Some(next_page) = &self.next_page { 17 | 18 | %% } else { 19 | next 20 | %% } 21 |
22 | %% } 23 | -------------------------------------------------------------------------------- /tests/wallet/cardinals.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | ord::subcommand::wallet::{cardinals::CardinalUtxo, outputs::Output}, 4 | }; 5 | 6 | #[test] 7 | fn cardinals() { 8 | let rpc_server = test_bitcoincore_rpc::spawn(); 9 | create_wallet(&rpc_server); 10 | 11 | inscribe(&rpc_server); 12 | 13 | let all_outputs = CommandBuilder::new("wallet outputs") 14 | .rpc_server(&rpc_server) 15 | .run_and_deserialize_output::>(); 16 | 17 | let cardinal_outputs = CommandBuilder::new("wallet cardinals") 18 | .rpc_server(&rpc_server) 19 | .run_and_deserialize_output::>(); 20 | 21 | assert_eq!(all_outputs.len() - cardinal_outputs.len(), 1); 22 | } 23 | -------------------------------------------------------------------------------- /test-bitcoincore-rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-bitcoincore-rpc" 3 | description = "Test Bitcoin Core RPC server" 4 | version = "0.0.1" 5 | edition = "2021" 6 | license = "CC0-1.0" 7 | homepage = "https://github.com/ordinals/ord" 8 | repository = "https://github.com/ordinals/ord" 9 | 10 | [dependencies] 11 | bitcoin = { version = "0.30.0", features = ["serde", "rand"] } 12 | hex = "0.4.3" 13 | jsonrpc-core = "18.0.0" 14 | jsonrpc-derive = "18.0.0" 15 | jsonrpc-http-server = "18.0.0" 16 | ord-bitcoincore-rpc = "0.17.0" 17 | reqwest = { version = "0.11.10", features = ["blocking"] } 18 | serde = { version = "1.0.137", features = ["derive"] } 19 | serde_json = { version = "1.0.81" } 20 | -------------------------------------------------------------------------------- /bin/flamegraph: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | mkdir $1 6 | cd $1 7 | 8 | sudo \ 9 | CARGO_PROFILE_RELEASE_DEBUG=true \ 10 | RUST_LOG=info \ 11 | cargo flamegraph \ 12 | --deterministic \ 13 | --bin ord \ 14 | -- \ 15 | --chain signet \ 16 | --data-dir . \ 17 | --height-limit 0 \ 18 | index 19 | 20 | rm -f flamegraph.svg 21 | 22 | /usr/bin/time -o time sudo \ 23 | CARGO_PROFILE_RELEASE_DEBUG=true \ 24 | RUST_LOG=info \ 25 | cargo flamegraph \ 26 | --deterministic \ 27 | --bin ord \ 28 | -- \ 29 | --chain signet \ 30 | --data-dir . \ 31 | --height-limit 5000 \ 32 | index 33 | 34 | sudo chown -n $UID flamegraph.svg 35 | sudo chown -n $UID index.redb 36 | -------------------------------------------------------------------------------- /docs/src/guides/batch-inscribing.md: -------------------------------------------------------------------------------- 1 | Batch Inscribing 2 | ================ 3 | 4 | Multiple inscriptions can be created inscriptions at the same time using the 5 | [pointer field](./../inscriptions/pointer.md). This is especially helpful for 6 | collections, or other cases when multiple inscriptions should share the same 7 | parent, since the parent can passed into a reveal transaction that creates 8 | multiple children. 9 | 10 | To create a batch inscription using a batchfile in `batch.yaml`, run the 11 | following command: 12 | 13 | ```bash 14 | ord wallet inscribe --fee-rate 21 --batch batch.yaml 15 | ``` 16 | 17 | Example `batch.yaml` 18 | -------------------- 19 | 20 | ```yaml 21 | {{#include ../../../batch.yaml}} 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/src/bounty/0.md: -------------------------------------------------------------------------------- 1 | Ordinal Bounty 0 2 | ================ 3 | 4 | Criteria 5 | -------- 6 | 7 | Send a sat whose ordinal number ends with a zero to the submission address: 8 | 9 | ✅: [1857578125803250](https://ordinals.com/ordinal/1857578125803250) 10 | 11 | ❌: [1857578125803251](https://ordinals.com/ordinal/1857578125803251) 12 | 13 | The sat must be the first sat of the output you send. 14 | 15 | Reward 16 | ------ 17 | 18 | 100,000 sats 19 | 20 | Submission Address 21 | ------------------ 22 | 23 | [`1PE7u4wbDP2RqfKN6geD1bG57v9Gj9FXm3`](https://mempool.space/address/1PE7u4wbDP2RqfKN6geD1bG57v9Gj9FXm3) 24 | 25 | Status 26 | ------ 27 | 28 | Claimed by [@count_null](https://twitter.com/rodarmor/status/1560793241473400833)! 29 | -------------------------------------------------------------------------------- /templates/input.html: -------------------------------------------------------------------------------- 1 |

Input /{{self.path.0}}/{{self.path.1}}/{{self.path.2}}

2 |
3 | %% if !self.input.previous_output.is_null() { 4 |
previous output
{{self.input.previous_output}}
5 | %% } 6 | %% if self.input.sequence != Sequence::MAX { 7 |
sequence
{{self.input.sequence}}
8 | %% } 9 | %% if !self.input.witness.is_empty() { 10 |
witness
{{hex::encode(consensus::serialize(&self.input.witness))}}
11 | %% } 12 | %% if !self.input.script_sig.is_empty() { 13 |
script sig
{{self.input.script_sig.to_asm_string()}}
14 |
text
{{String::from_utf8_lossy(self.input.script_sig.as_bytes())}}
15 | %% } 16 |
17 | -------------------------------------------------------------------------------- /src/arguments.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | clap::builder::styling::{AnsiColor, Effects, Styles}, 4 | }; 5 | 6 | #[derive(Debug, Parser)] 7 | #[command( 8 | version, 9 | styles = Styles::styled() 10 | .header(AnsiColor::Green.on_default() | Effects::BOLD) 11 | .usage(AnsiColor::Green.on_default() | Effects::BOLD) 12 | .literal(AnsiColor::Blue.on_default() | Effects::BOLD) 13 | .placeholder(AnsiColor::Cyan.on_default())) 14 | ] 15 | pub(crate) struct Arguments { 16 | #[command(flatten)] 17 | pub(crate) options: Options, 18 | #[command(subcommand)] 19 | pub(crate) subcommand: Subcommand, 20 | } 21 | 22 | impl Arguments { 23 | pub(crate) fn run(self) -> SubcommandResult { 24 | self.subcommand.run(self.options) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/subcommand/subsidy.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Subsidy { 5 | #[arg(help = "List sats in subsidy at .")] 6 | height: Height, 7 | } 8 | 9 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 10 | pub struct Output { 11 | pub first: u64, 12 | pub subsidy: u64, 13 | pub name: String, 14 | } 15 | 16 | impl Subsidy { 17 | pub(crate) fn run(self) -> SubcommandResult { 18 | let first = self.height.starting_sat(); 19 | 20 | let subsidy = self.height.subsidy(); 21 | 22 | if subsidy == 0 { 23 | bail!("block {} has no subsidy", self.height); 24 | } 25 | 26 | Ok(Box::new(Output { 27 | first: first.0, 28 | subsidy, 29 | name: first.name(), 30 | })) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /static/preview-pdf.js: -------------------------------------------------------------------------------- 1 | import pdfjs from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.2.146/+esm'; 2 | 3 | pdfjs.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.2.146/build/pdf.worker.min.js'; 4 | 5 | let canvas = document.querySelector('canvas'); 6 | 7 | let pdf = await pdfjs.getDocument(`/content/${canvas.dataset.inscription}`).promise; 8 | 9 | let page = await pdf.getPage(1); 10 | 11 | let scale = window.devicePixelRatio || 1; 12 | 13 | let viewport = page.getViewport({ scale }); 14 | 15 | canvas.width = Math.ceil(viewport.width * scale); 16 | 17 | canvas.height = Math.ceil(viewport.height * scale); 18 | 19 | page.render({ 20 | canvasContext: canvas.getContext('2d'), 21 | transform: [scale, 0, 0, scale, 0, 0], 22 | viewport, 23 | }); 24 | -------------------------------------------------------------------------------- /docs/src/bounty/1.md: -------------------------------------------------------------------------------- 1 | Ordinal Bounty 1 2 | ================ 3 | 4 | Criteria 5 | -------- 6 | 7 | The transaction that submits a UTXO containing the oldest sat, i.e., that with 8 | the lowest number, amongst all submitted UTXOs will be judged the winner. 9 | 10 | The bounty is open for submissions until block 753984—the first block of 11 | difficulty adjustment period 374. Submissions included in block 753984 or later 12 | will not be considered. 13 | 14 | Reward 15 | ------ 16 | 17 | 200,000 sats 18 | 19 | Submission Address 20 | ------------------ 21 | 22 | [`145Z7PFHyVrwiMWwEcUmDgFbmUbQSU9aap`](https://mempool.space/address/145Z7PFHyVrwiMWwEcUmDgFbmUbQSU9aap) 23 | 24 | Status 25 | ------ 26 | 27 | Claimed by [@ordinalsindex](https://twitter.com/rodarmor/status/1569883266508853251)! 28 | -------------------------------------------------------------------------------- /docs/src/bounty/2.md: -------------------------------------------------------------------------------- 1 | Ordinal Bounty 2 2 | ================ 3 | 4 | Criteria 5 | -------- 6 | 7 | Send an uncommon sat to the submission address: 8 | 9 | ✅: [347100000000000](https://ordinals.com/sat/347100000000000) 10 | 11 | ❌: [6685000001337](https://ordinals.com/sat/6685000001337) 12 | 13 | Confirm that the submission address has not received transactions before submitting your entry. Only the first successful submission will be rewarded. 14 | 15 | Reward 16 | ------ 17 | 18 | 300,000 sats 19 | 20 | Submission Address 21 | ------------------ 22 | 23 | [`1Hyr94uypwWq5CQffaXHvwUMEyBPp3TUZH`](https://mempool.space/address/1Hyr94uypwWq5CQffaXHvwUMEyBPp3TUZH) 24 | 25 | Status 26 | ------ 27 | 28 | Claimed by [@utxoset](https://twitter.com/rodarmor/status/1582424455615172608)! 29 | -------------------------------------------------------------------------------- /templates/preview-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/blocktime.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Copy, Clone)] 4 | pub(crate) enum Blocktime { 5 | Confirmed(DateTime), 6 | Expected(DateTime), 7 | } 8 | 9 | impl Blocktime { 10 | pub(crate) fn confirmed(seconds: u32) -> Self { 11 | Self::Confirmed(timestamp(seconds)) 12 | } 13 | 14 | pub(crate) fn timestamp(self) -> DateTime { 15 | match self { 16 | Self::Confirmed(timestamp) | Self::Expected(timestamp) => timestamp, 17 | } 18 | } 19 | 20 | pub(crate) fn unix_timestamp(self) -> i64 { 21 | match self { 22 | Self::Confirmed(timestamp) | Self::Expected(timestamp) => timestamp.timestamp(), 23 | } 24 | } 25 | 26 | pub(crate) fn suffix(self) -> &'static str { 27 | match self { 28 | Self::Confirmed(_) => "", 29 | Self::Expected(_) => " (expected)", 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/subcommand/decode.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Serialize, Eq, PartialEq, Deserialize, Debug)] 4 | pub struct Output { 5 | pub inscriptions: Vec, 6 | } 7 | 8 | #[derive(Debug, Parser)] 9 | pub(crate) struct Decode { 10 | transaction: Option, 11 | } 12 | 13 | impl Decode { 14 | pub(crate) fn run(self) -> SubcommandResult { 15 | let transaction = if let Some(path) = self.transaction { 16 | Transaction::consensus_decode(&mut File::open(path)?)? 17 | } else { 18 | Transaction::consensus_decode(&mut io::stdin())? 19 | }; 20 | 21 | let inscriptions = ParsedEnvelope::from_transaction(&transaction); 22 | 23 | Ok(Box::new(Output { 24 | inscriptions: inscriptions 25 | .into_iter() 26 | .map(|inscription| inscription.payload) 27 | .collect(), 28 | })) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/subcommand/wallet/create.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct Output { 5 | pub mnemonic: Mnemonic, 6 | pub passphrase: Option, 7 | } 8 | 9 | #[derive(Debug, Parser)] 10 | pub(crate) struct Create { 11 | #[arg( 12 | long, 13 | default_value = "", 14 | help = "Use to derive wallet seed." 15 | )] 16 | pub(crate) passphrase: String, 17 | } 18 | 19 | impl Create { 20 | pub(crate) fn run(self, options: Options) -> SubcommandResult { 21 | let mut entropy = [0; 16]; 22 | rand::thread_rng().fill_bytes(&mut entropy); 23 | 24 | let mnemonic = Mnemonic::from_entropy(&entropy)?; 25 | 26 | initialize_wallet(&options, mnemonic.to_seed(self.passphrase.clone()))?; 27 | 28 | Ok(Box::new(Output { 29 | mnemonic, 30 | passphrase: Some(self.passphrase), 31 | })) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/subcommand/wallet/balance.rs: -------------------------------------------------------------------------------- 1 | use {super::*, crate::wallet::Wallet, std::collections::BTreeSet}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct Output { 5 | pub cardinal: u64, 6 | } 7 | 8 | pub(crate) fn run(options: Options) -> SubcommandResult { 9 | let index = Index::open(&options)?; 10 | index.update()?; 11 | 12 | let unspent_outputs = index.get_unspent_outputs(Wallet::load(&options)?)?; 13 | 14 | let inscription_outputs = index 15 | .get_inscriptions(&unspent_outputs)? 16 | .keys() 17 | .map(|satpoint| satpoint.outpoint) 18 | .collect::>(); 19 | 20 | let mut balance = 0; 21 | for (outpoint, amount) in index.get_unspent_outputs(Wallet::load(&options)?)? { 22 | if !inscription_outputs.contains(&outpoint) { 23 | balance += amount.to_sat() 24 | } 25 | } 26 | 27 | Ok(Box::new(Output { cardinal: balance })) 28 | } 29 | -------------------------------------------------------------------------------- /tests/parse.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::parse::Output, ord::Object}; 2 | 3 | #[test] 4 | fn name() { 5 | assert_eq!( 6 | CommandBuilder::new("parse a").run_and_deserialize_output::(), 7 | Output { 8 | object: Object::Integer(2099999997689999), 9 | } 10 | ); 11 | } 12 | 13 | #[test] 14 | fn hash() { 15 | assert_eq!( 16 | CommandBuilder::new("parse 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") 17 | .run_and_deserialize_output::(), 18 | Output { 19 | object: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" 20 | .parse::() 21 | .unwrap(), 22 | } 23 | ); 24 | } 25 | 26 | #[test] 27 | fn unrecognized_object() { 28 | CommandBuilder::new("parse A") 29 | .stderr_regex(r"error: .*: unrecognized object\n.*") 30 | .expected_exit_code(2) 31 | .run_and_extract_stdout(); 32 | } 33 | -------------------------------------------------------------------------------- /deploy/bitcoind.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | After=network-online.target 3 | Description=Bitcoin daemon 4 | Documentation=https://github.com/bitcoin/bitcoin/blob/master/doc/init.md 5 | Wants=network-online.target 6 | 7 | [Service] 8 | ConfigurationDirectory=bitcoin 9 | ConfigurationDirectoryMode=0710 10 | ExecStart=/usr/local/bin/bitcoind \ 11 | -conf=/etc/bitcoin/bitcoin.conf \ 12 | -chain=${CHAIN} 13 | ExecStartPre=/bin/chgrp bitcoin /etc/bitcoin 14 | Group=bitcoin 15 | MemoryDenyWriteExecute=true 16 | NoNewPrivileges=true 17 | PermissionsStartOnly=true 18 | PrivateDevices=true 19 | PrivateTmp=true 20 | ProtectHome=true 21 | ProtectSystem=full 22 | Restart=on-failure 23 | RuntimeDirectory=bitcoind 24 | RuntimeDirectoryMode=0710 25 | StateDirectory=bitcoind 26 | StateDirectoryMode=0710 27 | TimeoutStartSec=infinity 28 | TimeoutStopSec=600 29 | Type=simple 30 | User=bitcoin 31 | 32 | [Install] 33 | WantedBy=multi-user.target 34 | -------------------------------------------------------------------------------- /templates/inscriptions-block.html: -------------------------------------------------------------------------------- 1 |

Inscriptions in Block {{ &self.block }}

2 |
3 | %% for id in &self.inscriptions { 4 | {{ Iframe::thumbnail(*id) }} 5 | %% } 6 |
7 |
8 | %% if let Some(prev_block) = &self.prev_block { 9 | 10 | • 11 | %% } 12 | %% if let Some(prev_page) = &self.prev_page { 13 | 14 | %% } else { 15 | prev 16 | %% } 17 | %% if let Some(next_page) = &self.next_page { 18 | 19 | %% } else { 20 | next 21 | %% } 22 | %% if let Some(next_block) = &self.next_block { 23 | • 24 | 25 | %% } 26 |
27 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/runestone_decipher.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use { 4 | bitcoin::{ 5 | locktime, opcodes, 6 | script::{self, PushBytes}, 7 | Transaction, TxOut, 8 | }, 9 | libfuzzer_sys::fuzz_target, 10 | ord::runes::Runestone, 11 | }; 12 | 13 | fuzz_target!(|input: Vec>| { 14 | let mut builder = script::Builder::new() 15 | .push_opcode(opcodes::all::OP_RETURN) 16 | .push_slice(b"R"); 17 | 18 | for slice in input { 19 | let Ok(push): Result<&PushBytes, _> = slice.as_slice().try_into() else { 20 | continue; 21 | }; 22 | builder = builder.push_slice(push); 23 | } 24 | 25 | let tx = Transaction { 26 | input: Vec::new(), 27 | lock_time: locktime::absolute::LockTime::ZERO, 28 | output: vec![TxOut { 29 | script_pubkey: builder.into_script(), 30 | value: 0, 31 | }], 32 | version: 0, 33 | }; 34 | 35 | Runestone::from_transaction(&tx); 36 | }); 37 | -------------------------------------------------------------------------------- /static/index.js: -------------------------------------------------------------------------------- 1 | for (let time of document.body.getElementsByTagName('time')) { 2 | time.setAttribute('title', new Date(time.textContent)); 3 | } 4 | 5 | let next = document.querySelector('a.next'); 6 | let prev = document.querySelector('a.prev'); 7 | 8 | window.addEventListener('keydown', e => { 9 | if (document.activeElement.tagName == 'INPUT') { 10 | return; 11 | } 12 | 13 | switch (e.key) { 14 | case 'ArrowRight': 15 | if (next) { 16 | window.location = next.href; 17 | } 18 | return; 19 | case 'ArrowLeft': 20 | if (prev) { 21 | window.location = prev.href; 22 | } 23 | return; 24 | } 25 | }); 26 | 27 | const search = document.querySelector('form[action="/search"]'); 28 | const query = search.querySelector('input[name="query"]'); 29 | 30 | search.addEventListener('submit', (e) => { 31 | if (!query.value) { 32 | e.preventDefault(); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /src/subcommand/wallet/transactions.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Transactions { 5 | #[arg(long, help = "Fetch at most transactions.")] 6 | limit: Option, 7 | } 8 | 9 | #[derive(Serialize, Deserialize)] 10 | pub struct Output { 11 | pub transaction: Txid, 12 | pub confirmations: i32, 13 | } 14 | 15 | impl Transactions { 16 | pub(crate) fn run(self, options: Options) -> SubcommandResult { 17 | let mut output = Vec::new(); 18 | for tx in options 19 | .bitcoin_rpc_client_for_wallet_command(false)? 20 | .list_transactions( 21 | None, 22 | Some(self.limit.unwrap_or(u16::MAX).into()), 23 | None, 24 | None, 25 | )? 26 | { 27 | output.push(Output { 28 | transaction: tx.info.txid, 29 | confirmations: tx.info.confirmations, 30 | }); 31 | } 32 | 33 | Ok(Box::new(output)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [workspace] 8 | members = ["."] 9 | 10 | [package.metadata] 11 | cargo-fuzz = true 12 | 13 | [profile.release] 14 | debug = 1 15 | 16 | [dependencies] 17 | arbitrary = { version = "1", features = ["derive"] } 18 | bitcoin = { version = "0.30.0", features = ["rand"] } 19 | libfuzzer-sys = "0.4" 20 | ord = { path = ".." } 21 | 22 | [[bin]] 23 | name = "runestone-decipher" 24 | path = "fuzz_targets/runestone_decipher.rs" 25 | test = false 26 | doc = false 27 | 28 | [[bin]] 29 | name = "transaction-builder" 30 | path = "fuzz_targets/transaction_builder.rs" 31 | test = false 32 | doc = false 33 | 34 | [[bin]] 35 | name = "varint-encode" 36 | path = "fuzz_targets/varint_encode.rs" 37 | test = false 38 | doc = false 39 | 40 | [[bin]] 41 | name = "varint-decode" 42 | path = "fuzz_targets/varint_decode.rs" 43 | test = false 44 | doc = false 45 | -------------------------------------------------------------------------------- /deploy/ord.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | After=network.target 3 | Description=Ord server 4 | StartLimitBurst=120 5 | StartLimitIntervalSec=10m 6 | 7 | [Service] 8 | AmbientCapabilities=CAP_NET_BIND_SERVICE 9 | Environment=RUST_BACKTRACE=1 10 | Environment=RUST_LOG=info 11 | ExecStart=/usr/local/bin/ord \ 12 | --bitcoin-data-dir /var/lib/bitcoind \ 13 | --data-dir /var/lib/ord \ 14 | --config-dir /var/lib/ord \ 15 | --chain ${CHAIN} \ 16 | --index-sats \ 17 | server \ 18 | --acme-contact mailto:casey@rodarmor.com \ 19 | --http \ 20 | --https 21 | Group=ord 22 | LimitNOFILE=65536 23 | MemoryDenyWriteExecute=true 24 | NoNewPrivileges=true 25 | PrivateDevices=true 26 | PrivateTmp=true 27 | ProtectHome=true 28 | ProtectSystem=full 29 | Restart=on-failure 30 | RestartSec=5s 31 | StateDirectory=ord 32 | StateDirectoryMode=0700 33 | TimeoutStopSec=10m 34 | Type=simple 35 | User=ord 36 | WorkingDirectory=/var/lib/ord 37 | 38 | [Install] 39 | WantedBy=multi-user.target 40 | -------------------------------------------------------------------------------- /docs/src/bounties.md: -------------------------------------------------------------------------------- 1 | Ordinal Bounty Hunting Hints 2 | ============================ 3 | 4 | - The `ord` wallet can send and receive specific satoshis. Additionally, 5 | ordinal theory is extremely simple. A clever hacker should be able to write 6 | code from scratch to manipulate satoshis using ordinal theory in no time. 7 | 8 | - For more information about ordinal theory, check out the [FAQ](./faq.md) for 9 | an overview, the 10 | [BIP](https://github.com/ordinals/ord/blob/master/bip.mediawiki) for the 11 | technical details, and the [ord repo](https://github.com/ordinals/ord) for the 12 | `ord` wallet and block explorer. 13 | 14 | - Satoshi was the original developer of ordinal theory. However, he knew that 15 | others would consider it heretical and dangerous, so he hid his knowledge, 16 | and it was lost to the sands of time. This potent theory is only now being 17 | rediscovered. You can help by researching rare satoshis. 18 | 19 | Good luck and godspeed! 20 | -------------------------------------------------------------------------------- /src/tally.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) trait Tally { 4 | fn tally(self, count: usize) -> Tallied; 5 | } 6 | 7 | impl Tally for &'static str { 8 | fn tally(self, count: usize) -> Tallied { 9 | Tallied { noun: self, count } 10 | } 11 | } 12 | 13 | pub(crate) struct Tallied { 14 | count: usize, 15 | noun: &'static str, 16 | } 17 | 18 | impl Display for Tallied { 19 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 20 | if self.count == 1 { 21 | write!(f, "{} {}", self.count, self.noun) 22 | } else { 23 | write!(f, "{} {}s", self.count, self.noun) 24 | } 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | 32 | #[test] 33 | fn zero() { 34 | assert_eq!("foo".tally(0).to_string(), "0 foos") 35 | } 36 | 37 | #[test] 38 | fn one() { 39 | assert_eq!("foo".tally(1).to_string(), "1 foo") 40 | } 41 | 42 | #[test] 43 | fn two() { 44 | assert_eq!("foo".tally(2).to_string(), "2 foos") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /quickstart/macos: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | # install homebrew 6 | if ! command -v brew; then 7 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 8 | fi 9 | 10 | # check homebrew 11 | brew --version 12 | 13 | # install bitcoin 14 | if ! command -v bitcoind; then 15 | brew install bitcoin 16 | fi 17 | 18 | # check bitcoind 19 | bitcoind --version 20 | 21 | # write config 22 | if [[ ! -f ~/Library/Application\ Support/Bitcoin/bitcoin.conf ]]; then 23 | printf 'txindex=1\nsignet=1\n' > ~/Library/Application\ Support/Bitcoin/bitcoin.conf 24 | fi 25 | 26 | # start bitcoind 27 | if ! bitcoin-cli getblockchaininfo; then 28 | brew services start bitcoin 29 | fi 30 | 31 | # check bitcoind 32 | bitcoin-cli getblockchaininfo | grep signet 33 | 34 | # install ord 35 | if ! command -v ord; then 36 | curl --proto '=https' --tlsv1.2 -fsLS https://ordinals.com/install.sh | bash -s 37 | fi 38 | 39 | # check ord 40 | ord --version 41 | -------------------------------------------------------------------------------- /src/subcommand/server/accept_json.rs: -------------------------------------------------------------------------------- 1 | use {super::*, axum::extract::FromRef}; 2 | 3 | pub(crate) struct AcceptJson(pub(crate) bool); 4 | 5 | #[async_trait::async_trait] 6 | impl axum::extract::FromRequestParts for AcceptJson 7 | where 8 | Arc: FromRef, 9 | S: Send + Sync, 10 | { 11 | type Rejection = (StatusCode, &'static str); 12 | 13 | async fn from_request_parts( 14 | parts: &mut http::request::Parts, 15 | state: &S, 16 | ) -> Result { 17 | let state = Arc::from_ref(state); 18 | let json_api_enabled = state.is_json_api_enabled; 19 | let json_header = parts 20 | .headers 21 | .get("accept") 22 | .map(|value| value == "application/json") 23 | .unwrap_or_default(); 24 | if json_header && json_api_enabled { 25 | Ok(Self(true)) 26 | } else if json_header && !json_api_enabled { 27 | Err((StatusCode::NOT_ACCEPTABLE, "JSON API not enabled")) 28 | } else { 29 | Ok(Self(false)) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/subcommand/traits.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Traits { 5 | #[arg(help = "Show traits for .")] 6 | sat: Sat, 7 | } 8 | 9 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 10 | pub struct Output { 11 | pub number: u64, 12 | pub decimal: String, 13 | pub degree: String, 14 | pub name: String, 15 | pub height: u64, 16 | pub cycle: u64, 17 | pub epoch: u64, 18 | pub period: u64, 19 | pub offset: u64, 20 | pub rarity: Rarity, 21 | } 22 | 23 | impl Traits { 24 | pub(crate) fn run(self) -> SubcommandResult { 25 | Ok(Box::new(Output { 26 | number: self.sat.n(), 27 | decimal: self.sat.decimal().to_string(), 28 | degree: self.sat.degree().to_string(), 29 | name: self.sat.name(), 30 | height: self.sat.height().0, 31 | cycle: self.sat.cycle(), 32 | epoch: self.sat.epoch().0, 33 | period: self.sat.period(), 34 | offset: self.sat.third(), 35 | rarity: self.sat.rarity(), 36 | })) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/subcommand/wallet/cardinals.rs: -------------------------------------------------------------------------------- 1 | use {super::*, crate::wallet::Wallet, std::collections::BTreeSet}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct CardinalUtxo { 5 | pub output: OutPoint, 6 | pub amount: u64, 7 | } 8 | 9 | pub(crate) fn run(options: Options) -> SubcommandResult { 10 | let index = Index::open(&options)?; 11 | index.update()?; 12 | 13 | let unspent_outputs = index.get_unspent_outputs(Wallet::load(&options)?)?; 14 | 15 | let inscribed_utxos = index 16 | .get_inscriptions(&unspent_outputs)? 17 | .keys() 18 | .map(|satpoint| satpoint.outpoint) 19 | .collect::>(); 20 | 21 | let cardinal_utxos = unspent_outputs 22 | .iter() 23 | .filter_map(|(output, amount)| { 24 | if inscribed_utxos.contains(output) { 25 | None 26 | } else { 27 | Some(CardinalUtxo { 28 | output: *output, 29 | amount: amount.to_sat(), 30 | }) 31 | } 32 | }) 33 | .collect::>(); 34 | 35 | Ok(Box::new(cardinal_utxos)) 36 | } 37 | -------------------------------------------------------------------------------- /tests/traits.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::traits::Output, ord::Rarity}; 2 | 3 | #[test] 4 | fn traits_command_prints_sat_traits() { 5 | assert_eq!( 6 | CommandBuilder::new("traits 0").run_and_deserialize_output::(), 7 | Output { 8 | number: 0, 9 | decimal: "0.0".into(), 10 | degree: "0°0′0″0‴".into(), 11 | name: "nvtdijuwxlp".into(), 12 | height: 0, 13 | cycle: 0, 14 | epoch: 0, 15 | period: 0, 16 | offset: 0, 17 | rarity: Rarity::Mythic, 18 | } 19 | ); 20 | } 21 | #[test] 22 | fn traits_command_for_last_sat() { 23 | assert_eq!( 24 | CommandBuilder::new("traits 2099999997689999").run_and_deserialize_output::(), 25 | Output { 26 | number: 2099999997689999, 27 | decimal: "6929999.0".into(), 28 | degree: "5°209999′1007″0‴".into(), 29 | name: "a".into(), 30 | height: 6929999, 31 | cycle: 5, 32 | epoch: 32, 33 | period: 3437, 34 | offset: 0, 35 | rarity: Rarity::Uncommon, 36 | } 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/decimal.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(PartialEq, Debug)] 4 | pub(crate) struct Decimal { 5 | height: Height, 6 | offset: u64, 7 | } 8 | 9 | impl From for Decimal { 10 | fn from(sat: Sat) -> Self { 11 | Self { 12 | height: sat.height(), 13 | offset: sat.third(), 14 | } 15 | } 16 | } 17 | 18 | impl Display for Decimal { 19 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 20 | write!(f, "{}.{}", self.height, self.offset) 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn decimal() { 30 | assert_eq!( 31 | Sat(0).decimal(), 32 | Decimal { 33 | height: Height(0), 34 | offset: 0 35 | } 36 | ); 37 | assert_eq!( 38 | Sat(1).decimal(), 39 | Decimal { 40 | height: Height(0), 41 | offset: 1 42 | } 43 | ); 44 | assert_eq!( 45 | Sat(2099999997689999).decimal(), 46 | Decimal { 47 | height: Height(6929999), 48 | offset: 0 49 | } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/src/donate.md: -------------------------------------------------------------------------------- 1 | Donate 2 | ====== 3 | 4 | Ordinals is open-source and community funded. The current lead maintainer of 5 | `ord` is [raphjaph](https://github.com/raphjaph/). Raph's work on `ord` is 6 | entirely funded by donations. If you can, please consider donating! 7 | 8 | The donation address for Bitcoin is 9 | [bc1q8kt9pyd6r27k2840l8g5d7zshz3cg9v6rfda0m248lva3ve5072q3sxelt](https://mempool.space/address/bc1q8kt9pyd6r27k2840l8g5d7zshz3cg9v6rfda0m248lva3ve5072q3sxelt). The donation address for inscriptions is [bc1qn3map8m9hmk5jyqdkkwlwvt335g94zvxwd9aql7q3vdkdw9r5eyqvlvec0](https://mempool.space/address/bc1qn3map8m9hmk5jyqdkkwlwvt335g94zvxwd9aql7q3vdkdw9r5eyqvlvec0). 10 | 11 | Both addresses are in a 2 of 4 multisig wallet with keys held by 12 | [raphjaph](https://twitter.com/raphjaph), 13 | [erin](https://twitter.com/realizingerin), 14 | [rodarmor](https://twitter.com/rodarmor), and 15 | [ordinally](https://twitter.com/veryordinally). 16 | 17 | Donations received will go towards funding maintenance and development of `ord`, 18 | as well as hosting costs for [ordinals.com](https://ordinals.com). 19 | 20 | Thank you for donating! 21 | -------------------------------------------------------------------------------- /bin/package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | VERSION=${REF#"refs/tags/"} 6 | DIST=`pwd`/dist 7 | 8 | echo "Packaging ord $VERSION for $TARGET..." 9 | 10 | test -f Cargo.lock || cargo generate-lockfile 11 | 12 | echo "Building ord..." 13 | RUSTFLAGS="--deny warnings $TARGET_RUSTFLAGS" \ 14 | cargo build --bin ord --target $TARGET --release 15 | EXECUTABLE=target/$TARGET/release/ord 16 | 17 | if [[ $OS == windows-latest ]]; then 18 | EXECUTABLE=$EXECUTABLE.exe 19 | fi 20 | 21 | echo "Copying release files..." 22 | mkdir -p dist/ord-$VERSION 23 | cp \ 24 | $EXECUTABLE \ 25 | Cargo.lock \ 26 | Cargo.toml \ 27 | LICENSE \ 28 | README.md \ 29 | $DIST/ord-$VERSION 30 | 31 | cd $DIST 32 | echo "Creating release archive..." 33 | case $OS in 34 | ubuntu-latest | macos-latest) 35 | ARCHIVE=$DIST/ord-$VERSION-$TARGET.tar.gz 36 | tar czf $ARCHIVE * 37 | echo "::set-output name=archive::$ARCHIVE" 38 | ;; 39 | windows-latest) 40 | ARCHIVE=$DIST/ord-$VERSION-$TARGET.zip 41 | 7z a $ARCHIVE * 42 | echo "::set-output name=archive::`pwd -W`/ord-$VERSION-$TARGET.zip" 43 | ;; 44 | esac 45 | -------------------------------------------------------------------------------- /src/subcommand/info.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Info { 5 | #[arg(long)] 6 | transactions: bool, 7 | } 8 | 9 | #[derive(Serialize, Deserialize)] 10 | pub struct TransactionsOutput { 11 | pub start: u64, 12 | pub end: u64, 13 | pub count: u64, 14 | pub elapsed: f64, 15 | } 16 | 17 | impl Info { 18 | pub(crate) fn run(self, options: Options) -> SubcommandResult { 19 | let index = Index::open(&options)?; 20 | index.update()?; 21 | let info = index.info()?; 22 | 23 | if self.transactions { 24 | let mut output = Vec::new(); 25 | for window in info.transactions.windows(2) { 26 | let start = &window[0]; 27 | let end = &window[1]; 28 | output.push(TransactionsOutput { 29 | start: start.starting_block_count, 30 | end: end.starting_block_count, 31 | count: end.starting_block_count - start.starting_block_count, 32 | elapsed: (end.starting_timestamp - start.starting_timestamp) as f64 / 1000.0 / 60.0, 33 | }); 34 | } 35 | Ok(Box::new(output)) 36 | } else { 37 | Ok(Box::new(info)) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | Summary 2 | ======= 3 | 4 | [Introduction](introduction.md) 5 | - [Overview](overview.md) 6 | - [Digital Artifacts](digital-artifacts.md) 7 | - [Inscriptions](inscriptions.md) 8 | - [Metadata](inscriptions/metadata.md) 9 | - [Provenance](inscriptions/provenance.md) 10 | - [Recursion](inscriptions/recursion.md) 11 | - [Pointer](inscriptions/pointer.md) 12 | - [FAQ](faq.md) 13 | - [Contributing](contributing.md) 14 | - [Donate](donate.md) 15 | - [Guides](guides.md) 16 | - [Explorer](guides/explorer.md) 17 | - [Inscriptions](guides/inscriptions.md) 18 | - [Batch Inscribing](guides/batch-inscribing.md) 19 | - [Sat Hunting](guides/sat-hunting.md) 20 | - [Teleburning](guides/teleburning.md) 21 | - [Collecting](guides/collecting.md) 22 | - [Sparrow Wallet](guides/collecting/sparrow-wallet.md) 23 | - [Testing](guides/testing.md) 24 | - [Moderation](guides/moderation.md) 25 | - [Reindexing](guides/reindexing.md) 26 | - [Bounties](bounties.md) 27 | - [Bounty 0: 100,000 sats Claimed!](bounty/0.md) 28 | - [Bounty 1: 200,000 sats Claimed!](bounty/1.md) 29 | - [Bounty 2: 300,000 sats Claimed!](bounty/2.md) 30 | - [Bounty 3: 400,000 sats](bounty/3.md) 31 | -------------------------------------------------------------------------------- /docs/src/guides/collecting.md: -------------------------------------------------------------------------------- 1 | Collecting 2 | ========== 3 | 4 | Currently, [ord](https://github.com/ordinals/ord/) is the only wallet supporting 5 | sat-control and sat-selection, which are required to safely store and send rare 6 | sats and inscriptions, hereafter ordinals. 7 | 8 | The recommended way to send, receive, and store ordinals is with `ord`, but if 9 | you are careful, it is possible to safely store, and in some cases send, 10 | ordinals with other wallets. 11 | 12 | As a general note, receiving ordinals in an unsupported wallet is not 13 | dangerous. Ordinals can be sent to any bitcoin address, and are safe as long as 14 | the UTXO that contains them is not spent. However, if that wallet is then used 15 | to send bitcoin, it may select the UTXO containing the ordinal as an input, and 16 | send the inscription or spend it to fees. 17 | 18 | A [guide](./collecting/sparrow-wallet.md) to creating an `ord`-compatible wallet with [Sparrow Wallet](https://sparrowwallet.com/), is available 19 | in this handbook. 20 | 21 | Please note that if you follow this guide, you should not use the wallet you 22 | create to send BTC, unless you perform manual coin-selection to avoid sending 23 | ordinals. 24 | -------------------------------------------------------------------------------- /docs/src/inscriptions/recursion.md: -------------------------------------------------------------------------------- 1 | Recursion 2 | ========= 3 | 4 | An important exception to [sandboxing](../inscriptions.md#sandboxing) is 5 | recursion: access to `ord`'s `/content` endpoint is permitted, allowing 6 | inscriptions to access the content of other inscriptions by requesting 7 | `/content/`. 8 | 9 | This has a number of interesting use-cases: 10 | 11 | - Remixing the content of existing inscriptions. 12 | 13 | - Publishing snippets of code, images, audio, or stylesheets as shared public 14 | resources. 15 | 16 | - Generative art collections where an algorithm is inscribed as JavaScript, 17 | and instantiated from multiple inscriptions with unique seeds. 18 | 19 | - Generative profile picture collections where accessories and attributes are 20 | inscribed as individual images, or in a shared texture atlas, and then 21 | combined, collage-style, in unique combinations in multiple inscriptions. 22 | 23 | A few other endpoints that inscriptions may access are the following: 24 | 25 | - `/blockheight`: latest block height. 26 | - `/blockhash`: latest block hash. 27 | - `/blockhash/`: block hash at given block height. 28 | - `/blocktime`: UNIX time stamp of latest block. 29 | -------------------------------------------------------------------------------- /src/templates/preview.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(boilerplate::Boilerplate)] 4 | pub(crate) struct PreviewAudioHtml { 5 | pub(crate) inscription_id: InscriptionId, 6 | } 7 | 8 | #[derive(boilerplate::Boilerplate)] 9 | pub(crate) struct PreviewCodeHtml { 10 | pub(crate) inscription_id: InscriptionId, 11 | } 12 | 13 | #[derive(boilerplate::Boilerplate)] 14 | pub(crate) struct PreviewImageHtml { 15 | pub(crate) inscription_id: InscriptionId, 16 | } 17 | 18 | #[derive(boilerplate::Boilerplate)] 19 | pub(crate) struct PreviewMarkdownHtml { 20 | pub(crate) inscription_id: InscriptionId, 21 | } 22 | 23 | #[derive(boilerplate::Boilerplate)] 24 | pub(crate) struct PreviewModelHtml { 25 | pub(crate) inscription_id: InscriptionId, 26 | } 27 | 28 | #[derive(boilerplate::Boilerplate)] 29 | pub(crate) struct PreviewPdfHtml { 30 | pub(crate) inscription_id: InscriptionId, 31 | } 32 | 33 | #[derive(boilerplate::Boilerplate)] 34 | pub(crate) struct PreviewTextHtml<'a> { 35 | pub(crate) text: &'a str, 36 | } 37 | 38 | #[derive(boilerplate::Boilerplate)] 39 | pub(crate) struct PreviewUnknownHtml; 40 | 41 | #[derive(boilerplate::Boilerplate)] 42 | pub(crate) struct PreviewVideoHtml { 43 | pub(crate) inscription_id: InscriptionId, 44 | } 45 | -------------------------------------------------------------------------------- /batch.yaml: -------------------------------------------------------------------------------- 1 | # example batch file 2 | 3 | # there are two modes: 4 | # - `separate-outputs`: place all inscriptions in separate postage-sized outputs 5 | # - `shared-output`: place inscriptions in a single output separated by postage 6 | mode: separate-outputs 7 | 8 | # parent inscription: 9 | parent: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0 10 | 11 | # inscriptions to inscribe 12 | # 13 | # each inscription has the following fields: 14 | # 15 | # `inscription`: path to inscription contents 16 | # `metadata`: inscription metadata (optional) 17 | # `metaprotocol`: inscription metaprotocol (optional) 18 | inscriptions: 19 | - file: mango.avif 20 | metadata: 21 | title: Delicious Mangos 22 | description: > 23 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam semper, 24 | ligula ornare laoreet tincidunt, odio nisi euismod tortor, vel blandit 25 | metus est et odio. Nullam venenatis, urna et molestie vestibulum, orci 26 | mi efficitur risus, eu malesuada diam lorem sed velit. Nam fermentum 27 | dolor et luctus euismod. 28 | 29 | - file: token.json 30 | metaprotocol: brc-20 31 | 32 | - file: tulip.png 33 | metadata: 34 | author: Satoshi Nakamoto 35 | -------------------------------------------------------------------------------- /src/templates/range.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Boilerplate)] 4 | pub(crate) struct RangeHtml { 5 | pub(crate) start: Sat, 6 | pub(crate) end: Sat, 7 | } 8 | 9 | impl PageContent for RangeHtml { 10 | fn title(&self) -> String { 11 | format!("Sat range {}–{}", self.start, self.end) 12 | } 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | 19 | #[test] 20 | fn range_html() { 21 | pretty_assert_eq!( 22 | RangeHtml { 23 | start: Sat(0), 24 | end: Sat(1), 25 | } 26 | .to_string(), 27 | " 28 |

Sat range 0–1

29 |
30 |
value
1
31 |
first
0
32 |
33 | " 34 | .unindent() 35 | ); 36 | } 37 | 38 | #[test] 39 | fn bugfix_broken_link() { 40 | pretty_assert_eq!( 41 | RangeHtml { 42 | start: Sat(1), 43 | end: Sat(10), 44 | } 45 | .to_string(), 46 | " 47 |

Sat range 1–10

48 |
49 |
value
9
50 |
first
1
51 |
52 | " 53 | .unindent() 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Deserialize, Default, PartialEq, Debug)] 4 | #[serde(deny_unknown_fields)] 5 | pub(crate) struct Config { 6 | pub(crate) hidden: HashSet, 7 | pub(crate) bitcoin_rpc_pass: Option, 8 | pub(crate) bitcoin_rpc_user: Option, 9 | } 10 | 11 | impl Config { 12 | pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { 13 | self.hidden.contains(&inscription_id) 14 | } 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | 21 | #[test] 22 | fn inscriptions_can_be_hidden() { 23 | let a = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" 24 | .parse::() 25 | .unwrap(); 26 | 27 | let b = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi1" 28 | .parse::() 29 | .unwrap(); 30 | 31 | let config = Config { 32 | hidden: iter::once(a).collect(), 33 | ..Default::default() 34 | }; 35 | 36 | assert!(config.is_hidden(a)); 37 | assert!(!config.is_hidden(b)); 38 | } 39 | 40 | #[test] 41 | fn example_config_file_is_valid() { 42 | let _: Config = serde_yaml::from_reader(File::open("ord.yaml").unwrap()).unwrap(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/wallet/balance.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::wallet::balance::Output}; 2 | 3 | #[test] 4 | fn wallet_balance() { 5 | let rpc_server = test_bitcoincore_rpc::spawn(); 6 | create_wallet(&rpc_server); 7 | 8 | assert_eq!( 9 | CommandBuilder::new("wallet balance") 10 | .rpc_server(&rpc_server) 11 | .run_and_deserialize_output::() 12 | .cardinal, 13 | 0 14 | ); 15 | 16 | rpc_server.mine_blocks(1); 17 | 18 | assert_eq!( 19 | CommandBuilder::new("wallet balance") 20 | .rpc_server(&rpc_server) 21 | .run_and_deserialize_output::() 22 | .cardinal, 23 | 50 * COIN_VALUE 24 | ); 25 | } 26 | 27 | #[test] 28 | fn wallet_balance_only_counts_cardinal_utxos() { 29 | let rpc_server = test_bitcoincore_rpc::spawn(); 30 | create_wallet(&rpc_server); 31 | 32 | assert_eq!( 33 | CommandBuilder::new("wallet balance") 34 | .rpc_server(&rpc_server) 35 | .run_and_deserialize_output::() 36 | .cardinal, 37 | 0 38 | ); 39 | 40 | inscribe(&rpc_server); 41 | 42 | assert_eq!( 43 | CommandBuilder::new("wallet balance") 44 | .rpc_server(&rpc_server) 45 | .run_and_deserialize_output::() 46 | .cardinal, 47 | 100 * COIN_VALUE - 10_000 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/subcommand/wallet/inscriptions.rs: -------------------------------------------------------------------------------- 1 | use {super::*, crate::wallet::Wallet}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct Output { 5 | pub inscription: InscriptionId, 6 | pub location: SatPoint, 7 | pub explorer: String, 8 | pub postage: u64, 9 | } 10 | 11 | pub(crate) fn run(options: Options) -> SubcommandResult { 12 | let index = Index::open(&options)?; 13 | index.update()?; 14 | 15 | let unspent_outputs = index.get_unspent_outputs(Wallet::load(&options)?)?; 16 | let inscriptions = index.get_inscriptions(&unspent_outputs)?; 17 | 18 | let explorer = match options.chain() { 19 | Chain::Mainnet => "https://ordinals.com/inscription/", 20 | Chain::Regtest => "http://localhost/inscription/", 21 | Chain::Signet => "https://signet.ordinals.com/inscription/", 22 | Chain::Testnet => "https://testnet.ordinals.com/inscription/", 23 | }; 24 | 25 | let mut output = Vec::new(); 26 | 27 | for (location, inscription) in inscriptions { 28 | if let Some(postage) = unspent_outputs.get(&location.outpoint) { 29 | output.push(Output { 30 | location, 31 | inscription, 32 | explorer: format!("{explorer}{inscription}"), 33 | postage: postage.to_sat(), 34 | }) 35 | } 36 | } 37 | 38 | Ok(Box::new(output)) 39 | } 40 | -------------------------------------------------------------------------------- /tests/wallet/outputs.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::wallet::outputs::Output}; 2 | 3 | #[test] 4 | fn outputs() { 5 | let rpc_server = test_bitcoincore_rpc::spawn(); 6 | create_wallet(&rpc_server); 7 | 8 | let coinbase_tx = &rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; 9 | let outpoint = OutPoint::new(coinbase_tx.txid(), 0); 10 | let amount = coinbase_tx.output[0].value; 11 | 12 | let output = CommandBuilder::new("wallet outputs") 13 | .rpc_server(&rpc_server) 14 | .run_and_deserialize_output::>(); 15 | 16 | assert_eq!(output[0].output, outpoint); 17 | assert_eq!(output[0].amount, amount); 18 | } 19 | 20 | #[test] 21 | fn outputs_includes_locked_outputs() { 22 | let rpc_server = test_bitcoincore_rpc::spawn(); 23 | create_wallet(&rpc_server); 24 | 25 | let coinbase_tx = &rpc_server.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0]; 26 | let outpoint = OutPoint::new(coinbase_tx.txid(), 0); 27 | let amount = coinbase_tx.output[0].value; 28 | 29 | rpc_server.lock(outpoint); 30 | 31 | let output = CommandBuilder::new("wallet outputs") 32 | .rpc_server(&rpc_server) 33 | .run_and_deserialize_output::>(); 34 | 35 | assert_eq!(output[0].output, outpoint); 36 | assert_eq!(output[0].amount, amount); 37 | } 38 | -------------------------------------------------------------------------------- /src/subcommand/find.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Find { 5 | #[arg(help = "Find output and offset of .")] 6 | sat: Sat, 7 | #[clap(help = "Find output and offset of all sats in the range [, ).")] 8 | end: Option, 9 | } 10 | 11 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 12 | pub struct Output { 13 | pub satpoint: SatPoint, 14 | } 15 | 16 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 17 | pub struct FindRangeOutput { 18 | pub start: u64, 19 | pub size: u64, 20 | pub satpoint: SatPoint, 21 | } 22 | 23 | impl Find { 24 | pub(crate) fn run(self, options: Options) -> SubcommandResult { 25 | let index = Index::open(&options)?; 26 | 27 | if !index.has_sat_index() { 28 | bail!("find requires index created with `--index-sats` flag"); 29 | } 30 | 31 | index.update()?; 32 | 33 | match self.end { 34 | Some(end) => match index.find_range(self.sat.0, end.0)? { 35 | Some(result) => Ok(Box::new(result)), 36 | None => Err(anyhow!("range has not been mined as of index height")), 37 | }, 38 | None => match index.find(self.sat.0)? { 39 | Some(satpoint) => Ok(Box::new(Output { satpoint })), 40 | None => Err(anyhow!("sat has not been mined as of index height")), 41 | }, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /static/preview-code.js: -------------------------------------------------------------------------------- 1 | import hljs from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/es/highlight.min.js'; 2 | import css from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/es/languages/css.min.js'; 3 | import javascript from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/es/languages/javascript.min.js'; 4 | import json from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/es/languages/json.min.js'; 5 | import python from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/es/languages/python.min.js'; 6 | import yaml from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/es/languages/yaml.min.js'; 7 | 8 | hljs.registerLanguage('css', css); 9 | hljs.registerLanguage('javascript', javascript); 10 | hljs.registerLanguage('json', json); 11 | hljs.registerLanguage('python', python); 12 | hljs.registerLanguage('yaml', yaml); 13 | 14 | const inscription = document.documentElement.dataset.inscription; 15 | const response = await fetch(`/content/${inscription}`); 16 | const contentType = response.headers.get("content-type"); 17 | let language = contentType.split("/")[1]; 18 | if (language === "x-python") { 19 | language = "python"; 20 | } 21 | const code = await response.text(); 22 | document.body.innerHTML = `
${hljs.highlight(code, {language, ignoreIllegals: true}).value}
`; 23 | -------------------------------------------------------------------------------- /bin/graph: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re, sys 4 | from matplotlib.pyplot import * 5 | from dataclasses import dataclass 6 | 7 | @dataclass 8 | class Block: 9 | height: int 10 | ranges: int 11 | time: int 12 | transactions: int 13 | 14 | pat = re.compile( 15 | '''Block (?P[0-9]+) at.*with (?P[0-9]+) transactions.* 16 | .*Wrote (?P[0-9]+) sat ranges from .* outputs in (?P