Skip to content

Commit

Permalink
Add ord wallet send (ordinals#305)
Browse files Browse the repository at this point in the history
  • Loading branch information
terror committed Aug 12, 2022
1 parent 6c6b354 commit 28a2362
Show file tree
Hide file tree
Showing 9 changed files with 357 additions and 104 deletions.
11 changes: 6 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use {
crate::{
arguments::Arguments, bytes::Bytes, epoch::Epoch, height::Height, index::Index, nft::Nft,
options::Options, ordinal::Ordinal, sat_point::SatPoint, subcommand::Subcommand,
options::Options, ordinal::Ordinal, purse::Purse, sat_point::SatPoint, subcommand::Subcommand,
},
anyhow::{anyhow, bail, Context, Error},
axum::{
Expand All @@ -12,13 +12,13 @@ use {
axum_server::Handle,
bdk::{
blockchain::rpc::{Auth, RpcBlockchain, RpcConfig},
blockchain::ConfigurableBlockchain,
blockchain::{Blockchain, ConfigurableBlockchain},
database::SqliteDatabase,
keys::bip39::{Language, Mnemonic},
template::Bip84,
wallet::AddressIndex::LastUnused,
wallet::{signer::SignOptions, AddressIndex::LastUnused},
wallet::{wallet_name_from_descriptor, SyncOptions},
KeychainKind,
KeychainKind, LocalUtxo,
},
bitcoin::{
blockdata::constants::COIN_VALUE,
Expand All @@ -32,7 +32,7 @@ use {
KeyPair, Secp256k1, XOnlyPublicKey,
},
util::key::PrivateKey,
Block, Network, OutPoint, Transaction, Txid,
Address, Block, Network, OutPoint, Transaction, Txid,
},
chrono::{DateTime, NaiveDateTime, Utc},
clap::Parser,
Expand Down Expand Up @@ -75,6 +75,7 @@ mod index;
mod nft;
mod options;
mod ordinal;
mod purse;
mod sat_point;
mod subcommand;

Expand Down
123 changes: 123 additions & 0 deletions src/purse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use super::*;

#[derive(Debug)]
pub(crate) struct Purse {
pub(crate) wallet: bdk::wallet::Wallet<SqliteDatabase>,
pub(crate) blockchain: RpcBlockchain,
}

impl Purse {
pub(crate) fn init(options: &Options) -> Result {
let path = data_dir()
.ok_or_else(|| anyhow!("Failed to retrieve data dir"))?
.join("ord");

if path.exists() {
return Err(anyhow!("Wallet already exists."));
}

fs::create_dir_all(&path)?;

let seed = Mnemonic::generate_in_with(&mut rand::thread_rng(), Language::English, 12)?;

fs::write(path.join("entropy"), seed.to_entropy())?;

let wallet = bdk::wallet::Wallet::new(
Bip84((seed.clone(), None), KeychainKind::External),
None,
options.network,
SqliteDatabase::new(
path
.join("wallet.sqlite")
.to_str()
.ok_or_else(|| anyhow!("Failed to convert path to str"))?
.to_string(),
),
)?;

wallet.sync(
&RpcBlockchain::from_config(&RpcConfig {
url: options.rpc_url(),
auth: Auth::Cookie {
file: options.cookie_file()?,
},
network: options.network,
wallet_name: wallet_name_from_descriptor(
Bip84((seed, None), KeychainKind::External),
None,
options.network,
&Secp256k1::new(),
)?,
skip_blocks: None,
})?,
SyncOptions::default(),
)?;

eprintln!("Wallet initialized.");

Ok(())
}

pub(crate) fn load(options: &Options) -> Result<Self> {
let path = data_dir()
.ok_or_else(|| anyhow!("Failed to retrieve data dir"))?
.join("ord");

if !path.exists() {
return Err(anyhow!("Wallet doesn't exist."));
}

let key = (
Mnemonic::from_entropy(&fs::read(path.join("entropy"))?)?,
None,
);

let wallet = bdk::wallet::Wallet::new(
Bip84(key.clone(), KeychainKind::External),
None,
options.network,
SqliteDatabase::new(
path
.join("wallet.sqlite")
.to_str()
.ok_or_else(|| anyhow!("Failed to convert path to str"))?
.to_string(),
),
)?;

let blockchain = RpcBlockchain::from_config(&RpcConfig {
url: options.rpc_url(),
auth: Auth::Cookie {
file: options.cookie_file()?,
},
network: options.network,
wallet_name: wallet_name_from_descriptor(
Bip84(key, KeychainKind::External),
None,
options.network,
&Secp256k1::new(),
)?,
skip_blocks: None,
})?;

wallet.sync(&blockchain, SyncOptions::default())?;

Ok(Self { wallet, blockchain })
}

pub(crate) fn find(&self, options: &Options, ordinal: Ordinal) -> Result<LocalUtxo> {
let index = Index::index(options)?;

for utxo in self.wallet.list_unspent()? {
if let Some(ranges) = index.list(utxo.outpoint)? {
for (start, end) in ranges {
if ordinal.0 >= start && ordinal.0 < end {
return Ok(utxo);
}
}
}
}

bail!("No utxo contains {}˚.", ordinal);
}
}
51 changes: 3 additions & 48 deletions src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,15 @@ use super::*;
mod balance;
mod fund;
mod init;
mod send;
mod utxos;

fn get_wallet(options: Options) -> Result<bdk::wallet::Wallet<SqliteDatabase>> {
let path = data_dir()
.ok_or_else(|| anyhow!("Failed to retrieve data dir"))?
.join("ord");

if !path.exists() {
return Err(anyhow!("Wallet doesn't exist."));
}

let key = (
Mnemonic::from_entropy(&fs::read(path.join("entropy"))?)?,
None,
);

let wallet = bdk::wallet::Wallet::new(
Bip84(key.clone(), KeychainKind::External),
None,
options.network,
SqliteDatabase::new(
path
.join("wallet.sqlite")
.to_str()
.ok_or_else(|| anyhow!("Failed to convert path to str"))?
.to_string(),
),
)?;

wallet.sync(
&RpcBlockchain::from_config(&RpcConfig {
url: options.rpc_url(),
auth: Auth::Cookie {
file: options.cookie_file()?,
},
network: options.network,
wallet_name: wallet_name_from_descriptor(
Bip84(key, KeychainKind::External),
None,
options.network,
&Secp256k1::new(),
)?,
skip_blocks: None,
})?,
SyncOptions::default(),
)?;

Ok(wallet)
}

#[derive(Debug, Parser)]
pub(crate) enum Wallet {
Balance,
Fund,
Init,
Send(send::Send),
Utxos,
}

Expand All @@ -67,6 +21,7 @@ impl Wallet {
Self::Balance => balance::run(options),
Self::Fund => fund::run(options),
Self::Init => init::run(options),
Self::Send(send) => send.run(options),
Self::Utxos => utxos::run(options),
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/balance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;

pub(crate) fn run(options: Options) -> Result {
println!("{}", get_wallet(options)?.get_balance()?);
println!("{}", Purse::load(&options)?.wallet.get_balance()?);
Ok(())
}
8 changes: 7 additions & 1 deletion src/subcommand/wallet/fund.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use super::*;

pub(crate) fn run(options: Options) -> Result {
println!("{}", get_wallet(options)?.get_address(LastUnused)?.address);
println!(
"{}",
Purse::load(&options)?
.wallet
.get_address(LastUnused)?
.address
);
Ok(())
}
49 changes: 1 addition & 48 deletions src/subcommand/wallet/init.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,5 @@
use super::*;

pub(crate) fn run(options: Options) -> Result {
let path = data_dir()
.ok_or_else(|| anyhow!("Failed to retrieve data dir"))?
.join("ord");

if path.exists() {
return Err(anyhow!("Wallet already exists."));
}

fs::create_dir_all(&path)?;

let seed = Mnemonic::generate_in_with(&mut rand::thread_rng(), Language::English, 12)?;

fs::write(path.join("entropy"), seed.to_entropy())?;

let wallet = bdk::wallet::Wallet::new(
Bip84((seed.clone(), None), KeychainKind::External),
None,
options.network,
SqliteDatabase::new(
path
.join("wallet.sqlite")
.to_str()
.ok_or_else(|| anyhow!("Failed to convert path to str"))?
.to_string(),
),
)?;

wallet.sync(
&RpcBlockchain::from_config(&RpcConfig {
url: options.rpc_url(),
auth: Auth::Cookie {
file: options.cookie_file()?,
},
network: options.network,
wallet_name: wallet_name_from_descriptor(
Bip84((seed, None), KeychainKind::External),
None,
options.network,
&Secp256k1::new(),
)?,
skip_blocks: None,
})?,
SyncOptions::default(),
)?;

eprintln!("Wallet initialized.");

Ok(())
Purse::init(&options)
}
46 changes: 46 additions & 0 deletions src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use super::*;

#[derive(Debug, Parser)]
pub(crate) struct Send {
#[clap(long)]
address: Address,
#[clap(long)]
ordinal: Ordinal,
}

impl Send {
pub(crate) fn run(self, options: Options) -> Result {
let wallet = Purse::load(&options)?;

let utxo = wallet.find(&options, self.ordinal)?;

let (mut psbt, _details) = {
let mut builder = wallet.wallet.build_tx();

builder
.manually_selected_only()
.fee_absolute(0)
.add_utxo(utxo.outpoint)?
.add_recipient(self.address.script_pubkey(), utxo.txout.value);

builder.finish()?
};

if !wallet.wallet.sign(&mut psbt, SignOptions::default())? {
bail!("Failed to sign transaction.");
}

let tx = psbt.extract_tx();

wallet.blockchain.broadcast(&tx)?;

println!(
"Sent ordinal {} to address {}: {}",
self.ordinal.0,
self.address,
tx.txid()
);

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/subcommand/wallet/utxos.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;

pub(crate) fn run(options: Options) -> Result {
for utxo in get_wallet(options)?.list_unspent()? {
for utxo in Purse::load(&options)?.wallet.list_unspent()? {
println!(
"{}:{} {}",
utxo.outpoint.txid, utxo.outpoint.vout, utxo.txout.value
Expand Down
Loading

0 comments on commit 28a2362

Please sign in to comment.