Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/core/daemon_errors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ String localizedDaemonError(
if (raw.contains('TradeNotDisputable')) {
return l10n.tradeNotDisputable;
}
// The fiat code failed the create-order preflight (#175): a stale or tampered
// saved default that is not a valid ISO 4217 code. Re-picking a currency fixes
// it before the request is ever published.
if (raw.contains('InvalidFiatCode')) {
return l10n.invalidFiatCode;
}
// A dispute for this trade already exists, or one is still in flight: the
// open is a duplicate either way, and retrying it changes nothing.
if (raw.contains('DisputeAlreadyOpen')) {
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"@@locale": "de",
"@@last_modified": "2026-03-31",
"invalidFiatCode": "Diese Währung wird nicht unterstützt. Bitte wähle vor dem Erstellen der Order eine andere.",

"appName": "Mostro",
"loading": "Laden…",
Expand Down
4 changes: 4 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"@@locale": "en",
"@@last_modified": "2026-03-31",
"invalidFiatCode": "That currency isn't supported. Please pick another before creating the order.",
"@invalidFiatCode": {
"description": "Shown when a create-order fiat code fails the ISO 4217 preflight (stale/tampered saved default)"
},

"appName": "Mostro",
"@appName": {"description": "Application name"},
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"@@locale": "es",
"@@last_modified": "2026-03-31",
"invalidFiatCode": "Esa moneda no es compatible. Elige otra antes de crear la orden.",

"appName": "Mostro",
"loading": "Cargando…",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"@@locale": "fr",
"@@last_modified": "2026-03-31",
"invalidFiatCode": "Cette devise n'est pas prise en charge. Choisissez-en une autre avant de créer l'ordre.",

"appName": "Mostro",
"loading": "Chargement…",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_it.arb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"@@locale": "it",
"@@last_modified": "2026-03-31",
"invalidFiatCode": "Quella valuta non è supportata. Scegline un'altra prima di creare l'ordine.",

"appName": "Mostro",
"loading": "Caricamento…",
Expand Down
794 changes: 506 additions & 288 deletions rust/src/api/orders.rs

Large diffs are not rendered by default.

34 changes: 32 additions & 2 deletions rust/src/api/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,14 @@ fn validate_locale(locale: &str) -> Result<()> {
}
}

/// Validates an ISO 4217 fiat code: exactly 3 uppercase ASCII letters.
fn validate_fiat_code(code: &str) -> Result<()> {
/// Validates the *syntactic* shape of an ISO 4217 fiat code: exactly 3
/// uppercase ASCII letters. This does not check membership in any supported
/// currency set — a well-formed but unsupported code (e.g. "XYZ") passes here
/// and is left for the daemon to reject. Membership validation against the
/// daemon's advertised `supported_currencies` is tracked as a follow-up
/// (#175 review) so there is a single authoritative source rather than a
/// bundled list that can drift.
pub(crate) fn validate_fiat_code(code: &str) -> Result<()> {
let valid = code.len() == 3 && code.chars().all(|c| c.is_ascii_uppercase());
if valid {
Ok(())
Expand Down Expand Up @@ -352,6 +358,30 @@ mod tests {
assert!(err.to_string().contains("InvalidFiatCode"));
}

/// #175: create_order preflights the fiat code with this same validator, so
/// a stale or tampered saved default is rejected locally with the stable
/// InvalidFiatCode marker instead of going out as a daemon CantDo. Covers
/// the format cases that reach the create path.
#[test]
fn validate_fiat_code_marker_cases() {
// Valid ISO 4217 shape passes.
assert!(validate_fiat_code("USD").is_ok());
assert!(validate_fiat_code("EUR").is_ok());
// Bad shapes all fail with the InvalidFiatCode marker.
for bad in ["", "US", "USDD", "usd", "Us1", "US$", "ドル"] {
let err = validate_fiat_code(bad).unwrap_err();
assert!(
err.to_string().contains("InvalidFiatCode"),
"{bad:?} must be rejected with the InvalidFiatCode marker"
);
}
// A well-formed but unsupported code passes syntax validation — this
// documents the boundary so the guarantee is not misread as "rejects
// unsupported currencies". Membership is a tracked follow-up (#175
// review), enforced by the daemon in the meantime.
assert!(validate_fiat_code("XYZ").is_ok());
}

#[tokio::test]
async fn set_default_fiat_code_none_clears() {
let _g = settings_lock().lock().unwrap();
Expand Down
18 changes: 18 additions & 0 deletions test/core/daemon_errors_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ void main() {
);
});

/// PR #304 review (Catrya): the InvalidFiatCode preflight marker must map to
/// its localized string like every other daemon-error marker, both bare and
/// with the offending code as context.
test('maps the InvalidFiatCode preflight marker (#175)', () {
expect(
localizedDaemonError(l10n, 'InvalidFiatCode', fallback: 'x'),
l10n.invalidFiatCode,
);
expect(
localizedDaemonError(
l10n,
"InvalidFiatCode: 'XYZ' must be exactly 3 uppercase ASCII letters (ISO 4217)",
fallback: 'x',
),
l10n.invalidFiatCode,
);
});

test('maps timeout and storage markers, and falls back otherwise', () {
expect(
localizedDaemonError(l10n, 'NoDaemonResponse', fallback: 'x'),
Expand Down
Loading