Skip to content

Commit 019c6f4

Browse files
committed
Expose error code for pocket offers
1 parent 155ea24 commit 019c6f4

File tree

7 files changed

+167
-17
lines changed

7 files changed

+167
-17
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ name = "uniffi_lipalightninglib"
1111
nigiri = []
1212

1313
[dependencies]
14-
chameleon = { git = "https://github.com/getlipa/wild", tag = "v1.12.0" }
15-
crow = { git = "https://github.com/getlipa/wild", tag = "v1.12.0" }
16-
honey-badger = { git = "https://github.com/getlipa/wild", tag = "v1.12.0" }
17-
graphql = { git = "https://github.com/getlipa/wild", tag = "v1.12.0" }
14+
chameleon = { git = "https://github.com/getlipa/wild/", tag = "v1.13.0" }
15+
crow = { git = "https://github.com/getlipa/wild/", tag = "v1.13.0" }
16+
honey-badger = { git = "https://github.com/getlipa/wild/", tag = "v1.13.0" }
17+
graphql = { git = "https://github.com/getlipa/wild/", tag = "v1.13.0" }
1818
perro = { git = "https://github.com/getlipa/perro", tag = "v1.1.0" }
1919

2020
breez-sdk-core = { git = "https://github.com/breez/breez-sdk", tag = "0.2.5" }

examples/3l-node/cli.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ fn list_offers(node: &LightningNode) -> Result<(), String> {
607607
topup_value_minor_units,
608608
exchange_fee_minor_units,
609609
exchange_fee_rate_permyriad,
610+
error,
610611
} => {
611612
println!(" ID: {id}");
612613
println!(
@@ -628,6 +629,10 @@ fn list_offers(node: &LightningNode) -> Result<(), String> {
628629
" Exchange at: {}",
629630
exchanged_at.format("%d/%m/%Y %T UTC"),
630631
);
632+
633+
if let Some(e) = error {
634+
println!(" Failure reason: {:?}", e);
635+
}
631636
}
632637
}
633638
println!(" Status: {:?}", offer.status);
@@ -709,14 +714,15 @@ fn offer_to_string(offer: Option<OfferKind>) -> String {
709714
topup_value_minor_units,
710715
exchange_fee_minor_units,
711716
exchange_fee_rate_permyriad,
717+
..
712718
}) => {
713719
let updated_at: DateTime<Utc> = updated_at.into();
714720
format!(
715721
"Pocket exchange ({id}) of {:.2} {currency_code} at {} at rate {rate} SATS per {currency_code}, fee was {:.2}% or {:.2} {currency_code}",
716722
topup_value_minor_units as f64 / 100f64,
717723
updated_at.format("%d/%m/%Y %T UTC"),
718724
exchange_fee_rate_permyriad as f64 / 100f64,
719-
exchange_fee_minor_units as f64 / 100f64
725+
exchange_fee_minor_units as f64 / 100f64,
720726
)
721727
}
722728
None => "None".to_string(),

src/data_store.rs

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::errors::Result;
22
use crate::fund_migration::MigrationStatus;
33
use crate::migrations::migrate;
4-
use crate::{ExchangeRate, OfferKind, TzConfig, UserPreferences};
4+
use crate::{ExchangeRate, OfferKind, PocketOfferError, TzConfig, UserPreferences};
55

