├── .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 │ ├── preview.rs │ ├── range.rs │ ├── iframe.rs │ ├── inscriptions.rs │ ├── home.rs │ ├── input.rs │ ├── block.rs │ ├── clock.rs │ ├── transaction.rs │ ├── sat.rs │ └── output.rs ├── page_config.rs ├── subcommand │ ├── index.rs │ ├── epochs.rs │ ├── wallet │ │ ├── receive.rs │ │ ├── restore.rs │ │ ├── outputs.rs │ │ ├── balance.rs │ │ ├── create.rs │ │ ├── transactions.rs │ │ ├── cardinals.rs │ │ ├── inscriptions.rs │ │ └── send.rs │ ├── parse.rs │ ├── supply.rs │ ├── find.rs │ ├── subsidy.rs │ ├── traits.rs │ ├── info.rs │ ├── server │ │ └── error.rs │ ├── list.rs │ └── preview.rs ├── wallet.rs ├── arguments.rs ├── deserialize_from_str.rs ├── blocktime.rs ├── index │ ├── rtx.rs │ ├── fetcher.rs │ └── entry.rs ├── config.rs ├── tally.rs ├── decimal.rs ├── fee_rate.rs ├── degree.rs ├── outgoing.rs ├── representation.rs ├── chain.rs ├── subcommand.rs ├── test.rs ├── sat_point.rs ├── height.rs └── media.rs ├── .prettierignore ├── deploy ├── bitcoin.conf ├── checkout ├── ord-dev.service ├── bitcoind.service ├── ord.service └── setup ├── static ├── favicon.png ├── preview-pdf.css ├── preview-audio.css ├── preview-text.js ├── preview-video.css ├── preview-text.css ├── favicon.svg ├── index.js └── preview-pdf.js ├── templates ├── rare.txt ├── preview-unknown.html ├── range.html ├── preview-audio.html ├── preview-video.html ├── preview-pdf.html ├── preview-text.html ├── home.html ├── inscriptions.html ├── input.html ├── preview-image.html ├── block.html ├── output.html ├── sat.html ├── transaction.html ├── page.html └── inscription.html ├── docs ├── 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 │ │ ├── collecting.md │ │ ├── moderation.md │ │ └── explorer.md │ ├── guides.md │ ├── bounty │ │ ├── 0.md │ │ ├── 1.md │ │ ├── 2.md │ │ └── 3.md │ ├── SUMMARY.md │ ├── bounties.md │ ├── digital-artifacts.md │ ├── introduction.md │ ├── inscriptions.md │ ├── contributing.md │ └── donate.md └── book.toml ├── rustfmt.toml ├── tests ├── version.rs ├── wallet.rs ├── supply.rs ├── wallet │ ├── receive.rs │ ├── cardinals.rs │ ├── balance.rs │ ├── outputs.rs │ ├── restore.rs │ ├── sats.rs │ ├── transactions.rs │ ├── inscriptions.rs │ └── create.rs ├── expected.rs ├── parse.rs ├── traits.rs ├── index.rs ├── find.rs ├── subsidy.rs ├── epochs.rs ├── list.rs ├── core.rs ├── lib.rs ├── info.rs ├── test_server.rs └── command_builder.rs ├── .editorconfig ├── .gitignore ├── CONTRIBUTING ├── bin ├── forbid ├── benchmark ├── install-bitcoin-core-linux ├── update-ord-dev ├── flamegraph ├── package └── graph ├── benchmark ├── run └── checkout ├── ord.yaml ├── logo-bw.svg ├── logo-wb.svg ├── contrib ├── initialize-opendime └── raw │ └── justfile ├── shell.nix ├── Vagrantfile ├── test-bitcoincore-rpc └── Cargo.toml ├── fuzz ├── Cargo.toml └── fuzz_targets │ └── transaction_builder.rs ├── quickstart └── macos ├── .github └── workflows │ ├── ci.yaml │ └── 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 | -------------------------------------------------------------------------------- /.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/robotchangzhang/ord/HEAD/examples/h264.mp4 -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/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/src/guides/collecting/images/sending_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/sending_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/sending_02.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/sending_03.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/sending_04.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/sending_05.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/sending_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/sending_06.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_02.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_03.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_04.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_05.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_06.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_07.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/wallet_setup_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/wallet_setup_08.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/troubleshooting_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/troubleshooting_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/troubleshooting_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/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/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/validating_viewing_01.png -------------------------------------------------------------------------------- /docs/src/guides/collecting/images/validating_viewing_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robotchangzhang/ord/HEAD/docs/src/guides/collecting/images/validating_viewing_02.png -------------------------------------------------------------------------------- /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 | } 8 | -------------------------------------------------------------------------------- /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(); 8 | } 9 | -------------------------------------------------------------------------------- /src/subcommand/index.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) fn run(options: Options) -> Result { 4 | let index = Index::open(&options)?; 5 | 6 | index.update()?; 7 | 8 | Ok(()) 9 | } 10 | -------------------------------------------------------------------------------- /.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 | /.idea/ 2 | /.vagrant 3 | /docs/build 4 | /fuzz/artifacts 5 | /fuzz/corpus 6 | /fuzz/coverage 7 | /fuzz/target 8 | /index.redb 9 | /ord.log 10 | /target 11 | /test-times.txt 12 | /tmp 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --ignore-case \ 11 | 'dbg!|fixme|todo|xxx' \ 12 | . 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ord.yaml: -------------------------------------------------------------------------------- 1 | # Example Config 2 | 3 | # prevent `ord server` from serving the content of the inscriptions below 4 | hidden: 5 | - 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 6 | - 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /templates/preview-audio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/preview-video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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/casey/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 | ./deploy/setup $CHAIN $DOMAIN 19 | -------------------------------------------------------------------------------- /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/casey/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/supply.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::supply::Output}; 2 | 3 | #[test] 4 | fn genesis() { 5 | assert_eq!( 6 | CommandBuilder::new("supply").output::(), 7 | Output { 8 | supply: 2099999997690000, 9 | first: 0, 10 | last: 2099999997689999, 11 | last_mined_in_block: 6929999 12 | } 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 10 | [output.html] 11 | cname = "docs.ordinals.com" 12 | default-theme = "coal" 13 | git-repository-url = "https://github.com/casey/ord" 14 | preferred-dark-theme = "coal" 15 | 16 | [output.linkcheck] 17 | -------------------------------------------------------------------------------- /src/arguments.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | #[clap(version)] 5 | pub(crate) struct Arguments { 6 | #[clap(flatten)] 7 | pub(crate) options: Options, 8 | #[clap(subcommand)] 9 | pub(crate) subcommand: Subcommand, 10 | } 11 | 12 | impl Arguments { 13 | pub(crate) fn run(self) -> Result { 14 | self.subcommand.run(self.options) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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() -> Result { 9 | let mut starting_sats = Vec::new(); 10 | for sat in Epoch::STARTING_SATS { 11 | starting_sats.push(sat); 12 | } 13 | 14 | print_json(Output { starting_sats })?; 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /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 | .output::(); 11 | 12 | assert!(output.address.is_valid_for_network(Network::Bitcoin)); 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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) -> Result { 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 | print_json(Output { address })?; 14 | 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /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 | #[clap(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) -> Result { 16 | print_json(Output { 17 | object: self.object, 18 | })?; 19 | Ok(()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | %% if !&self.inscriptions.is_empty() { 2 |

