Skip to content

Commit

Permalink
Batch inscriptions (ordinals#2504)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph committed Oct 24, 2023
1 parent 676c212 commit c7df8bb
Show file tree
Hide file tree
Showing 24 changed files with 2,152 additions and 698 deletions.
34 changes: 34 additions & 0 deletions batch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# example batch file

# there are two modes:
# - `separate-outputs`: place all inscriptions in separate postage-sized outputs
# - `shared-output`: place inscriptions in a single output separated by postage
mode: separate-outputs

# parent inscription:
parent: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0

# inscriptions to inscribe
#
# each inscription has the following fields:
#
# `inscription`: path to inscription contents
# `metadata`: inscription metadata (optional)
# `metaprotocol`: inscription metaprotocol (optional)
inscriptions:
- file: mango.avif
metadata:
title: Delicious Mangos
description: >
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam semper,
ligula ornare laoreet tincidunt, odio nisi euismod tortor, vel blandit
metus est et odio. Nullam venenatis, urna et molestie vestibulum, orci
mi efficitur risus, eu malesuada diam lorem sed velit. Nam fermentum
dolor et luctus euismod.
- file: token.json
metaprotocol: brc-20

- file: tulip.png
metadata:
author: Satoshi Nakamoto
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Guides](guides.md)
- [Explorer](guides/explorer.md)
- [Inscriptions](guides/inscriptions.md)
- [Batch Inscribing](guides/batch-inscribing.md)
- [Sat Hunting](guides/sat-hunting.md)
- [Collecting](guides/collecting.md)
- [Sparrow Wallet](guides/collecting/sparrow-wallet.md)
Expand Down
24 changes: 18 additions & 6 deletions docs/src/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,30 +52,40 @@ Let's imagine a transaction with three inputs and two outputs. The inputs are
on the left of the arrow and the outputs are on the right, all labeled with
their values:

[2] [1] [3] → [4] [2]
```
[2] [1] [3] → [4] [2]
```

Now let's label the same transaction with the ordinal numbers of the satoshis
that each input contains, and question marks for each output slot. Ordinal
numbers are large, so let's use letters to represent them:

[a b] [c] [d e f] → [? ? ? ?] [? ?]
```
[a b] [c] [d e f] → [? ? ? ?] [? ?]
```

To figure out which satoshi goes to which output, go through the input satoshis
in order and assign each to a question mark:

[a b] [c] [d e f] → [a b c d] [e f]
```
[a b] [c] [d e f] → [a b c d] [e f]
```

What about fees, you might ask? Good question! Let's imagine the same
transaction, this time with a two satoshi fee. Transactions with fees send more
satoshis in the inputs than are received by the outputs, so to make our
transaction into one that pays fees, we'll remove the second output:

[2] [1] [3] → [4]
```
[2] [1] [3] → [4]
```

The satoshis <var>e</var> and <var>f</var> now have nowhere to go in the
outputs:

[a b] [c] [d e f] → [a b c d]
```
[a b] [c] [d e f] → [a b c d]
```

