Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mint endpoint #1

Open
wants to merge 4 commits into
base: carbonadord
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 39 additions & 36 deletions batch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,74 +8,77 @@
mode: separate-outputs

# parent inscription:
parent: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0
# parent: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0

# postage for each inscription:
postage: 12345
postage: 888

# allow reinscribing
reinscribe: true
reinscribe: false

# sat to inscribe on, can only be used with `same-sat`:
# sat: 5000000000

# rune to etch (optional)
etching:
# rune name
rune: THE•BEST•RUNE
rune: MEOW.MEOW.MEOW
# allow subdividing super-unit into `10^divisibility` sub-units
divisibility: 2
divisibility: 8
# premine
premine: 1000.00
premine: 7920000000.00000000
# total supply, must be equal to `premine + terms.cap * terms.amount`
supply: 10000.00
supply: 8000000000.00000000
# currency symbol
symbol: $
symbol: 😸
# mint terms (optional)
terms:
# amount per mint
amount: 100.00
amount: 800.00000000
# maximum number of mints
cap: 90
cap: 100000 # H: 4B / 8K
# mint start and end absolute block height (optional)
height:
start: 840000
end: 850000
end: 876208
# mint start and end block height relative to etching height (optional)
offset:
start: 1000
end: 9000
# offset:
# start: 1000
# end: 9000
# future runes protocol changes may be opt-in. this may be for a variety of
# reasons, including that they make light client validation harder, or simply
# because they are too degenerate.
#
# setting `turbo` to `true` opts in to these future protocol changes,
# whatever they may be.
turbo: true
turbo: true # H: Because it's a meme token, more features are better

# inscriptions to inscribe
inscriptions:
# path to inscription content
- file: mango.avif
# inscription to delegate content to (optional)
delegate: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0
# destination (optional, if no destination is specified a new wallet change address will be used)
destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
# inscription metadata (optional)
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.
# # path to inscription content
# - file: mango.avif
# # inscription to delegate content to (optional)
# delegate: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0
# # destination (optional, if no destination is specified a new wallet change address will be used)
# destination: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
# # inscription metadata (optional)
# 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
# inscription metaprotocol (optional)
metaprotocol: DOPEPROTOCOL-42069
# - file: token.json
# # inscription metaprotocol (optional)
# metaprotocol: DOPEPROTOCOL-42069

- file: tulip.png
destination: bc1pdqrcrxa8vx6gy75mfdfj84puhxffh4fq46h3gkp6jxdd0vjcsdyspfxcv6
- file: diba192.png
destination: bc1pwnaljnawhxm0g5jamc898uj0whklzf55qw72zsxrrp4e6aggrvaqv0tssk
metadata:
author: Satoshi Nakamoto
author: BitMask Foundation
caption: MEOW etch - 1% Runes, 99% RGB - 99% of Runes burnt to Satoshi's address

