@@ -35,6 +35,27 @@ pub fn convert_to_homograph_safe_chars(input: &str) -> String {
3535 . collect ( )
3636}
3737
38+ fn extract_dpns_label ( name : & str ) -> & str {
39+ if let Some ( dot_pos) = name. rfind ( '.' ) {
40+ let ( label_part, suffix) = name. split_at ( dot_pos) ;
41+ if suffix. eq_ignore_ascii_case ( ".dash" ) {
42+ return label_part;
43+ }
44+ }
45+ name
46+ }
47+
48+ /// Strip an optional case-insensitive `.dash` suffix and apply DPNS
49+ /// homograph-safe normalization, producing a value suitable for matching
50+ /// against the `normalizedLabel` field of `domain` documents.
51+ ///
52+ /// Accepts either a bare label (e.g. `"alice"`) or a full DPNS name
53+ /// (e.g. `"alice.dash"`, `"Alice.DASH"`) and returns the normalized label
54+ /// (e.g. `"a11ce"`).
55+ fn normalize_dpns_label ( input : & str ) -> String {
56+ convert_to_homograph_safe_chars ( extract_dpns_label ( input) )
57+ }
58+
3859/// Check if a username is valid according to DPNS rules
3960///
4061/// A username is valid if:
@@ -365,19 +386,31 @@ impl Sdk {
365386 ///
366387 /// # Arguments
367388 ///
368- /// * `label` - The username label to check (e.g., "alice")
389+ /// * `name` - The username label (e.g., "alice") or full DPNS name
390+ /// (e.g., "alice.dash"). The `.dash` suffix is matched
391+ /// case-insensitively and stripped before normalization, mirroring
392+ /// [`Sdk::resolve_dpns_name`].
369393 ///
370394 /// # Returns
371395 ///
372396 /// Returns `true` if the name is available, `false` if it's taken
373- pub async fn is_dpns_name_available ( & self , label : & str ) -> Result < bool , Error > {
397+ pub async fn is_dpns_name_available ( & self , name : & str ) -> Result < bool , Error > {
374398 use crate :: platform:: documents:: document_query:: DocumentQuery ;
375399 use drive:: query:: WhereClause ;
376400 use drive:: query:: WhereOperator ;
377401
378- let dpns_contract = self . fetch_dpns_contract ( ) . await ?;
402+ let normalized_label = normalize_dpns_label ( name) ;
403+
404+ // An empty normalized label (e.g. `""`, `".dash"`, `".DASH"`) is not
405+ // a registrable DPNS name, so report it as unavailable rather than
406+ // doing a network round-trip that would query for
407+ // `normalizedLabel == ""`. This mirrors the early-return guard in
408+ // `resolve_dpns_name` so the two APIs agree on malformed input.
409+ if normalized_label. is_empty ( ) {
410+ return Ok ( false ) ;
411+ }
379412
380- let normalized_label = convert_to_homograph_safe_chars ( label ) ;
413+ let dpns_contract = self . fetch_dpns_contract ( ) . await ? ;
381414
382415 // Query for existing domain with this label
383416 let query = DocumentQuery {
@@ -422,29 +455,13 @@ impl Sdk {
422455
423456 let dpns_contract = self . fetch_dpns_contract ( ) . await ?;
424457
425- // Extract label from full name if needed
426- // Handle both "alice" and "alice.dash" formats
427- let label = if let Some ( dot_pos) = name. rfind ( '.' ) {
428- let ( label_part, suffix) = name. split_at ( dot_pos) ;
429- // Strip ".dash" / ".DASH" / mixed case — DPNS itself is case-insensitive.
430- if suffix. eq_ignore_ascii_case ( ".dash" ) {
431- label_part
432- } else {
433- // If it's not ".dash", treat the whole thing as the label
434- name
435- }
436- } else {
437- // No dot found, use the whole name as the label
438- name
439- } ;
458+ let normalized_label = normalize_dpns_label ( name) ;
440459
441- // Validate the label before proceeding
442- if label . is_empty ( ) {
460+ // Validate the normalized label before proceeding
461+ if normalized_label . is_empty ( ) {
443462 return Ok ( None ) ;
444463 }
445464
446- let normalized_label = convert_to_homograph_safe_chars ( label) ;
447-
448465 // Query for domain with this label
449466 let query = DocumentQuery {
450467 data_contract : dpns_contract,
@@ -499,6 +516,40 @@ mod tests {
499516 assert_eq ! ( convert_to_homograph_safe_chars( "test123" ) , "test123" ) ;
500517 }
501518
519+ #[ test]
520+ fn test_normalize_dpns_label_strips_dash_suffix_case_insensitively ( ) {
521+ // Bare label and full name normalize to the same value, regardless
522+ // of the case of the .dash suffix. This is the contract that
523+ // `is_dpns_name_available` and `resolve_dpns_name` share so that
524+ // queries against `normalizedLabel` agree.
525+ let expected = "a11ce" ;
526+ assert_eq ! ( normalize_dpns_label( "alice" ) , expected) ;
527+ assert_eq ! ( normalize_dpns_label( "alice.dash" ) , expected) ;
528+ assert_eq ! ( normalize_dpns_label( "alice.DASH" ) , expected) ;
529+ assert_eq ! ( normalize_dpns_label( "Alice.DaSh" ) , expected) ;
530+ assert_eq ! ( normalize_dpns_label( "ALICE.DASH" ) , expected) ;
531+
532+ // Non-.dash suffixes are not stripped (they are treated as part of
533+ // the label and normalized whole).
534+ assert_eq ! ( normalize_dpns_label( "alice.eth" ) , "a11ce.eth" ) ;
535+
536+ // Empty / suffix-only inputs normalize to an empty label.
537+ assert_eq ! ( normalize_dpns_label( "" ) , "" ) ;
538+ assert_eq ! ( normalize_dpns_label( ".dash" ) , "" ) ;
539+ assert_eq ! ( normalize_dpns_label( ".DASH" ) , "" ) ;
540+ }
541+
542+ #[ test]
543+ fn test_extract_dpns_label ( ) {
544+ assert_eq ! ( extract_dpns_label( "alice.dash" ) , "alice" ) ;
545+ assert_eq ! ( extract_dpns_label( "alice.DASH" ) , "alice" ) ;
546+ assert_eq ! ( extract_dpns_label( "alice.DaSh" ) , "alice" ) ;
547+ assert_eq ! ( extract_dpns_label( "Alice.DASH" ) , "Alice" ) ;
548+ assert_eq ! ( extract_dpns_label( "alice" ) , "alice" ) ;
549+ assert_eq ! ( extract_dpns_label( "alice.eth" ) , "alice.eth" ) ;
550+ assert_eq ! ( extract_dpns_label( ".dash" ) , "" ) ;
551+ }
552+
502553 #[ test]
503554 fn test_is_valid_username ( ) {
504555 // Valid usernames
0 commit comments