Skip to main content

anyswap_sdk/driver/
error.rs

1use anyswap_core::{
2    transaction::TxError,
3    types::{Chain, SwapNetwork, SwapType},
4};
5use thiserror::Error;
6use uuid::Uuid;
7
8use super::{chain::ChainError, record::StoreError};
9use crate::{client::ClientError, swap_script::ScriptError, utxo::UtxoError};
10
11#[derive(Clone, Debug, Error)]
12pub enum DriverError {
13    #[error(transparent)]
14    Chain(#[from] ChainError),
15
16    #[error("server: {0}")]
17    Server(#[from] ClientError),
18
19    #[error(transparent)]
20    Store(#[from] StoreError),
21
22    #[error("destination: {0}")]
23    Destination(String),
24
25    #[error("wallet: {0}")]
26    Wallet(String),
27
28    #[error("refused: {0}")]
29    Refused(#[from] Refusal),
30
31    #[error(transparent)]
32    Tx(#[from] TxError),
33
34    #[error(transparent)]
35    Utxo(#[from] UtxoError),
36
37    #[error("chain {0} is not configured")]
38    ChainNotConfigured(Chain),
39
40    #[error("chain {0} config: {1}")]
41    InvalidChainConfig(Chain, &'static str),
42
43    #[error("swap {0} is not in the store")]
44    Unknown(Uuid),
45
46    #[error("the record of swap {0} has no {1}")]
47    MissingField(Uuid, String),
48
49    #[error("the record of swap {0} is version {1}, which this SDK does not drive")]
50    RecordVersion(Uuid, u32),
51
52    #[error("swap type {0} is not driven")]
53    Unsupported(SwapType),
54
55    #[error("the server holds no signed request for swap {0}")]
56    Unsigned(Uuid),
57
58    #[error("the signed request of swap {0} does not verify")]
59    BadSignature(Uuid),
60
61    #[error("the quote expired")]
62    QuoteExpired,
63
64    #[error("the swap type does not match")]
65    WrongSwapType,
66
67    #[error("the server offers no quote: {0}")]
68    NotQuotable(&'static str),
69
70    #[error("amount outside {min}..={max}")]
71    AmountOutOfRange { min: u64, max: u64 },
72}
73
74impl DriverError {
75    /// Chain or server unreachable, or the server asking to slow down: the
76    /// next pass sees the same world. Anything else means nothing may act
77    /// before it is understood.
78    pub fn retryable(&self) -> bool {
79        match self {
80            Self::Chain(_) | Self::Server(ClientError::Transport(_)) => true,
81            Self::Server(ClientError::Api { status, .. }) => *status == 429 || *status >= 500,
82            _ => false,
83        }
84    }
85}
86
87impl From<ScriptError> for DriverError {
88    fn from(e: ScriptError) -> Self {
89        Self::Refused(e.into())
90    }
91}
92
93/// An acceptance check the server's copy of a swap failed.
94#[derive(Clone, Debug, PartialEq, Error)]
95pub enum Refusal {
96    #[error("the server is on {found}, this wallet on {expected}")]
97    Network {
98        expected: SwapNetwork,
99        found: SwapNetwork,
100    },
101
102    #[error("protocol version {found}, asked {expected}")]
103    ProtocolVersion { expected: String, found: String },
104
105    #[error("swap type {0}")]
106    SwapType(SwapType),
107
108    #[error("swap id {0}")]
109    SwapId(Uuid),
110
111    #[error("chain {0}")]
112    Chain(Chain),
113
114    #[error("asset {0}")]
115    Asset(String),
116
117    #[error("receive amount {found}, asked {expected}")]
118    ReceiveAmount { expected: u64, found: u64 },
119
120    #[error(
121        "send amount {found} as {receive_amount} + {base_fee} + {service_fee}, asked {expected}"
122    )]
123    SendAmount {
124        expected: u64,
125        found: u64,
126        receive_amount: u64,
127        base_fee: u64,
128        service_fee: u64,
129    },
130
131    #[error("script: {0}")]
132    Script(String),
133
134    #[error("address: {0}")]
135    Address(String),
136
137    #[error("blinding pubkey")]
138    BlindingPubkey,
139
140    #[error("our pubkey")]
141    OurPubkey,
142
143    #[error("claim invoice differs from the one submitted")]
144    ClaimInvoice,
145
146    #[error("payment hash")]
147    PaymentHash,
148
149    #[error("invoice: {0}")]
150    Invoice(String),
151
152    #[error("csv {csv} outside {min}..={max}")]
153    Csv { csv: u32, min: u32, max: u32 },
154
155    #[error(
156        "funding timeout {funding_timeout} s and {confirm} s to confirm outlast the server's wait of {await_opening_tx} s"
157    )]
158    FundingTimeout {
159        funding_timeout: u64,
160        confirm: u64,
161        await_opening_tx: u64,
162    },
163
164    #[error("wallet: {0}")]
165    Wallet(String),
166
167    #[error("Liquid is not configured")]
168    LiquidNotConfigured,
169
170    #[error("malformed: {0}")]
171    Malformed(String),
172}
173
174impl From<ScriptError> for Refusal {
175    fn from(e: ScriptError) -> Self {
176        match e {
177            ScriptError::LiquidNotConfigured => Self::LiquidNotConfigured,
178            ScriptError::UnexpectedAsset { found, .. } => Self::Asset(found),
179            ScriptError::MissingField(_) => Self::BlindingPubkey,
180            ScriptError::Wallet(e) => Self::Wallet(e),
181            ScriptError::Script(e) => Self::Script(e),
182        }
183    }
184}