Latest Inscriptions

3 |
4 | %% for id in &self.inscriptions { 5 | {{Iframe::thumbnail(*id)}} 6 | %% } 7 |
8 | 9 | %% } 10 |

Latest Blocks

11 |
    12 | %% for hash in &self.blocks { 13 |
  1. {{hash}}
  2. 14 | %% } 15 |
16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/update-ord-dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | mkdir -p /etc/systemd/system/ord-dev.service.d 6 | 7 | cp /etc/systemd/system/{ord,ord-dev}.service.d/override.conf 8 | 9 | source ~/.cargo/env 10 | 11 | cargo build --release 12 | 13 | if [[ -f /usr/local/bin/ord-dev ]]; then 14 | mv /usr/local/bin/ord-dev{,.bak} 15 | fi 16 | 17 | cp target/release/ord /usr/local/bin/ord-dev 18 | 19 | cp deploy/ord-dev.service /etc/systemd/system/ 20 | systemctl daemon-reload 21 | systemctl restart ord-dev 22 | -------------------------------------------------------------------------------- /src/subcommand/wallet/restore.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Restore { 5 | #[clap(help = "Restore wallet from ")] 6 | mnemonic: Mnemonic, 7 | #[clap( 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) -> Result { 17 | initialize_wallet(&options, self.mnemonic.to_seed(self.passphrase))?; 18 | 19 | Ok(()) 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() -> Result { 12 | let mut last = 0; 13 | 14 | loop { 15 | if Height(last + 1).subsidy() == 0 { 16 | break; 17 | } 18 | last += 1; 19 | } 20 | 21 | print_json(Output { 22 | supply: Sat::SUPPLY, 23 | first: 0, 24 | last: Sat::SUPPLY - 1, 25 | last_mined_in_block: last, 26 | })?; 27 | 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /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) -> Result { 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 | print_json(outputs)?; 22 | 23 | Ok(()) 24 | } 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 | -------------------------------------------------------------------------------- /tests/wallet/cardinals.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | ord::subcommand::wallet::{cardinals::Cardinal, 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 | .output::>(); 16 | 17 | let cardinal_outputs = CommandBuilder::new("wallet cardinals") 18 | .rpc_server(&rpc_server) 19 | .output::>(); 20 | 21 | assert_eq!(all_outputs.len() - cardinal_outputs.len(), 1); 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 suffix(self) -> &'static str { 21 | match self { 22 | Self::Confirmed(_) => "", 23 | Self::Expected(_) => " (expected)", 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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/casey/ord" 8 | repository = "https://github.com/casey/ord" 9 | 10 | [dependencies] 11 | bitcoin = { version = "0.29.1", 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.16.5" 17 | reqwest = { version = "0.11.10", features = ["blocking"] } 18 | serde = { version = "1", features = ["derive"] } 19 | serde_json = "1.0.0" 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/subcommand/find.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Find { 5 | #[clap(help = "Find output and offset of .")] 6 | sat: Sat, 7 | } 8 | 9 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 10 | pub struct Output { 11 | pub satpoint: SatPoint, 12 | } 13 | 14 | impl Find { 15 | pub(crate) fn run(self, options: Options) -> Result { 16 | let index = Index::open(&options)?; 17 | 18 | index.update()?; 19 | 20 | match index.find(self.sat.0)? { 21 | Some(satpoint) => { 22 | print_json(Output { satpoint })?; 23 | Ok(()) 24 | } 25 | None => Err(anyhow!("sat has not been mined as of index height")), 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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.asm()}}
14 |
text
{{String::from_utf8_lossy(self.input.script_sig.as_bytes())}}
15 | %% } 16 |
17 | -------------------------------------------------------------------------------- /src/index/rtx.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) struct Rtx<'a>(pub(crate) redb::ReadTransaction<'a>); 4 | 5 | impl Rtx<'_> { 6 | pub(crate) fn height(&self) -> Result> { 7 | Ok( 8 | self 9 | .0 10 | .open_table(HEIGHT_TO_BLOCK_HASH)? 11 | .range(0..)? 12 | .rev() 13 | .next() 14 | .map(|(height, _hash)| Height(height.value())), 15 | ) 16 | } 17 | 18 | pub(crate) fn block_count(&self) -> Result { 19 | Ok( 20 | self 21 | .0 22 | .open_table(HEIGHT_TO_BLOCK_HASH)? 23 | .range(0..)? 24 | .rev() 25 | .next() 26 | .map(|(height, _hash)| height.value() + 1) 27 | .unwrap_or(0), 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/subcommand/subsidy.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Subsidy { 5 | #[clap(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) -> Result { 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 | print_json(Output { 27 | first: first.0, 28 | subsidy, 29 | name: first.name(), 30 | })?; 31 | 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.29.1", features = ["rand"] } 19 | libfuzzer-sys = "0.4" 20 | ord = { path = ".." } 21 | 22 | [[bin]] 23 | name = "transaction-builder" 24 | path = "fuzz_targets/transaction_builder.rs" 25 | test = false 26 | doc = false 27 | 28 | [patch.crates-io] 29 | jsonrpc = { git = "https://github.com/apoelstra/rust-jsonrpc.git", rev = "64b58797dd517c4de0cec769ff5652220801fe18" } 30 | redb = { git = "https://github.com/casey/redb.git", branch = "ord" } 31 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | [Introduction](introduction.md) 3 | - [Overview](overview.md) 4 | - [Digital Artifacts](digital-artifacts.md) 5 | - [Inscriptions](inscriptions.md) 6 | - [FAQ](faq.md) 7 | - [Contributing](contributing.md) 8 | - [Donate](donate.md) 9 | - [Guides](guides.md) 10 | - [Explorer](guides/explorer.md) 11 | - [Inscriptions](guides/inscriptions.md) 12 | - [Sat Hunting](guides/sat-hunting.md) 13 | - [Collecting](guides/collecting.md) 14 | - [Sparrow Wallet](guides/collecting/sparrow-wallet.md) 15 | - [Moderation](guides/moderation.md) 16 | - [Bounties](bounties.md) 17 | - [Bounty 0: 100,000 sats Claimed!](bounty/0.md) 18 | - [Bounty 1: 200,000 sats Claimed!](bounty/1.md) 19 | - [Bounty 2: 300,000 sats Claimed!](bounty/2.md) 20 | - [Bounty 3: 400,000 sats](bounty/3.md) 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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) -> Result { 9 | let index = Index::open(&options)?; 10 | index.update()?; 11 | 12 | let inscription_outputs = index 13 | .get_inscriptions(None)? 14 | .keys() 15 | .map(|satpoint| satpoint.outpoint) 16 | .collect::>(); 17 | 18 | let mut balance = 0; 19 | for (outpoint, amount) in index.get_unspent_outputs(Wallet::load(&options)?)? { 20 | if !inscription_outputs.contains(&outpoint) { 21 | balance += amount.to_sat() 22 | } 23 | } 24 | 25 | print_json(Output { cardinal: balance })?; 26 | 27 | Ok(()) 28 | } 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 PreviewImageHtml { 10 | pub(crate) inscription_id: InscriptionId, 11 | } 12 | 13 | #[derive(boilerplate::Boilerplate)] 14 | pub(crate) struct PreviewPdfHtml { 15 | pub(crate) inscription_id: InscriptionId, 16 | } 17 | 18 | #[derive(boilerplate::Boilerplate)] 19 | pub(crate) struct PreviewTextHtml<'a> { 20 | pub(crate) text: &'a str, 21 | } 22 | 23 | #[derive(boilerplate::Boilerplate)] 24 | pub(crate) struct PreviewUnknownHtml; 25 | 26 | #[derive(boilerplate::Boilerplate)] 27 | pub(crate) struct PreviewVideoHtml { 28 | pub(crate) inscription_id: InscriptionId, 29 | } 30 | -------------------------------------------------------------------------------- /deploy/ord-dev.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | After=network.target 3 | Description=Ord Dev 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-dev \ 12 | --bitcoin-data-dir /var/lib/bitcoind \ 13 | --chain ${CHAIN} \ 14 | --data-dir /var/lib/ord-dev \ 15 | --index-sats \ 16 | server \ 17 | --http-port 8080 18 | Group=ord 19 | MemoryDenyWriteExecute=true 20 | NoNewPrivileges=true 21 | PrivateDevices=true 22 | PrivateTmp=true 23 | ProtectHome=true 24 | ProtectSystem=full 25 | Restart=on-failure 26 | RestartSec=5s 27 | StateDirectory=ord-dev 28 | StateDirectoryMode=0700 29 | TimeoutStopSec=10m 30 | Type=simple 31 | User=ord 32 | WorkingDirectory=/var/lib/ord-dev 33 | -------------------------------------------------------------------------------- /templates/preview-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /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").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 | .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(); 32 | } 33 | -------------------------------------------------------------------------------- /src/subcommand/wallet/create.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Serialize)] 4 | struct Output { 5 | mnemonic: Mnemonic, 6 | passphrase: Option, 7 | } 8 | 9 | #[derive(Debug, Parser)] 10 | pub(crate) struct Create { 11 | #[clap( 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) -> Result { 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 | print_json(Output { 29 | mnemonic, 30 | passphrase: Some(self.passphrase), 31 | })?; 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Deserialize, Default, PartialEq, Debug)] 4 | pub(crate) struct Config { 5 | pub(crate) hidden: HashSet, 6 | } 7 | 8 | impl Config { 9 | pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { 10 | self.hidden.contains(&inscription_id) 11 | } 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | 18 | #[test] 19 | fn inscriptions_can_be_hidden() { 20 | let a = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" 21 | .parse::() 22 | .unwrap(); 23 | 24 | let b = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi1" 25 | .parse::() 26 | .unwrap(); 27 | 28 | let config = Config { 29 | hidden: iter::once(a).collect(), 30 | }; 31 | 32 | assert!(config.is_hidden(a)); 33 | assert!(!config.is_hidden(b)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/subcommand/wallet/transactions.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Transactions { 5 | #[clap(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) -> Result { 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 | print_json(output)?; 34 | 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /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/casey/ord/blob/master/bip.mediawiki) for the 11 | technical details, and the [ord repo](https://github.com/casey/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/wallet/cardinals.rs: -------------------------------------------------------------------------------- 1 | use {super::*, crate::wallet::Wallet, std::collections::BTreeSet}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct Cardinal { 5 | pub output: OutPoint, 6 | pub amount: u64, 7 | } 8 | 9 | pub(crate) fn run(options: Options) -> Result { 10 | let index = Index::open(&options)?; 11 | index.update()?; 12 | 13 | let inscribed_utxos = index 14 | .get_inscriptions(None)? 15 | .keys() 16 | .map(|satpoint| satpoint.outpoint) 17 | .collect::>(); 18 | 19 | let cardinal_utxos = index 20 | .get_unspent_outputs(Wallet::load(&options)?)? 21 | .iter() 22 | .filter_map(|(output, amount)| { 23 | if inscribed_utxos.contains(output) { 24 | None 25 | } else { 26 | Some(Cardinal { 27 | output: *output, 28 | amount: amount.to_sat(), 29 | }) 30 | } 31 | }) 32 | .collect::>(); 33 | 34 | print_json(cardinal_utxos)?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /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").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").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/subcommand/traits.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Traits { 5 | #[clap(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) -> Result { 25 | print_json(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 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/index.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn custom_index_path() { 5 | let rpc_server = test_bitcoincore_rpc::spawn(); 6 | rpc_server.mine_blocks(1); 7 | 8 | let tempdir = TempDir::new().unwrap(); 9 | 10 | let index_path = tempdir.path().join("foo.redb"); 11 | 12 | CommandBuilder::new(format!("--index {} index", index_path.display())) 13 | .rpc_server(&rpc_server) 14 | .run(); 15 | 16 | assert!(index_path.is_file()) 17 | } 18 | 19 | #[test] 20 | fn re_opening_database_does_not_trigger_schema_check() { 21 | let rpc_server = test_bitcoincore_rpc::spawn(); 22 | rpc_server.mine_blocks(1); 23 | 24 | let tempdir = TempDir::new().unwrap(); 25 | 26 | let index_path = tempdir.path().join("foo.redb"); 27 | 28 | CommandBuilder::new(format!("--index {} index", index_path.display())) 29 | .rpc_server(&rpc_server) 30 | .run(); 31 | 32 | assert!(index_path.is_file()); 33 | 34 | CommandBuilder::new(format!("--index {} index", index_path.display())) 35 | .rpc_server(&rpc_server) 36 | .run(); 37 | } 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 dist 23 | cp \ 24 | $EXECUTABLE \ 25 | Cargo.lock \ 26 | Cargo.toml \ 27 | LICENSE \ 28 | README.md \ 29 | $DIST 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 | -------------------------------------------------------------------------------- /templates/block.html: -------------------------------------------------------------------------------- 1 |

Block {{ self.height }}

2 |
3 |
hash
{{self.hash}}
4 |
target
{{self.target}}
5 |
timestamp
6 |
size
{{self.block.size()}}
7 |
weight
{{self.block.weight()}}
8 | %% if self.height.0 > 0 { 9 |
previous blockhash
{{self.block.header.prev_blockhash}}
10 | %% } 11 |
12 |
13 | %% if let Some(prev_height) = self.height.n().checked_sub(1) { 14 | 15 | %% } else { 16 | prev 17 | %% } 18 | %% if self.height < self.best_height { 19 | 20 | %% } else { 21 | next 22 | %% } 23 |
24 |

{{"Transaction".tally(self.block.txdata.len())}}

25 |
    26 | %% for tx in &self.block.txdata { 27 | %% let txid = tx.txid(); 28 |
  • {{txid}}
  • 29 | %% } 30 |
31 | -------------------------------------------------------------------------------- /tests/find.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::find::Output}; 2 | 3 | #[test] 4 | fn find_command_returns_satpoint_for_sat() { 5 | let rpc_server = test_bitcoincore_rpc::spawn(); 6 | assert_eq!( 7 | CommandBuilder::new("--index-sats find 0") 8 | .rpc_server(&rpc_server) 9 | .output::(), 10 | Output { 11 | satpoint: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0" 12 | .parse() 13 | .unwrap() 14 | } 15 | ); 16 | } 17 | 18 | #[test] 19 | fn unmined_sat() { 20 | let rpc_server = test_bitcoincore_rpc::spawn(); 21 | CommandBuilder::new("--index-sats find 5000000000") 22 | .rpc_server(&rpc_server) 23 | .expected_stderr("error: sat has not been mined as of index height\n") 24 | .expected_exit_code(1) 25 | .run(); 26 | } 27 | 28 | #[test] 29 | fn no_satoshi_index() { 30 | let rpc_server = test_bitcoincore_rpc::spawn(); 31 | CommandBuilder::new("find 0") 32 | .rpc_server(&rpc_server) 33 | .expected_stderr("error: find requires index created with `--index-sats` flag\n") 34 | .expected_exit_code(1) 35 | .run(); 36 | } 37 | -------------------------------------------------------------------------------- /src/subcommand/info.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub(crate) struct Info { 5 | #[clap(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) -> Result { 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 | print_json(output)?; 36 | } else { 37 | print_json(info)?; 38 | } 39 | 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/src/guides/collecting.md: -------------------------------------------------------------------------------- 1 | Collecting 2 | ========== 3 | 4 | Currently, [ord](https://github.com/casey/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 | -------------------------------------------------------------------------------- /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 | .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 | .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 | .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 | .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 | } 9 | 10 | pub(crate) fn run(options: Options) -> Result { 11 | let index = Index::open(&options)?; 12 | index.update()?; 13 | 14 | let inscriptions = index.get_inscriptions(None)?; 15 | let unspent_outputs = index.get_unspent_outputs(Wallet::load(&options)?)?; 16 | 17 | let explorer = match options.chain() { 18 | Chain::Mainnet => "https://ordinals.com/inscription/", 19 | Chain::Regtest => "http://localhost/inscription/", 20 | Chain::Signet => "https://signet.ordinals.com/inscription/", 21 | Chain::Testnet => "https://testnet.ordinals.com/inscription/", 22 | }; 23 | 24 | let mut output = Vec::new(); 25 | 26 | for (location, inscription) in inscriptions { 27 | if unspent_outputs.contains_key(&location.outpoint) { 28 | output.push(Output { 29 | location, 30 | inscription, 31 | explorer: format!("{explorer}{inscription}"), 32 | }); 33 | } 34 | } 35 | 36 | print_json(&output)?; 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | .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 | .output::>(); 34 | 35 | assert_eq!(output[0].output, outpoint); 36 | assert_eq!(output[0].amount, amount); 37 | } 38 | -------------------------------------------------------------------------------- /templates/output.html: -------------------------------------------------------------------------------- 1 |

