From 6efb5243ad1c590ddce854dc791d155865146990 Mon Sep 17 00:00:00 2001 From: joshpainter Date: Wed, 11 Feb 2026 02:10:24 -0600 Subject: [PATCH 1/4] =?UTF-8?q?Fix=20#691:=20display=20=E2=88=9E=20for=20N?= =?UTF-8?q?FT=20edition=5Ftotal=3D0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MintGarden convention uses edition_total=0 to mean unlimited editions. Previously hidden because condition only checked > 1. Co-Authored-By: Claude Opus 4.6 --- src/components/NftCard.tsx | 4 ++-- src/pages/Nft.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/NftCard.tsx b/src/components/NftCard.tsx index d8354f1c..30dfbc6d 100644 --- a/src/components/NftCard.tsx +++ b/src/components/NftCard.tsx @@ -317,12 +317,12 @@ export function NftCard({ nft, updateNfts, selectionState }: NftCardProps) {

{nftName} - {nft.edition_total != null && nft.edition_total > 1 && ( + {nft.edition_total != null && (nft.edition_total === 0 || nft.edition_total > 1) && ( {' '} ( - {nft.edition_number} of {nft.edition_total} + {nft.edition_number} of {nft.edition_total === 0 ? '∞' : nft.edition_total} ) diff --git a/src/pages/Nft.tsx b/src/pages/Nft.tsx index ad6b9e46..88f42457 100644 --- a/src/pages/Nft.tsx +++ b/src/pages/Nft.tsx @@ -272,10 +272,10 @@ export default function Nft() { address={nft?.launcher_id ?? ''} /> - {nft?.edition_total != null && nft?.edition_total > 1 && ( + {nft?.edition_total != null && (nft?.edition_total === 0 || nft?.edition_total > 1) && ( )} From 7abe4bc1963c39c81ac036088dad4f8541d853d5 Mon Sep 17 00:00:00 2001 From: joshpainter Date: Wed, 11 Feb 2026 02:10:29 -0600 Subject: [PATCH 2/4] Fix #390: make dialogs scrollable when viewport is constrained Add max-h-[85vh] overflow-y-auto to DialogContent so virtual keyboard on iPad landscape no longer covers dialog content. Co-Authored-By: Claude Opus 4.6 --- src/components/ui/dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index c5fde55b..68beae52 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -64,7 +64,7 @@ const DialogContent = React.forwardRef< Date: Wed, 11 Feb 2026 02:10:34 -0600 Subject: [PATCH 3/4] Fix #726: improve mnemonic import error messages Replace cryptic BIP39 errors with user-friendly messages that identify the specific invalid word, wrong word count, or checksum failure. Co-Authored-By: Claude Opus 4.6 --- crates/sage/src/endpoints/keys.rs | 25 ++++++++++++++++++++++++- crates/sage/src/error.rs | 4 ++++ src/pages/ImportWallet.tsx | 7 ++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/sage/src/endpoints/keys.rs b/crates/sage/src/endpoints/keys.rs index 3b59b515..5f6974f0 100644 --- a/crates/sage/src/endpoints/keys.rs +++ b/crates/sage/src/endpoints/keys.rs @@ -148,7 +148,30 @@ impl Sage { return Err(Error::InvalidKey); } } else { - let mnemonic = Mnemonic::from_str(&req.key)?; + let words: Vec<&str> = req.key.split_whitespace().collect(); + let word_count = words.len(); + + if word_count != 12 && word_count != 24 { + return Err(Error::InvalidMnemonic(format!( + "Expected 12 or 24 words, but got {word_count}." + ))); + } + + let mnemonic = Mnemonic::from_str(&req.key).map_err(|e| match e { + bip39::Error::BadWordCount(count) => Error::InvalidMnemonic(format!( + "Expected 12 or 24 words, but got {count}." + )), + bip39::Error::UnknownWord(idx) => Error::InvalidMnemonic(format!( + "Word #{} ({}) is not a valid BIP39 word.", + idx + 1, + words.get(idx).copied().unwrap_or("unknown"), + )), + bip39::Error::InvalidChecksum => Error::InvalidMnemonic( + "Invalid checksum. Please verify all words are correct and in the right order." + .to_string(), + ), + _ => Error::InvalidMnemonic(format!("Invalid mnemonic: {e}")), + })?; let master_sk = SecretKey::from_seed(&mnemonic.to_seed("")); let master_pk = master_sk.public_key(); let fingerprint = if req.save_secrets { diff --git a/crates/sage/src/error.rs b/crates/sage/src/error.rs index 66ff45a0..043f7e0e 100644 --- a/crates/sage/src/error.rs +++ b/crates/sage/src/error.rs @@ -122,6 +122,9 @@ pub enum Error { #[error("Invalid key")] InvalidKey, + #[error("{0}")] + InvalidMnemonic(String), + #[error("Wrong address prefix: {0}")] AddressPrefix(String), @@ -279,6 +282,7 @@ impl Error { Self::Bls(..) | Self::Hex(..) | Self::InvalidKey + | Self::InvalidMnemonic(..) | Self::TryFromSlice(..) | Self::TryFromInt(..) | Self::ParseInt(..) diff --git a/src/pages/ImportWallet.tsx b/src/pages/ImportWallet.tsx index 609736e7..55f0e6ad 100644 --- a/src/pages/ImportWallet.tsx +++ b/src/pages/ImportWallet.tsx @@ -183,9 +183,10 @@ export default function ImportWallet() { - Enter your mnemonic, private key, or public key above. - If it's a public key, it will be imported as a - read-only cold wallet. + Enter your 12 or 24-word mnemonic seed phrase, private + key, or public key. Words should be separated by + spaces. If it's a public key, it will be imported + as a read-only cold wallet. From 88ef01dc82d8f5d9b9a3b0c3bbce75173046f6f4 Mon Sep 17 00:00:00 2001 From: joshpainter Date: Wed, 11 Feb 2026 02:10:39 -0600 Subject: [PATCH 4/4] Fix #723: validate request-only offers require a network fee Prevent crash when creating an offer with only requested assets and zero fee by adding validation on both backend and frontend. Co-Authored-By: Claude Opus 4.6 --- crates/sage/src/endpoints/offers.rs | 11 +++++++++++ src/pages/MakeOffer.tsx | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/crates/sage/src/endpoints/offers.rs b/crates/sage/src/endpoints/offers.rs index c80a21cc..b6d72019 100644 --- a/crates/sage/src/endpoints/offers.rs +++ b/crates/sage/src/endpoints/offers.rs @@ -81,6 +81,17 @@ impl Sage { } } + let has_offered_assets = offered.xch > 0 + || !offered.cats.is_empty() + || !offered.nfts.is_empty() + || !offered.options.is_empty(); + + if !has_offered_assets && offered.fee == 0 { + return Err(Error::InvalidAmount( + "A request-only offer requires a network fee.".to_string(), + )); + } + let mut requested = Requested::default(); let mut peer = None; diff --git a/src/pages/MakeOffer.tsx b/src/pages/MakeOffer.tsx index c16c4d68..a8aa0ab6 100644 --- a/src/pages/MakeOffer.tsx +++ b/src/pages/MakeOffer.tsx @@ -86,6 +86,20 @@ export function MakeOffer() { }); return; } + + if ( + !hasOfferedTokens && + !hasOfferedNfts && + !hasOfferedOptions && + parseFloat(state.fee || '0') === 0 + ) { + addError({ + kind: 'invalid', + reason: t`A request-only offer requires a network fee.`, + }); + return; + } + setIsConfirmDialogOpen(true); };