depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

lib.rs (5885B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 use std::str::FromStr;
     17 
     18 use bitcoin::{Address, Amount, Network, Txid, hashes::hex::FromHex};
     19 use rpc::{Category, Transaction};
     20 use rpc_utils::segwit_min_amount;
     21 use segwit::{decode_segwit_msg, encode_segwit_key};
     22 use taler_common::{api::EddsaPublicKey, config::parser::ConfigSource};
     23 
     24 use crate::{rpc::RpcApi, segwit::DecodeSegWitErr};
     25 
     26 pub mod api;
     27 pub mod cli;
     28 pub mod config;
     29 pub mod db;
     30 pub mod loops;
     31 pub mod payto;
     32 pub mod rpc;
     33 pub mod rpc_utils;
     34 pub mod segwit;
     35 pub mod setup;
     36 pub mod sql;
     37 pub mod taler_utils;
     38 
     39 pub const CONFIG_SOURCE: ConfigSource = ConfigSource::simple("depolymerizer-bitcoin");
     40 
     41 #[derive(Debug, thiserror::Error)]
     42 pub enum GetOpReturnErr {
     43     #[error("Missing opreturn")]
     44     MissingOpReturn,
     45     #[error(transparent)]
     46     RPC(#[from] rpc::Error),
     47 }
     48 
     49 /// An extended bitcoincore JSON-RPC api client who can send and retrieve metadata with their transaction
     50 #[allow(async_fn_in_trait)]
     51 pub trait RpcApiExtended: RpcApi {
     52     /// Send a transaction with a 32B key as metadata encoded using fake segwit addresses
     53     async fn send_segwit_key(
     54         &mut self,
     55         to: &Address,
     56         amount: Amount,
     57         metadata: &[u8; 32],
     58     ) -> rpc::Result<Txid> {
     59         let network = guess_network(to);
     60         let hrp = match network {
     61             Network::Bitcoin => bech32::hrp::BC,
     62             Network::Testnet | Network::Signet => bech32::hrp::TB,
     63             Network::Regtest => bech32::hrp::BCRT,
     64             _ => unimplemented!(),
     65         };
     66         let addresses = encode_segwit_key(hrp, metadata);
     67         let addresses = [
     68             Address::from_str(&addresses[0]).unwrap().assume_checked(),
     69             Address::from_str(&addresses[1]).unwrap().assume_checked(),
     70         ];
     71         let mut recipients = vec![(to, amount)];
     72         let min = segwit_min_amount();
     73         recipients.extend(addresses.iter().map(|addr| (addr, min)));
     74         self.send_many(recipients).await
     75     }
     76 
     77     /// Get detailed information about an in-wallet transaction and it's 32B metadata key encoded using fake segwit addresses
     78     async fn get_tx_segwit_key(
     79         &mut self,
     80         id: &Txid,
     81     ) -> Result<(Transaction, Result<EddsaPublicKey, DecodeSegWitErr>), rpc::Error> {
     82         let full = self.get_tx(id).await?;
     83 
     84         let addresses: Vec<String> = full
     85             .decoded
     86             .vout
     87             .iter()
     88             .filter_map(|it| {
     89                 it.script_pub_key
     90                     .address
     91                     .as_ref()
     92                     .map(|addr| addr.clone().assume_checked().to_string())
     93             })
     94             .collect();
     95 
     96         Ok((full, decode_segwit_msg(&addresses)))
     97     }
     98 
     99     /// Get detailed information about an in-wallet transaction and its op_return metadata
    100     async fn get_tx_op_return(
    101         &mut self,
    102         id: &Txid,
    103     ) -> Result<(Transaction, Vec<u8>), GetOpReturnErr> {
    104         let full = self.get_tx(id).await?;
    105 
    106         let op_return_out = full
    107             .decoded
    108             .vout
    109             .iter()
    110             .find(|it| it.script_pub_key.asm.starts_with("OP_RETURN"))
    111             .ok_or(GetOpReturnErr::MissingOpReturn)?;
    112 
    113         let hex = op_return_out.script_pub_key.asm.split_once(' ').unwrap().1;
    114         // Op return payload is always encoded in hexadecimal
    115         let metadata = Vec::from_hex(hex).unwrap();
    116 
    117         Ok((full, metadata))
    118     }
    119 
    120     /// Get the first sender address from a raw transaction
    121     async fn sender_address(&mut self, full: &Transaction) -> rpc::Result<Address> {
    122         let first = &full.decoded.vin[0];
    123         let tx = self.get_input_output(&first.txid.unwrap()).await?;
    124         Ok(tx
    125             .vout
    126             .into_iter()
    127             .find(|it| it.n == first.vout.unwrap())
    128             .unwrap()
    129             .script_pub_key
    130             .address
    131             .unwrap()
    132             .assume_checked())
    133     }
    134 
    135     /// Bounce a transaction bask to its sender
    136     ///
    137     /// There is no reliable way to bounce a transaction as you cannot know if the addresses
    138     /// used are shared or come from a third-party service. We only send back to the first input
    139     /// address as a best-effort gesture.
    140     async fn bounce(
    141         &mut self,
    142         id: &Txid,
    143         bounce_fee: &Amount,
    144         metadata: Option<&[u8]>,
    145     ) -> Result<Txid, rpc::Error> {
    146         let full = self.get_tx(id).await?;
    147         let detail = &full.details[0];
    148         assert!(detail.category == Category::Receive);
    149 
    150         let amount = detail.amount.to_unsigned().unwrap();
    151         let sender = self.sender_address(&full).await?;
    152         let bounce_amount = Amount::from_sat(amount.to_sat().saturating_sub(bounce_fee.to_sat()));
    153         // Send refund making recipient pay the transaction fees
    154         self.send(&sender, bounce_amount, metadata, true).await
    155     }
    156 }
    157 
    158 impl<T: RpcApi> RpcApiExtended for T {}
    159 
    160 pub fn guess_network(address: &Address) -> Network {
    161     let addr = address.as_unchecked();
    162     for network in [
    163         Network::Bitcoin,
    164         Network::Regtest,
    165         Network::Signet,
    166         Network::Regtest,
    167     ] {
    168         if addr.is_valid_for_network(network) {
    169             return network;
    170         }
    171     }
    172     unreachable!()
    173 }