Output {{self.outpoint}}

2 |
3 | %% if !self.inscriptions.is_empty() { 4 |
inscriptions
5 |
6 | %% for inscription in &self.inscriptions { 7 | {{Iframe::thumbnail(*inscription)}} 8 | %% } 9 |
10 | %% } 11 |
value
{{ self.output.value }}
12 |
script pubkey
{{ self.output.script_pubkey.asm() }}
13 | %% if let Ok(address) = self.chain.address_from_script(&self.output.script_pubkey ) { 14 |
address
{{ address }}
15 | %% } 16 |
transaction
{{ self.outpoint.txid }}
17 |
18 | %% if let Some(list) = &self.list { 19 | %% match list { 20 | %% List::Unspent(ranges) => { 21 |

{{"Sat Range".tally(ranges.len())}}

22 |
    23 | %% for (start, end) in ranges { 24 | %% if end - start == 1 { 25 |
  • {{start}}
  • 26 | %% } else { 27 |
  • {{start}}–{{end}}
  • 28 | %% } 29 | %% } 30 |
31 | %% } 32 | %% List::Spent => { 33 |

Output has been spent.

34 | %% } 35 | %% } 36 | %% } 37 | -------------------------------------------------------------------------------- /templates/sat.html: -------------------------------------------------------------------------------- 1 |