66
use chrono::{DateTime, Utc};
7+
use crow::{PermanentFailureCode, TemporaryFailureCode};
78
use perro::MapToError;
89
use rusqlite::Connection;
910
use rusqlite::Row;
@@ -66,13 +67,14 @@ impl DataStore {
6667
topup_value_minor_units,
6768
exchange_fee_minor_units,
6869
exchange_fee_rate_permyriad,
70+
error,
6971
}) = offer
7072
{
7173
let exchanged_at: DateTime<Utc> = updated_at.into();
7274
tx.execute(
7375
"\
74-
INSERT INTO offers (payment_hash, pocket_id, fiat_currency, rate, exchanged_at, topup_value_minor_units, exchange_fee_minor_units, exchange_fee_rate_permyriad)\
75-
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)\
76+
INSERT INTO offers (payment_hash, pocket_id, fiat_currency, rate, exchanged_at, topup_value_minor_units, exchange_fee_minor_units, exchange_fee_rate_permyriad, error)\
77+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)\
7678
",
7779
(
7880
payment_hash,
@@ -82,7 +84,8 @@ impl DataStore {
8284
&exchanged_at,
8385
topup_value_minor_units,
8486
exchange_fee_minor_units,
85-
exchange_fee_rate_permyriad
87+
exchange_fee_rate_permyriad,
88+
from_offer_error(error)
8689
),
8790
)
8891
.map_to_invalid_input("Failed to add new incoming pocket offer to offers db")?;
@@ -99,7 +102,7 @@ impl DataStore {
99102
" \
100103
SELECT timezone_id, timezone_utc_offset_secs, payments.fiat_currency, h.rate, h.updated_at, \
101104
o.pocket_id, o.fiat_currency, o.rate, o.exchanged_at, o.topup_value_minor_units, \
102-
o.exchange_fee_minor_units, o.exchange_fee_rate_permyriad \
105+
o.exchange_fee_minor_units, o.exchange_fee_rate_permyriad, o.error \
103106
FROM payments \
104107
LEFT JOIN exchange_rates_history h on payments.exchange_rates_history_snaphot_id=h.snapshot_id \
105108
AND payments.fiat_currency=h.fiat_currency \
@@ -262,6 +265,7 @@ fn offer_kind_from_row(row: &Row) -> rusqlite::Result<Option<OfferKind>> {
262265
topup_value_minor_units,
263266
exchange_fee_minor_units,
264267
exchange_fee_rate_permyriad,
268+
error: to_offer_error(row.get(12)?),
265269
}))
266270
}
267271
None => Ok(None),
@@ -293,13 +297,80 @@ fn local_payment_data_from_row(row: &Row) -> rusqlite::Result<LocalPaymentData>
293297
})
294298
}
295299

