use alloy::{
dyn_abi::DynSolType,
hex::{self},
primitives::Address,
providers::{Provider, ProviderBuilder},
};
use crate::{
error::AuthError,
middleware::{
one_time_payment::{
types::{OneTimePayment, OneTimePaymentConfig, SignedPaymentTx, ABS_WINDOW_SEC},
utils::create_tx_message,
},
utils::get_current_time,
},
};
pub async fn verify_tx(
signed_tx: SignedPaymentTx,
config: OneTimePaymentConfig,
) -> Result<(OneTimePayment, bool), AuthError> {
let reconstructed_message = create_tx_message(signed_tx.tx_hash);
println!("Message: 0x{}", hex::encode(&reconstructed_message));
let signature = signed_tx.signature;
println!("Signature: 0x{}", hex::encode(&signature.as_bytes()));
let recovered = match signature.recover_address_from_msg(reconstructed_message) {
Ok(address) => address,
Err(_) => return Err(AuthError::InvalidSignature),
};
println!("Recovered address: {}", recovered);
let provider = ProviderBuilder::new().on_http(config.rpc_url.parse().unwrap());
let tx_receipt = match provider
.get_transaction_receipt(signed_tx.tx_hash)
.await
.map_err(|e| AuthError::ContractError(e.to_string()))?
{
Some(tx_receipt) => tx_receipt,
None => {
println!("Failed: Transaction not found");
return Err(AuthError::TransactionNotFound);
}
};
if recovered != tx_receipt.from {
println!("Failed: Recovered address mismatch");
return Err(AuthError::InvalidSignature);
}
match tx_receipt.to {
Some(to) => {
if to != config.token_address {
return Err(AuthError::InvalidTransaction(
"Invalid token contract address".to_string(),
));
}
}
None => {
println!("Failed: To address not found");
return Err(AuthError::InvalidTransaction(
"To address not found".to_string(),
));
}
}
let receipt = match tx_receipt.inner.as_receipt() {
Some(receipt) => receipt,
None => {
return Err(AuthError::InvalidTransaction(
"Receipt not found".to_string(),
))
}
};
let transfer_log = match receipt.logs.first() {
Some(log) => log,
None => return Err(AuthError::InvalidTransaction("Log not found".to_string())),
};
if transfer_log.address() == config.token_address {
let current_time = get_current_time();
match transfer_log.block_timestamp {
Some(timestamp) => {
let log_time = timestamp;
if current_time < log_time || current_time - log_time > ABS_WINDOW_SEC {
return Err(AuthError::InvalidTransaction(
"Transaction outside valid period".to_string(),
));
}
}
None => {
return Err(AuthError::InvalidTransaction(
"Block timestamp not found".to_string(),
))
}
}
match transfer_log.topics().get(2) {
Some(t) => {
let to = Address::from_word(t.clone());
let data = &transfer_log.data().data;
let data_type = DynSolType::Uint(256);
let decoded = data_type
.abi_decode(&data)
.map_err(|e| AuthError::ContractError(e.to_string()))?;
let (amount, _) = match decoded.as_uint() {
Some(amount) => amount,
None => {
return Err(AuthError::InvalidTransaction(
"Amount couldn't be parsed from event".to_string(),
))
}
};
if to != config.recipient || amount != config.amount {
return Err(AuthError::InvalidTransaction(
"Invalid recipient or amount".to_string(),
));
}
}
None => return Err(AuthError::InvalidTransaction("Topic not found".to_string())),
}
let payment = OneTimePayment {
tx_hash: signed_tx.tx_hash,
sender: tx_receipt.from,
payment_timestamp: if let Some(ts) = transfer_log.block_timestamp {
ts
} else {
0
},
first_reedemed: current_time, redemptions: 0,
};
return Ok((payment, true));
} else {
return Err(AuthError::InvalidTransaction(
"Invalid Token used for payment".to_string(),
));
}
}