Sat {{ self.sat.n() }}

2 |
3 |
decimal
{{ self.sat.decimal() }}
4 |
degree
{{ self.sat.degree() }}
5 |
percentile
{{ self.sat.percentile() }}
6 |
name
{{ self.sat.name() }}
7 |
cycle
{{ self.sat.cycle() }}
8 |
epoch
{{ self.sat.epoch() }}
9 |
period
{{ self.sat.period() }}
10 |
block
{{ self.sat.height() }}
11 |
offset
{{ self.sat.third() }}
12 |
rarity
{{ self.sat.rarity() }}
13 |
timestamp
{{self.blocktime.suffix()}}
14 | %% if let Some((inscription)) = &self.inscription { 15 |
inscription
{{ Iframe::thumbnail(*inscription) }}
16 | %% } 17 | %% if let Some(satpoint) = self.satpoint { 18 |
location
{{ satpoint }}
19 | %% } 20 |
21 |
22 | %% if self.sat.n() > 0 { 23 | 24 | %% } else { 25 | prev 26 | %% } 27 | %% if self.sat < Sat::LAST { 28 | 29 | %% } else { 30 | next 31 | %% } 32 |
33 | -------------------------------------------------------------------------------- /templates/transaction.html: -------------------------------------------------------------------------------- 1 |