300+
pub fn from_offer_error(error: Option<PocketOfferError>) -> Option<String> {
301+
error.map(|e| {
302+
match e {
303+
PocketOfferError::TemporaryFailure { code } => match code {
304+
TemporaryFailureCode::NoRoute => "no_route".to_string(),
305+
TemporaryFailureCode::InvoiceExpired => "invoice_expired".to_string(),
306+
TemporaryFailureCode::Unexpected => "error".to_string(),
307+
TemporaryFailureCode::Unknown { msg } => msg,
308+
},
309+
PocketOfferError::PermanentFailure { code } => match code {
310+
PermanentFailureCode::ThresholdExceeded => "threshold_exceeded".to_string(),
311+
PermanentFailureCode::OrderInactive => "order_inactive".to_string(),
312+
PermanentFailureCode::CompaniesUnsupported => "companies_unsupported".to_string(),
313+
PermanentFailureCode::CountryUnsupported => "country_unsupported".to_string(),
314+
PermanentFailureCode::OtherRiskDetected => "other_risk_detected".to_string(),
315+
PermanentFailureCode::CustomerRequested => "customer_requested".to_string(),
316+
PermanentFailureCode::AccountNotMatching => "account_not_matching".to_string(),
317+
PermanentFailureCode::PayoutExpired => "payout_expired".to_string(),
318+
},
319+
}
320+
.to_string()
321+
})
322+
}
323+
324+
pub fn to_offer_error(code: Option<String>) -> Option<PocketOfferError> {
325+
code.map(|c| match &*c {
326+
"no_route" => PocketOfferError::TemporaryFailure {
327+
code: TemporaryFailureCode::NoRoute,
328+
},
329+
"invoice_expired" => PocketOfferError::TemporaryFailure {
330+
code: TemporaryFailureCode::InvoiceExpired,
331+
},
332+
"error" => PocketOfferError::TemporaryFailure {
333+
code: TemporaryFailureCode::Unexpected,
334+
},
335+
"threshold_exceeded" => PocketOfferError::PermanentFailure {
336+
code: PermanentFailureCode::ThresholdExceeded,
337+
},
338+
"order_inactive" => PocketOfferError::PermanentFailure {
339+
code: PermanentFailureCode::OrderInactive,
340+
},
341+
"companies_unsupported" => PocketOfferError::PermanentFailure {
342+
code: PermanentFailureCode::CompaniesUnsupported,
343+
},
344+
"country_unsupported" => PocketOfferError::PermanentFailure {
345+
code: PermanentFailureCode::CountryUnsupported,
346+
},
347+
"other_risk_detected" => PocketOfferError::PermanentFailure {
348+
code: PermanentFailureCode::OtherRiskDetected,
349+
},
350+
"customer_requested" => PocketOfferError::PermanentFailure {
351+
code: PermanentFailureCode::CustomerRequested,
352+
},
353+
"account_not_matching" => PocketOfferError::PermanentFailure {
354+
code: PermanentFailureCode::AccountNotMatching,
355+
},
356+
"payout_expired" => PocketOfferError::PermanentFailure {
357+
code: PermanentFailureCode::PayoutExpired,
358+
},
359+
e => PocketOfferError::TemporaryFailure {
360+
code: TemporaryFailureCode::Unknown { msg: e.to_string() },
361+
},
362+
})
363+
}
364+
296365
#[cfg(test)]
297366
mod tests {
298367
use crate::config::TzConfig;
299368
use crate::data_store::DataStore;
300369
use crate::fund_migration::MigrationStatus;
301370
use crate::{ExchangeRate, OfferKind, UserPreferences};
302371

372+
use crow::TemporaryFailureCode;
373+
use crow::TopupError::TemporaryFailure;
303374
use std::fs;
304375
use std::thread::sleep;
305376
use std::time::{Duration, SystemTime};
@@ -342,6 +413,22 @@ mod tests {
342413
topup_value_minor_units: 51245,
343414
exchange_fee_minor_units: 123,
344415
exchange_fee_rate_permyriad: 50,
416+
error: Some(TemporaryFailure {
417+
code: TemporaryFailureCode::NoRoute,
418+
}),
419+
};
420+
421+
let offer_kind_no_error = OfferKind::Pocket {
422+
id: "id".to_string(),
423+
exchange_rate: ExchangeRate {
424+
currency_code: "EUR".to_string(),
425+
rate: 5123,
426+
updated_at: SystemTime::now(),
427+
},
428+
topup_value_minor_units: 51245,
429+
exchange_fee_minor_units: 123,
430+
exchange_fee_rate_permyriad: 50,
431+
error: None,
345432
};
346433

347434
data_store
@@ -362,11 +449,20 @@ mod tests {
362449
.store_payment_info(
363450
"hash - no offer",
364451
user_preferences.clone(),
365-
exchange_rates,
452+
exchange_rates.clone(),
366453
None,
367454
)
368455
.unwrap();
369456

457+
data_store
458+
.store_payment_info(
459+
"hash - no error",
460+
user_preferences.clone(),
461+
exchange_rates,
462+
Some(offer_kind_no_error.clone()),
463+
)
464+
.unwrap();
465+
370466
assert!(data_store
371467
.retrieve_payment_info("non existent hash")
372468
.unwrap()
@@ -398,6 +494,12 @@ mod tests {
398494
user_preferences.fiat_currency
399495
);
400496
assert_eq!(local_payment_data.exchange_rate.rate, 4123);
497+
498+
let local_payment_data = data_store
499+
.retrieve_payment_info("hash - no error")
500+
.unwrap()
501+
.unwrap();
502+
assert_eq!(local_payment_data.offer.unwrap(), offer_kind_no_error);
401503
}
402504

403505
#[test]

src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub use crate::recovery::recover_lightning_node;
5151
use crate::secret::Secret;
5252
use crate::task_manager::{TaskManager, TaskPeriods};
5353
use crate::util::unix_timestamp_to_system_time;
54+
pub use crow::{PermanentFailureCode, TemporaryFailureCode};
5455

5556
use bip39::{Language, Mnemonic};
5657
use bitcoin::hashes::hex::ToHex;
@@ -63,7 +64,7 @@ use breez_sdk_core::{
6364
OpeningFeeParams, PaymentDetails, PaymentStatus, PaymentTypeFilter, SweepRequest,
6465
};
6566
use cipher::generic_array::typenum::U32;
66-
use crow::{CountryCode, LanguageCode, OfferManager, TopupInfo, TopupStatus};
67+
use crow::{CountryCode, LanguageCode, OfferManager, TopupError, TopupInfo, TopupStatus};
6768
use data_store::DataStore;
6869
use email_address::EmailAddress;
6970
use honey_badger::secrets::{generate_keypair, KeyPair};
@@ -206,6 +207,8 @@ pub enum OfferStatus {
206207
SETTLED,
207208
}
208209

210+
pub type PocketOfferError = TopupError;
211+
209212
#[derive(PartialEq, Eq, Debug, Clone)]
210213
pub enum OfferKind {
211214
/// An offer related to a topup using the Pocket exchange
@@ -221,6 +224,8 @@ pub enum OfferKind {
221224
exchange_fee_minor_units: u64,
222225
/// The rate of the fee expressed in permyriad (e.g. 1.5% would be 150)
223226
exchange_fee_rate_permyriad: u16,
227+
/// The optional error that might occurred in the offer process
228+
error: Option<PocketOfferError>,
224229
},
225230
}
226231

@@ -1097,6 +1102,7 @@ fn to_offer(topup_info: TopupInfo, current_rate: &Option<ExchangeRate>) -> Offer
10971102
topup_value_minor_units: topup_info.topup_value_minor_units,
10981103
exchange_fee_minor_units: topup_info.exchange_fee_minor_units,
10991104
exchange_fee_rate_permyriad: topup_info.exchange_fee_rate_permyriad,
1105+
error: topup_info.error,
11001106
},
11011107
amount: (topup_info.amount_sat * 1000).to_amount_down(current_rate),
11021108
lnurlw: topup_info.lnurlw,

src/lipalightninglib.udl

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,46 @@ dictionary OfferInfo {
265265
OfferStatus status;
266266
};
267267

268+
enum PermanentFailureCode {
269+
"ThresholdExceeded",
270+
"OrderInactive",
271+
"CompaniesUnsupported",
272+
"CountryUnsupported",
273+
"OtherRiskDetected",
274+
"CustomerRequested",
275+
"AccountNotMatching",
276+
"PayoutExpired",
277+
};
278+
279+
[Enum]
280+
interface TemporaryFailureCode {
281+
NoRoute();
282+
InvoiceExpired();
283+
Unexpected();
284+
Unknown(
285+
string msg
286+
);
287+
};
288+
289+
[Enum]
290+
interface PocketOfferError {
291+
TemporaryFailure(
292+
TemporaryFailureCode code
293+
);
294+
PermanentFailure(
295+
PermanentFailureCode code
296+
);
297+
};
298+
268299
[Enum]
269300
interface OfferKind {
270301
Pocket(
271302
string id,
272303
ExchangeRate exchange_rate,
273304
u64 topup_value_minor_units,
274305
u64 exchange_fee_minor_units,
275-
u16 exchange_fee_rate_permyriad
306+
u16 exchange_fee_rate_permyriad,
307+
PocketOfferError? error
276308
);
277309
};
278310

src/migrations.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ const MIGRATION_02_FUNDS_MIGRATION_STATUS: &str = "
5050
VALUES (0);
5151
";
5252

53+
const MIGRATION_03_OFFER_ERROR_MESSAGE: &str = "
54+
ALTER TABLE offers ADD COLUMN error TEXT NULL;
55+
";
5356
pub(crate) fn migrate(conn: &mut Connection) -> Result<()> {
5457
migrations()
5558
.to_latest(conn)
@@ -60,6 +63,7 @@ fn migrations() -> Migrations<'static> {
6063
Migrations::new(vec![
6164
M::up(MIGRATION_01_INIT),
6265
M::up(MIGRATION_02_FUNDS_MIGRATION_STATUS),
66+
M::up(MIGRATION_03_OFFER_ERROR_MESSAGE),
6367
])
6468
}
6569

0 commit comments

Comments
 (0)