-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add payjoin-bitcoincore-axum example receive part
- Loading branch information
Showing
8 changed files
with
1,294 additions
and
268 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "tutorial" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[workspace] | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
axum = "0.7.2" | ||
# bitcoin = "0.30.0" | ||
bitcoincore-rpc = "0.17.0" | ||
http-body-util = "0.1.0" | ||
payjoin = { path = "../../payjoin", features = ["send", "receive", "base64"] } | ||
tokio = {version = "1.35.1", features = ["rt", "macros", "rt-multi-thread"] } | ||
ureq = "2.9.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
use bitcoincore_rpc::RpcApi; | ||
pub struct CoreRpcConnection; | ||
|
||
impl CoreRpcConnection { | ||
pub fn new() -> (bitcoincore_rpc::Client, bitcoincore_rpc::Client) { | ||
let sender_wallet = bitcoincore_rpc::Client::new( | ||
"http://127.0.0.1:18332/wallet/sender", | ||
bitcoincore_rpc::Auth::UserPass("user".to_string(), "password".to_string()), | ||
) | ||
.unwrap(); | ||
let receiver_wallet = bitcoincore_rpc::Client::new( | ||
"http://127.0.0.1:18332/wallet/receiver", | ||
bitcoincore_rpc::Auth::UserPass("user".to_string(), "password".to_string()), | ||
) | ||
.unwrap(); | ||
(receiver_wallet, sender_wallet) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
use axum::Router; | ||
|
||
pub struct HttpServer; | ||
|
||
impl HttpServer { | ||
pub async fn new(port: u16, router: Router) { | ||
let url = format!("0.0.0.0:{}", port); | ||
let listener = tokio::net::TcpListener::bind(url).await.unwrap(); | ||
axum::serve(listener, router).await.unwrap(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use std::collections::HashMap; | ||
|
||
use axum::routing::post; | ||
use axum::Router; | ||
use bitcoincore_rpc::RpcApi; | ||
use http_server::HttpServer; | ||
use receive::Receiver; | ||
|
||
mod blockchain; | ||
mod http_server; | ||
mod receive; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let (receiver_wallet, sender_wallet) = blockchain::CoreRpcConnection::new(); | ||
tokio::task::spawn(async move { | ||
let receive_router = Router::new().route("/payjoin", post(Receiver::new)); | ||
let receive_port = 3227; | ||
HttpServer::new(receive_port, receive_router).await; | ||
}); | ||
let amount = payjoin::bitcoin::Amount::from_sat(1000); | ||
let endpoint = payjoin::Url::parse("https://localhost:3227/payjoin").unwrap(); | ||
let pj_uri = payjoin::PjUriBuilder::new( | ||
receiver_wallet.get_new_address(None, None).unwrap().assume_checked(), | ||
endpoint, | ||
) | ||
.amount(Some(amount)) | ||
.build() | ||
.unwrap(); | ||
|
||
let mut outputs = HashMap::with_capacity(1); | ||
outputs.insert(pj_uri.address.to_string(), amount); | ||
let options = bitcoincore_rpc::json::WalletCreateFundedPsbtOptions { | ||
lock_unspent: Some(true), | ||
fee_rate: Some(bitcoincore_rpc::bitcoin::Amount::ONE_SAT), | ||
..Default::default() | ||
}; | ||
let sender_psbt = sender_wallet | ||
.wallet_create_funded_psbt( | ||
&[], // inputs | ||
&outputs, | ||
None, // locktime | ||
Some(options), | ||
None, | ||
) | ||
.unwrap(); | ||
dbg!(&sender_psbt); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
use std::{collections::HashMap, str::FromStr}; | ||
|
||
use axum::extract::Request; | ||
use axum::http::HeaderMap; | ||
use axum::response::IntoResponse; | ||
use bitcoincore_rpc::{bitcoin::psbt::Psbt, RpcApi}; | ||
use http_body_util::BodyExt; | ||
use payjoin::{ | ||
base64, | ||
receive::{PayjoinProposal, ProvisionalProposal}, | ||
}; | ||
|
||
use crate::blockchain::CoreRpcConnection; | ||
|
||
struct Headers(HeaderMap); | ||
|
||
impl payjoin::receive::Headers for Headers { | ||
fn get_header(&self, key: &str) -> Option<&str> { | ||
self.0.get(key).and_then(|v| v.to_str().ok()) | ||
} | ||
} | ||
|
||
fn try_contributing_inputs(provisional_proposal: &mut ProvisionalProposal) -> Result<(), ()> { | ||
use payjoin::bitcoin::OutPoint; | ||
let (bitcoind, _) = CoreRpcConnection::new(); | ||
|
||
let available_inputs = bitcoind.list_unspent(None, None, None, None, None).unwrap(); | ||
let candidate_inputs: HashMap<payjoin::bitcoin::Amount, OutPoint> = available_inputs | ||
.iter() | ||
.map(|i| (i.amount, OutPoint { txid: i.txid, vout: i.vout })) | ||
.collect(); | ||
|
||
let selected_outpoint = provisional_proposal.try_preserving_privacy(candidate_inputs).unwrap(); | ||
let selected_utxo = available_inputs | ||
.iter() | ||
.find(|i| i.txid == selected_outpoint.txid && i.vout == selected_outpoint.vout) | ||
.unwrap(); | ||
|
||
// calculate receiver payjoin outputs given receiver payjoin inputs and original_psbt | ||
let txo_to_contribute = payjoin::bitcoin::TxOut { | ||
value: selected_utxo.amount.to_sat(), | ||
script_pubkey: selected_utxo.script_pub_key.clone(), | ||
}; | ||
let outpoint_to_contribute = | ||
payjoin::bitcoin::OutPoint { txid: selected_utxo.txid, vout: selected_utxo.vout }; | ||
provisional_proposal.contribute_witness_input(txo_to_contribute, outpoint_to_contribute); | ||
Ok(()) | ||
} | ||
|
||
async fn handle_request(request: Request, bitcoind: bitcoincore_rpc::Client) -> impl IntoResponse { | ||
let (parts, body) = request.into_parts(); | ||
let bytes = body.collect().await.unwrap().to_bytes(); | ||
let headers = Headers(parts.headers.clone()); | ||
let proposal = | ||
payjoin::receive::UncheckedProposal::from_request(&bytes[..], "", headers).unwrap(); | ||
let network = bitcoincore_rpc::bitcoin::Network::Testnet; | ||
|
||
let min_fee_rate = None; | ||
let checked_1 = proposal | ||
.check_broadcast_suitability(min_fee_rate, |tx| { | ||
let raw_tx = bitcoincore_rpc::bitcoin::consensus::encode::serialize_hex(&tx); | ||
let mempool_results = bitcoind.test_mempool_accept(&[raw_tx]).unwrap(); | ||
match mempool_results.first() { | ||
Some(result) => Ok(result.allowed), | ||
None => panic!(""), | ||
} | ||
}) | ||
.unwrap(); | ||
let checked_2 = checked_1 | ||
.check_inputs_not_owned(|input| { | ||
if let Ok(address) = payjoin::bitcoin::Address::from_script(input, network) { | ||
Ok(bitcoind | ||
.get_address_info(&address) | ||
.map(|info| info.is_mine.unwrap_or(false)) | ||
.unwrap()) | ||
} else { | ||
Ok(false) | ||
} | ||
}) | ||
.unwrap(); | ||
let checked_3 = checked_2.check_no_mixed_input_scripts().unwrap(); | ||
let checked_4 = checked_3.check_no_inputs_seen_before(|_outpoint| Ok(true)).unwrap(); | ||
let mut prov_proposal = checked_4 | ||
.identify_receiver_outputs(|output_script| { | ||
if let Ok(address) = payjoin::bitcoin::Address::from_script(output_script, network) { | ||
Ok(bitcoind | ||
.get_address_info(&address) | ||
.map(|info| info.is_mine.unwrap_or(false)) | ||
.unwrap()) | ||
} else { | ||
Ok(false) | ||
} | ||
}) | ||
.unwrap(); | ||
let _ = try_contributing_inputs(&mut prov_proposal); | ||
// Select receiver payjoin inputs. | ||
let receiver_substitute_address = bitcoind.get_new_address(None, None).unwrap(); | ||
prov_proposal.substitute_output_address(receiver_substitute_address.assume_checked()); | ||
let payjoin_proposal: PayjoinProposal = prov_proposal | ||
.finalize_proposal( | ||
|psbt: &Psbt| { | ||
Ok(bitcoind | ||
.wallet_process_psbt(&base64::encode(psbt.serialize()), None, None, Some(false)) | ||
.map(|res| Psbt::from_str(&res.psbt).unwrap()) | ||
.unwrap()) | ||
}, | ||
Some(payjoin::bitcoin::FeeRate::MIN), | ||
) | ||
.unwrap(); | ||
let payjoin_proposal_psbt = payjoin_proposal.psbt(); | ||
dbg!(&payjoin_proposal_psbt); | ||
|
||
"" | ||
} | ||
|
||
pub struct Receiver; | ||
|
||
impl Receiver { | ||
pub async fn new(request: Request) -> impl IntoResponse { | ||
let (receiver_wallet, _) = CoreRpcConnection::new(); | ||
let _request = handle_request(request, receiver_wallet).await; | ||
"Hello, World!" | ||
} | ||
} |
Empty file.
Oops, something went wrong.