Transaction {{self.txid}}

2 | %% if let Some(id) = self.inscription { 3 |

Inscription Geneses

4 |
5 | {{ Iframe::thumbnail(id) }} 6 |
7 | %% } 8 | %% if let Some(blockhash) = self.blockhash { 9 |
10 |
block
11 |
{{ blockhash }}
12 |
13 | %% } 14 |

{{"Input".tally(self.transaction.input.len())}}

15 | 20 |

{{"Output".tally(self.transaction.output.len())}}

21 |
    22 | %% for (vout, output) in self.transaction.output.iter().enumerate() { 23 | %% let outpoint = OutPoint::new(self.txid, vout as u32); 24 |
  • 25 | 26 | {{ outpoint }} 27 | 28 |
    29 |
    value
    {{ output.value }}
    30 |
    script pubkey
    {{ output.script_pubkey.asm() }}
    31 | %% if let Ok(address) = self.chain.address_from_script(&output.script_pubkey) { 32 |
    address
    {{ address }}
    33 | %% } 34 |
    35 |
  • 36 | %% } 37 |
38 | -------------------------------------------------------------------------------- /tests/subsidy.rs: -------------------------------------------------------------------------------- 1 | use {super::*, ord::subcommand::subsidy::Output}; 2 | 3 | #[test] 4 | fn genesis() { 5 | assert_eq!( 6 | CommandBuilder::new("subsidy 0").output::(), 7 | Output { 8 | first: 0, 9 | subsidy: 5000000000, 10 | name: "nvtdijuwxlp".into(), 11 | } 12 | ); 13 | } 14 | 15 | #[test] 16 | fn second_block() { 17 | assert_eq!( 18 | CommandBuilder::new("subsidy 1").output::(), 19 | Output { 20 | first: 5000000000, 21 | subsidy: 5000000000, 22 | name: "nvtcsezkbth".into(), 23 | } 24 | ); 25 | } 26 | 27 | #[test] 28 | fn second_to_last_block_with_subsidy() { 29 | assert_eq!( 30 | CommandBuilder::new("subsidy 6929998").output::(), 31 | Output { 32 | first: 2099999997689998, 33 | subsidy: 1, 34 | name: "b".into(), 35 | } 36 | ); 37 | } 38 | 39 | #[test] 40 | fn last_block_with_subsidy() { 41 | assert_eq!( 42 | CommandBuilder::new("subsidy 6929999").output::(), 43 | Output { 44 | first: 2099999997689999, 45 | subsidy: 1, 46 | name: "a".into(), 47 | } 48 | ); 49 | } 50 | 51 | #[test] 52 | fn first_block_without_subsidy() { 53 | CommandBuilder::new("subsidy 6930000") 54 | .expected_stderr("error: block 6930000 has no subsidy\n") 55 | .expected_exit_code(1) 56 | .run(); 57 | } 58 | -------------------------------------------------------------------------------- /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