# H: batchfile must contain at least one inscription
4 changes: 2 additions & 2 deletions crates/ordinals/src/rune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ impl Rune {
pub fn first_rune_height(network: Network) -> u32 {
SUBSIDY_HALVING_INTERVAL
* match network {
Network::Bitcoin => 4,
Network::Bitcoin => 3,
Network::Regtest => 0,
Network::Signet => 0,
Network::Testnet => 12,
Network::Testnet => 11,
_ => 0,
}
}
Expand Down
Binary file added diba192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use {
accept_encoding::AcceptEncoding,
accept_json::AcceptJson,
error::{OptionExt, ServerError, ServerResult},
wallet::mint,
},
super::*,
crate::templates::{
Expand Down Expand Up @@ -41,6 +42,8 @@ use {

pub(crate) use server_config::ServerConfig;

use crate::subcommand::wallet::mint::MintRequest;

mod accept_encoding;
mod accept_json;
mod error;
Expand Down Expand Up @@ -254,6 +257,7 @@ impl Server {
.route("/rare.txt", get(Self::rare_txt))
.route("/rune/:rune", get(Self::rune))
.route("/runes", get(Self::runes))
.route("/runes/mint", post(Self::runes_mint))
.route("/runes/:page", get(Self::runes_paginated))
.route("/runes/balances", get(Self::runes_balances))
.route("/sat/:sat", get(Self::sat))
Expand Down Expand Up @@ -712,6 +716,15 @@ impl Server {
.await
}

async fn runes_mint(
// Extension(server_config): Extension<Arc<ServerConfig>>,
// Extension(index): Extension<Arc<Index>>,
// AcceptJson(accept_json): AcceptJson,
Json(mint_req): Json<MintRequest>,
) -> ServerResult {
task::block_in_place(|| Ok(Json(mint::Mint::post(mint_req)?).into_response()))
}

async fn runes_paginated(
Extension(server_config): Extension<Arc<ServerConfig>>,
Extension(index): Extension<Arc<Index>>,
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl WalletCommand {
Subcommand::Inscribe(inscribe) => inscribe.run(wallet),
Subcommand::Inscriptions => inscriptions::run(wallet),
Subcommand::Label => label::run(wallet),
Subcommand::Mint(mint) => mint.run(wallet),
Subcommand::Mint(_mint) => unreachable!("local mint unavailable"),
Subcommand::Outputs => outputs::run(wallet),
Subcommand::Receive(receive) => receive.run(wallet),
Subcommand::Resume(resume) => resume.run(wallet),
Expand Down
138 changes: 80 additions & 58 deletions src/subcommand/wallet/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,37 @@ pub(crate) struct Mint {
pub struct Output {
pub rune: SpacedRune,
pub pile: Pile,
pub mint: Txid,
pub mint: String,
}

#[derive(Deserialize)]
pub struct MintRequest {
rune: SpacedRune,
// destination: Address<NetworkUnchecked>,
// postage: u64,
}

impl Mint {
pub(crate) fn run(self, wallet: Wallet) -> SubcommandResult {
pub(crate) fn post(mint_req: MintRequest) -> Result<Output> {
let settings = Settings::load(Options::default())?;

let wallet = WalletConstructor::construct(
"ord".to_owned(),
true,
settings.clone(),
settings
.server_url()
.unwrap_or("http://127.0.0.1:80")
.parse::<Url>()
.context("invalid server URL")?,
)?;

ensure!(
wallet.has_rune_index(),
"`ord wallet mint` requires index created with `--index-runes` flag",
);

let rune = self.rune.rune;
let rune = mint_req.rune.rune;

let bitcoin_client = wallet.bitcoin_client();

Expand All @@ -39,24 +59,21 @@ impl Mint {
bail!("rune {rune} has not been etched");
};

let postage = self.postage.unwrap_or(TARGET_POSTAGE);
// let postage = Amount::from_sat(mint_req.postage);

let amount = rune_entry
.mintable(block_height + 1)
.map_err(|err| anyhow!("rune {rune} {err}"))?;

let chain = wallet.chain();
// let chain = wallet.chain();

let destination = match self.destination {
Some(destination) => destination.require_network(chain.network())?,
None => wallet.get_change_address()?,
};
// let destination = mint_req.destination.require_network(chain.network())?;

ensure!(
destination.script_pubkey().dust_value() < postage,
"postage below dust limit of {}sat",
destination.script_pubkey().dust_value().to_sat()
);
// ensure!(
// destination.script_pubkey().dust_value() < postage,
// "postage below dust limit of {}sat",
// destination.script_pubkey().dust_value().to_sat()
// );

let runestone = Runestone {
mint: Some(id),
Expand All @@ -65,54 +82,59 @@ impl Mint {

let script_pubkey = runestone.encipher();

ensure!(
script_pubkey.len() <= 82,
"runestone greater than maximum OP_RETURN size: {} > 82",
script_pubkey.len()
);

let unfunded_transaction = Transaction {
version: 2,
lock_time: LockTime::ZERO,
input: Vec::new(),
output: vec![
TxOut {
script_pubkey,
value: 0,
},
TxOut {
script_pubkey: destination.script_pubkey(),
value: postage.to_sat(),
},
],
};

wallet.lock_non_cardinal_outputs()?;

let unsigned_transaction =
fund_raw_transaction(bitcoin_client, self.fee_rate, &unfunded_transaction)?;

let signed_transaction = bitcoin_client
.sign_raw_transaction_with_wallet(&unsigned_transaction, None, None)?
.hex;

let signed_transaction = consensus::encode::deserialize(&signed_transaction)?;

assert_eq!(
Runestone::decipher(&signed_transaction),
Some(Artifact::Runestone(runestone)),
);

let transaction = bitcoin_client.send_raw_transaction(&signed_transaction)?;

Ok(Some(Box::new(Output {
rune: self.rune,
// Libre Relay exempts this
// ensure!(
// script_pubkey.len() <= 82,
// "runestone greater than maximum OP_RETURN size: {} > 82",
// script_pubkey.len()
// );

// let unfunded_transaction = Transaction {
// version: 2,
// lock_time: LockTime::ZERO,
// input: Vec::new(),
// output: vec![
// TxOut {
// script_pubkey,
// value: 0,
// },
// TxOut {
// script_pubkey: destination.script_pubkey(),
// value: mint_req.postage,
// },
// ],
// };

// unfunded_transaction.raw_hex();

// let psbt = consensus::encode::serialize_hex(&unfunded_transaction);

// wallet.lock_non_cardinal_outputs()?;

// let unsigned_transaction =
// fund_raw_transaction(bitcoin_client, self.fee_rate, &unfunded_transaction)?;

// let signed_transaction = bitcoin_client
// .sign_raw_transaction_with_wallet(&unsigned_transaction, None, None)?
// .hex;

// let signed_transaction = consensus::encode::deserialize(&signed_transaction)?;

// assert_eq!(
// Runestone::decipher(&signed_transaction),
// Some(Artifact::Runestone(runestone)),
// );

// let transaction = bitcoin_client.send_raw_transaction(&signed_transaction)?;

Ok(Output {
rune: mint_req.rune,
pile: Pile {
amount,
divisibility: rune_entry.divisibility,
symbol: rune_entry.symbol,
},
mint: transaction,
})))
mint: script_pubkey.to_hex_string(),
})
}
}
Loading