use core::fmt;
use std::error::Error as StdError;
use std::sync::Arc;
use async_trait::async_trait;
use paralock_core::evm::{self as core_evm, AdaptorSwap, ClaimSignature, EscrowStatus, EvmAddress, HtlcSwap, U256};
use paralock_core::keys::Secret;
use paralock_core::protocol::{ClaimLimits, EvmChain, Hex};
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
#[error("{message}")]
pub struct EvmError {
pub transient: bool,
message: String,
unsupported: bool,
tx_hash: Option<TxHash>,
nonce: Option<u64>,
#[source]
source: Option<Box<dyn StdError + Send + Sync>>,
}
impl EvmError {
pub fn new(message: impl Into<String>) -> Self {
Self { transient: false, message: message.into(), unsupported: false, tx_hash: None, nonce: None, source: None }
}
pub fn transient(message: impl Into<String>) -> Self {
Self { transient: true, ..Self::new(message) }
}
pub fn unsupported(method: &str) -> Self {
Self { unsupported: true, ..Self::new(format!("{method} is not supported by this EVM client")) }
}
#[must_use]
pub fn with_source(mut self, source: impl StdError + Send + Sync + 'static) -> Self {
self.source = Some(Box::new(source));
self
}
#[must_use]
pub fn with_transaction(mut self, hash: TxHash, nonce: Option<u64>) -> Self {
self.tx_hash = Some(hash);
self.nonce = nonce;
self
}
pub fn message(&self) -> &str {
&self.message
}
pub fn is_unsupported(&self) -> bool {
self.unsupported
}
pub fn tx_hash(&self) -> Option<&TxHash> {
self.tx_hash.as_ref()
}
pub fn nonce(&self) -> Option<u64> {
self.nonce
}
}
pub type TxHash = Hex;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TxStatus {
Unknown,
Pending,
Mined {
success: bool,
block_number: u64,
},
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SentTx {
pub hash: TxHash,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nonce: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw: Option<Hex>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client: Option<serde_json::Value>,
}
impl SentTx {
pub fn new(hash: TxHash) -> Self {
Self { hash, nonce: None, raw: None, client: None }
}
}
impl fmt::Debug for SentTx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SentTx")
.field("hash", &self.hash)
.field("nonce", &self.nonce)
.field("raw", &self.raw.as_ref().map(|r| format!("<{} bytes>", r.byte_len())))
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EvmLock<S> {
pub swap: S,
pub block_number: u64,
pub tx_hash: Option<TxHash>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HtlcOutcome {
Empty,
Active,
Refunded,
Claimed {
preimage: Secret,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AdaptorOutcome {
Empty,
Active,
Refunded,
Claimed {
signature: ClaimSignature,
},
}
#[async_trait]
pub trait ChainInfo: Send + Sync {
fn chain(&self) -> EvmChain;
fn chain_id(&self) -> u64;
fn contract(&self) -> Hex;
fn address(&self) -> Hex;
async fn now(&self) -> Result<u64, EvmError>;
async fn block_number(&self) -> Result<u64, EvmError>;
async fn rpc_chain_id(&self) -> Result<u64, EvmError>;
async fn token_balance(&self, token: &EvmAddress, owner: Option<&EvmAddress>) -> Result<U256, EvmError>;
async fn transaction_status(&self, hash: &TxHash) -> Result<TxStatus, EvmError> {
let _ = hash;
Err(EvmError::unsupported("transaction_status"))
}
async fn broadcast_raw(&self, raw: &Hex) -> Result<TxHash, EvmError> {
let _ = raw;
Err(EvmError::unsupported("broadcast_raw"))
}
}
#[async_trait]
pub trait EvmClient: ChainInfo {
async fn lock(&self, swap: &HtlcSwap) -> Result<TxHash, EvmError>;
async fn claim(&self, swap: &HtlcSwap, preimage: &Secret) -> Result<TxHash, EvmError>;
async fn simulate_claim(&self, swap: &HtlcSwap, preimage: &Secret) -> Result<(), EvmError>;
async fn sign_claim(
&self,
swap: &HtlcSwap,
preimage: &Secret,
replacing: Option<&SentTx>,
) -> Result<SentTx, EvmError> {
let _ = (swap, preimage, replacing);
Err(EvmError::unsupported("sign_claim"))
}
async fn send_claim(&self, swap: &HtlcSwap, preimage: &Secret) -> Result<SentTx, EvmError> {
self.claim(swap, preimage).await.map(SentTx::new)
}
async fn bump_claim(&self, swap: &HtlcSwap, preimage: &Secret, pending: &SentTx) -> Result<SentTx, EvmError> {
let _ = (swap, preimage, pending);
Err(EvmError::unsupported("bump_claim"))
}
async fn prepare_lock(&self, swap: &HtlcSwap) -> Result<(), EvmError> {
let _ = swap;
Ok(())
}
async fn refund(&self, swap: &HtlcSwap) -> Result<TxHash, EvmError>;
async fn get_lock(&self, hashlock: &[u8; 32]) -> Result<Option<EvmLock<HtlcSwap>>, EvmError>;
async fn state(&self, hashlock: &[u8; 32]) -> Result<[u8; 32], EvmError>;
async fn get_locked_swap(&self, hashlock: &[u8; 32]) -> Result<Option<HtlcSwap>, EvmError> {
Ok(self.get_lock(hashlock).await?.map(|l| l.swap))
}
async fn outcome(&self, hashlock: &[u8; 32]) -> Result<HtlcOutcome, EvmError>;
}
#[async_trait]
pub trait AdaptorEvmClient: ChainInfo {
fn id_of(&self, swap: &AdaptorSwap) -> [u8; 32] {
swap.id()
}
fn claim_digest(&self, id: &[u8; 32]) -> Result<[u8; 32], EvmError> {
let contract = EvmAddress::parse_any_case(self.contract().as_str())
.map_err(|_| EvmError::new("the escrow contract address is invalid"))?;
Ok(core_evm::claim_digest(self.chain_id(), &contract, id))
}
async fn lock(&self, swap: &AdaptorSwap) -> Result<(TxHash, [u8; 32]), EvmError>;
async fn claim(&self, swap: &AdaptorSwap, signature: &ClaimSignature) -> Result<TxHash, EvmError>;
async fn simulate_claim(&self, swap: &AdaptorSwap, signature: &ClaimSignature) -> Result<(), EvmError>;
async fn sign_claim(
&self,
swap: &AdaptorSwap,
signature: &ClaimSignature,
replacing: Option<&SentTx>,
) -> Result<SentTx, EvmError> {
let _ = (swap, signature, replacing);
Err(EvmError::unsupported("sign_claim"))
}
async fn send_claim(&self, swap: &AdaptorSwap, signature: &ClaimSignature) -> Result<SentTx, EvmError> {
self.claim(swap, signature).await.map(SentTx::new)
}
async fn bump_claim(
&self,
swap: &AdaptorSwap,
signature: &ClaimSignature,
pending: &SentTx,
) -> Result<SentTx, EvmError> {
let _ = (swap, signature, pending);
Err(EvmError::unsupported("bump_claim"))
}
async fn prepare_lock(&self, swap: &AdaptorSwap) -> Result<(), EvmError> {
let _ = swap;
Ok(())
}
async fn refund(&self, swap: &AdaptorSwap) -> Result<TxHash, EvmError>;
async fn get_lock(&self, id: &[u8; 32]) -> Result<Option<EvmLock<AdaptorSwap>>, EvmError>;
async fn status(&self, id: &[u8; 32]) -> Result<EscrowStatus, EvmError>;
async fn get_locked_swap(&self, id: &[u8; 32]) -> Result<Option<AdaptorSwap>, EvmError> {
Ok(self.get_lock(id).await?.map(|l| l.swap))
}
async fn outcome(&self, id: &[u8; 32]) -> Result<AdaptorOutcome, EvmError>;
}
#[derive(Clone)]
pub struct ChainClients {
pub chain: EvmChain,
pub chain_id: u64,
pub token: Hex,
pub token_symbol: String,
pub token_decimals: u32,
pub htlc: Arc<dyn EvmClient>,
pub adaptor: Option<Arc<dyn AdaptorEvmClient>>,
}
impl fmt::Debug for ChainClients {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChainClients")
.field("chain", &self.chain)
.field("chain_id", &self.chain_id)
.field("token", &self.token)
.field("token_symbol", &self.token_symbol)
.field("token_decimals", &self.token_decimals)
.field("adaptor", &self.adaptor.is_some())
.finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct RelayerClients {
pub htlc: Arc<dyn EvmClient>,
pub adaptor: Option<Arc<dyn AdaptorEvmClient>>,
}
#[derive(Clone)]
pub struct VerifierClients {
pub htlc: Arc<dyn EvmClient>,
pub adaptor: Option<Arc<dyn AdaptorEvmClient>>,
}
#[derive(Clone)]
pub struct TakerChain {
pub htlc: Arc<dyn EvmClient>,
pub adaptor: Option<Arc<dyn AdaptorEvmClient>>,
pub relayer: Option<RelayerClients>,
pub verifier: Option<VerifierClients>,
pub tokens: Vec<Hex>,
pub token_decimals: Option<u32>,
pub claim_limits: Option<ClaimLimits>,
}
impl TakerChain {
pub fn new(htlc: Arc<dyn EvmClient>, tokens: Vec<Hex>) -> Self {
Self { htlc, adaptor: None, relayer: None, verifier: None, tokens, token_decimals: None, claim_limits: None }
}
#[must_use]
pub fn with_adaptor(mut self, adaptor: Arc<dyn AdaptorEvmClient>) -> Self {
self.adaptor = Some(adaptor);
self
}
#[must_use]
pub fn with_relayer(mut self, relayer: RelayerClients) -> Self {
self.relayer = Some(relayer);
self
}
#[must_use]
pub fn with_verifier(mut self, verifier: VerifierClients) -> Self {
self.verifier = Some(verifier);
self
}
}
impl fmt::Debug for TakerChain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TakerChain")
.field("tokens", &self.tokens)
.field("adaptor", &self.adaptor.is_some())
.field("relayer", &self.relayer.is_some())
.field("verifier", &self.verifier.is_some())
.field("token_decimals", &self.token_decimals)
.field("claim_limits", &self.claim_limits)
.finish_non_exhaustive()
}
}
pub(crate) fn parse_address(hex: &Hex, what: &str) -> crate::Result<EvmAddress> {
EvmAddress::parse(hex.as_str())
.ok()
.filter(|a| !a.is_zero())
.ok_or_else(|| crate::Error::invalid_request(format!("{what} must be a non-zero 0x address")))
}
pub(crate) fn parse_wire_address(hex: &Hex, what: &str) -> crate::Result<EvmAddress> {
EvmAddress::parse_any_case(hex.as_str())
.ok()
.filter(|a| !a.is_zero())
.ok_or_else(|| crate::Error::invalid_request(format!("{what} must be a non-zero 0x address")))
}