|
1 | | -use std::sync::Arc; |
| 1 | +use std::{result::Result as StdResult, str::FromStr, sync::Arc}; |
2 | 2 |
|
| 3 | +use ain_dftx::{deserialize, DfTx}; |
| 4 | +use ain_macros::ocean_endpoint; |
3 | 5 | use axum::{ |
| 6 | + extract::{Json, Path}, |
4 | 7 | routing::{get, post}, |
5 | | - Router, |
| 8 | + Extension, Router, |
6 | 9 | }; |
7 | | -use defichain_rpc::{Client, RpcApi}; |
8 | | -use super::path::Path; |
| 10 | +use bitcoin::{Transaction, Txid}; |
| 11 | +use defichain_rpc::{PoolPairRPC, RpcApi}; |
| 12 | +use rust_decimal::prelude::ToPrimitive; |
| 13 | +use rust_decimal_macros::dec; |
| 14 | +use serde::{Deserialize, Serialize, Serializer}; |
9 | 15 |
|
10 | | -async fn send_rawtx() -> String { |
11 | | - "Sending raw transaction".to_string() |
| 16 | +use super::{query::Query, response::Response, AppContext}; |
| 17 | +use crate::{ |
| 18 | + error::{ApiError, NotFoundKind}, |
| 19 | + model::{default_max_fee_rate, MempoolAcceptResult, RawTransactionResult, RawTxDto}, |
| 20 | + Error, Result, |
| 21 | +}; |
| 22 | + |
| 23 | +enum TransactionResponse { |
| 24 | + HexString(String), |
| 25 | + TransactionDetails(Box<RawTransactionResult>), |
| 26 | +} |
| 27 | + |
| 28 | +#[derive(Deserialize, Default)] |
| 29 | +struct QueryParams { |
| 30 | + verbose: bool, |
12 | 31 | } |
13 | 32 |
|
14 | | -async fn test_rawtx() -> String { |
15 | | - "Testing raw transaction".to_string() |
| 33 | +#[ocean_endpoint] |
| 34 | +async fn send_raw_tx( |
| 35 | + Extension(ctx): Extension<Arc<AppContext>>, |
| 36 | + Json(raw_tx_dto): Json<RawTxDto>, |
| 37 | +) -> Result<String> { |
| 38 | + validate(ctx.clone(), raw_tx_dto.hex.clone()).await?; |
| 39 | + let max_fee = match raw_tx_dto.max_fee_rate { |
| 40 | + Some(fee_rate) => { |
| 41 | + let sat_per_bitcoin = dec!(100_000_000); |
| 42 | + let fee_in_satoshis = fee_rate.checked_mul(sat_per_bitcoin); |
| 43 | + match fee_in_satoshis { |
| 44 | + Some(value) => Some(value.to_u64().unwrap_or_default()), |
| 45 | + None => Some(default_max_fee_rate().to_sat()), |
| 46 | + } |
| 47 | + } |
| 48 | + None => Some(default_max_fee_rate().to_sat()), |
| 49 | + }; |
| 50 | + match ctx |
| 51 | + .client |
| 52 | + .send_raw_transaction(raw_tx_dto.hex, max_fee) |
| 53 | + .await |
| 54 | + { |
| 55 | + Ok(tx_hash) => Ok(tx_hash.to_string()), |
| 56 | + Err(e) => { |
| 57 | + eprintln!("Failed to send raw transaction: {:?}", e); |
| 58 | + if e.to_string().contains("TX decode failed") { |
| 59 | + Err(Error::BadRequest("Transaction decode failed".to_string())) |
| 60 | + } else { |
| 61 | + Err(Error::RpcError(e)) |
| 62 | + } |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | +#[ocean_endpoint] |
| 67 | +async fn test_raw_tx( |
| 68 | + Extension(ctx): Extension<Arc<AppContext>>, |
| 69 | + Json(raw_tx_dto): Json<RawTxDto>, |
| 70 | +) -> Result<Response<Vec<MempoolAcceptResult>>> { |
| 71 | + let trx = defichain_rpc::RawTx::raw_hex(raw_tx_dto.hex); |
| 72 | + let max_fee = match raw_tx_dto.max_fee_rate { |
| 73 | + Some(fee_rate) => { |
| 74 | + let sat_per_bitcoin = dec!(100_000_000); |
| 75 | + let fee_in_satoshis = fee_rate.checked_mul(sat_per_bitcoin); |
| 76 | + match fee_in_satoshis { |
| 77 | + Some(value) => Some(value.to_u64().unwrap_or_default()), |
| 78 | + None => Some(default_max_fee_rate().to_sat()), |
| 79 | + } |
| 80 | + } |
| 81 | + None => Some(default_max_fee_rate().to_sat()), |
| 82 | + }; |
| 83 | + match ctx.client.test_mempool_accept(&[trx], max_fee).await { |
| 84 | + Ok(mempool_tx) => { |
| 85 | + let results = mempool_tx |
| 86 | + .into_iter() |
| 87 | + .map(|tx_result| MempoolAcceptResult { |
| 88 | + txid: tx_result.txid, |
| 89 | + allowed: tx_result.allowed, |
| 90 | + reject_reason: tx_result.reject_reason, |
| 91 | + vsize: tx_result.vsize, |
| 92 | + fees: tx_result.fees.map(|f| f.base), |
| 93 | + }) |
| 94 | + .collect::<Vec<MempoolAcceptResult>>(); |
| 95 | + Ok(Response::new(results)) |
| 96 | + } |
| 97 | + Err(e) => { |
| 98 | + eprintln!("Failed to send raw transaction: {:?}", e); |
| 99 | + if e.to_string().contains("TX decode failed") { |
| 100 | + Err(Error::BadRequest("Transaction decode failed".to_string())) |
| 101 | + } else { |
| 102 | + Err(Error::RpcError(e)) |
| 103 | + } |
| 104 | + } |
| 105 | + } |
16 | 106 | } |
17 | 107 |
|
18 | | -async fn get_rawtx(Path(txid): Path<String>) -> String { |
19 | | - format!("Details of raw transaction with txid {}", txid) |
| 108 | +impl Serialize for TransactionResponse { |
| 109 | + fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> |
| 110 | + where |
| 111 | + S: Serializer, |
| 112 | + { |
| 113 | + match *self { |
| 114 | + TransactionResponse::HexString(ref s) => serializer.serialize_str(s), |
| 115 | + TransactionResponse::TransactionDetails(ref details) => details.serialize(serializer), |
| 116 | + } |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +#[ocean_endpoint] |
| 121 | +async fn get_raw_tx( |
| 122 | + Extension(ctx): Extension<Arc<AppContext>>, |
| 123 | + Path(txid): Path<String>, |
| 124 | + Query(QueryParams { verbose }): Query<QueryParams>, |
| 125 | +) -> Result<TransactionResponse> { |
| 126 | + let tx_hash = Txid::from_str(&txid)?; |
| 127 | + if !verbose { |
| 128 | + let tx_hex = ctx.client.get_raw_transaction_hex(&tx_hash, None).await.map_err(|e| { |
| 129 | + if e.to_string().contains("No such mempool or blockchain transaction. Use gettransaction for wallet transactions.") { |
| 130 | + Error::NotFound(NotFoundKind::RawTx) |
| 131 | + } else { |
| 132 | + Error::RpcError(e) |
| 133 | + } |
| 134 | + })?; |
| 135 | + Ok(TransactionResponse::HexString(tx_hex)) |
| 136 | + } else { |
| 137 | + let tx_info = ctx |
| 138 | + .client |
| 139 | + .get_raw_transaction_info(&tx_hash, None) |
| 140 | + .await |
| 141 | + .map_err(|e| { |
| 142 | + eprintln!("Failed to get raw transaction hex: {:?}", e); |
| 143 | + Error::RpcError(e) |
| 144 | + })?; |
| 145 | + let result = RawTransactionResult { |
| 146 | + in_active_chain: tx_info.in_active_chain, |
| 147 | + hex: tx_info.hex, |
| 148 | + txid: tx_info.txid, |
| 149 | + hash: tx_info.hash, |
| 150 | + size: tx_info.size, |
| 151 | + vsize: tx_info.vsize, |
| 152 | + version: tx_info.version, |
| 153 | + locktime: tx_info.locktime, |
| 154 | + vin: tx_info.vin, |
| 155 | + vout: tx_info.vout, |
| 156 | + blockhash: tx_info.blockhash, |
| 157 | + confirmations: tx_info.confirmations, |
| 158 | + time: tx_info.time, |
| 159 | + blocktime: tx_info.blocktime, |
| 160 | + }; |
| 161 | + Ok(TransactionResponse::TransactionDetails(Box::new(result))) |
| 162 | + } |
| 163 | +} |
| 164 | + |
| 165 | +async fn validate(ctx: Arc<AppContext>, hex: String) -> Result<()> { |
| 166 | + if !hex.starts_with("040000000001") { |
| 167 | + return Ok(()); |
| 168 | + } |
| 169 | + let data = hex::decode(hex)?; |
| 170 | + println!("decode_hex {:?}", data); |
| 171 | + let trx = deserialize::<Transaction>(&data)?; |
| 172 | + let bytes = trx.output[0].clone().script_pubkey.into_bytes(); |
| 173 | + let tx: Option<DfTx> = if bytes.len() > 2 && bytes[0] == 0x6a && bytes[1] <= 0x4e { |
| 174 | + let offset = 1 + match bytes[1] { |
| 175 | + 0x4c => 2, |
| 176 | + 0x4d => 3, |
| 177 | + 0x4e => 4, |
| 178 | + _ => 1, |
| 179 | + }; |
| 180 | + |
| 181 | + let raw_tx = &bytes[offset..]; |
| 182 | + Some(deserialize::<DfTx>(raw_tx)?) |
| 183 | + } else { |
| 184 | + return Ok(()); |
| 185 | + }; |
| 186 | + |
| 187 | + if let Some(tx) = tx { |
| 188 | + if let DfTx::CompositeSwap(composite_swap) = tx { |
| 189 | + if composite_swap.pools.as_ref().is_empty() { |
| 190 | + return Ok(()); |
| 191 | + } |
| 192 | + let pool_id = composite_swap.pools.iter().last().unwrap(); |
| 193 | + let tokio_id = composite_swap.pool_swap.to_token_id.0.to_string(); |
| 194 | + let pool_pair = ctx |
| 195 | + .client |
| 196 | + .get_pool_pair(pool_id.to_string(), Some(true)) |
| 197 | + .await?; |
| 198 | + for (_, pool_pair_info) in pool_pair.0 { |
| 199 | + if pool_pair_info.id_token_a.eq(&tokio_id) |
| 200 | + || pool_pair_info.id_token_b.eq(&tokio_id) |
| 201 | + { |
| 202 | + println!("Found a match: {:?}", pool_pair_info); |
| 203 | + } |
| 204 | + } |
| 205 | + Ok(()) |
| 206 | + } else { |
| 207 | + Err(Error::BadRequest( |
| 208 | + "Transaction is not a composite swap".to_string(), |
| 209 | + )) |
| 210 | + } |
| 211 | + } else { |
| 212 | + Ok(()) |
| 213 | + } |
20 | 214 | } |
21 | 215 |
|
22 | 216 | pub fn router(ctx: Arc<AppContext>) -> Router { |
| 217 | + println!("{:?}", ctx.network); |
23 | 218 | Router::new() |
24 | | - .route("/send", post(send_rawtx)) |
25 | | - .route("/test", get(test_rawtx)) |
26 | | - .route("/:txid", get(get_rawtx)) |
| 219 | + .route("/send", post(send_raw_tx)) |
| 220 | + .route("/test", post(test_raw_tx)) |
| 221 | + .route("/:txid", get(get_raw_tx)) |
| 222 | + .layer(Extension(ctx)) |
27 | 223 | } |
0 commit comments