So they go to the miner who mined the block as fees. [The
BIP](https://github.com/ordinals/ord/blob/master/bip.mediawiki) has the details,
Expand All @@ -84,7 +94,9 @@ coinbase transaction, and are ordered how their corresponding transactions are
ordered in the block. The coinbase transaction of the block might look like
this:

[SUBSIDY] [e f] → [SUBSIDY e f]
```
[SUBSIDY] [e f] → [SUBSIDY e f]
```

Where can I find the nitty-gritty details?
------------------------------------------
Expand Down
22 changes: 22 additions & 0 deletions docs/src/guides/batch-inscribing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Batch Inscribing
================

Multiple inscriptions can be created inscriptions at the same time using the
[pointer field](./../inscriptions/pointer.md). This is especially helpful for
collections, or other cases when multiple inscriptions should share the same
parent, since the parent can passed into a reveal transaction that creates
multiple children.

To create a batch inscription using a batchfile in `batch.yaml`, run the
following command:

```bash
ord wallet inscribe --fee-rate 21 --batch batch.yaml
```

Example `batch.yaml`
--------------------

```yaml
{{#include ../../../batch.yaml}}
```
1 change: 1 addition & 0 deletions docs/src/guides/inscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ This guide covers:
5. Creating inscriptions with `ord wallet inscribe`
6. Sending inscriptions with `ord wallet send`
7. Receiving inscriptions with `ord wallet receive`
8. Batch inscribing with `ord wallet inscribe --batch`

Getting Help
------------
Expand Down
2 changes: 1 addition & 1 deletion src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,7 @@ impl Index {

pub(crate) fn get_inscriptions(
&self,
utxos: BTreeMap<OutPoint, Amount>,
utxos: &BTreeMap<OutPoint, Amount>,
) -> Result<BTreeMap<SatPoint, InscriptionId>> {
let rtx = self.database.begin_read()?;

Expand Down
60 changes: 59 additions & 1 deletion src/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl Inscription {
chain: Chain,
path: impl AsRef<Path>,
parent: Option<InscriptionId>,
pointer: Option<u64>,
metaprotocol: Option<String>,
metadata: Option<Vec<u8>>,
) -> Result<Self, Error> {
Expand All @@ -72,10 +73,21 @@ impl Inscription {
metadata,
metaprotocol: metaprotocol.map(|metaprotocol| metaprotocol.into_bytes()),
parent: parent.map(|id| id.parent_value()),
pointer: pointer.map(Self::pointer_value),
..Default::default()
})
}

fn pointer_value(pointer: u64) -> Vec<u8> {
let mut bytes = pointer.to_le_bytes().to_vec();

while bytes.last().copied() == Some(0) {
bytes.pop();
}

bytes
}

pub(crate) fn append_reveal_script_to_builder(
&self,
mut builder: script::Builder,
Expand Down Expand Up @@ -126,10 +138,29 @@ impl Inscription {
builder.push_opcode(opcodes::all::OP_ENDIF)
}

#[cfg(test)]
pub(crate) fn append_reveal_script(&self, builder: script::Builder) -> ScriptBuf {
self.append_reveal_script_to_builder(builder).into_script()
}

pub(crate) fn append_batch_reveal_script_to_builder(
inscriptions: &[Inscription],
mut builder: script::Builder,
) -> script::Builder {
for inscription in inscriptions {
builder = inscription.append_reveal_script_to_builder(builder);
}

builder
}

pub(crate) fn append_batch_reveal_script(
inscriptions: &[Inscription],
builder: script::Builder,
) -> ScriptBuf {
Inscription::append_batch_reveal_script_to_builder(inscriptions, builder).into_script()
}

pub(crate) fn media(&self) -> Media {
if self.body.is_none() {
return Media::Unknown;
Expand Down Expand Up @@ -239,7 +270,7 @@ impl Inscription {

#[cfg(test)]
mod tests {
use super::*;
use {super::*, std::io::Write};

#[test]
fn reveal_script_chunks_body() {
Expand Down Expand Up @@ -631,4 +662,31 @@ mod tests {
envelope(&[b"ord", &[2], &[1, 2, 3]]),
);
}

#[test]
fn pointer_value() {
let mut file = tempfile::Builder::new().suffix(".txt").tempfile().unwrap();

write!(file, "foo").unwrap();

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, None, None, None).unwrap();

assert_eq!(inscription.pointer, None);

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, Some(0), None, None).unwrap();

assert_eq!(inscription.pointer, Some(Vec::new()));

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, Some(1), None, None).unwrap();

assert_eq!(inscription.pointer, Some(vec![1]));

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, Some(256), None, None).unwrap();

assert_eq!(inscription.pointer, Some(vec![0, 1]));
}
}
4 changes: 2 additions & 2 deletions src/sat_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use super::*;

#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord)]
pub struct SatPoint {
pub(crate) outpoint: OutPoint,
pub(crate) offset: u64,
pub outpoint: OutPoint,
pub offset: u64,
}

impl Display for SatPoint {
Expand Down
3 changes: 2 additions & 1 deletion src/subcommand/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,13 @@ impl Preview {
options: options.clone(),
subcommand: Subcommand::Wallet(super::wallet::Wallet::Inscribe(
super::wallet::inscribe::Inscribe {
batch: None,
cbor_metadata: None,
commit_fee_rate: None,
destination: None,
dry_run: false,
fee_rate: FeeRate::try_from(1.0).unwrap(),
file,
file: Some(file),
json_metadata: None,
metaprotocol: None,
no_backup: true,
Expand Down
4 changes: 2 additions & 2 deletions src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ impl Wallet {
}
}

fn get_change_address(client: &Client, options: &Options) -> Result<Address> {
fn get_change_address(client: &Client, chain: Chain) -> Result<Address> {
Ok(
client
.call::<Address<NetworkUnchecked>>("getrawchangeaddress", &["bech32m".into()])
.context("could not get change addresses from wallet")?
.require_network(options.chain().network())?,
.require_network(chain.network())?,
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub(crate) fn run(options: Options) -> SubcommandResult {
let unspent_outputs = index.get_unspent_outputs(Wallet::load(&options)?)?;

let inscription_outputs = index
.get_inscriptions(unspent_outputs)?
.get_inscriptions(&unspent_outputs)?
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<BTreeSet<OutPoint>>();
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/cardinals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub(crate) fn run(options: Options) -> SubcommandResult {
let unspent_outputs = index.get_unspent_outputs(Wallet::load(&options)?)?;

let inscribed_utxos = index
.get_inscriptions(unspent_outputs.clone())?
.get_inscriptions(&unspent_outputs)?
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<BTreeSet<OutPoint>>();
Expand Down
Loading

0 comments on commit c7df8bb

Please sign in to comment.