pallet_handles/
lib.rs

1//! Unique human-readable strings for MSAs
2//!
3//! ## Quick Links
4//! - [Configuration: `Config`](Config)
5//! - [Extrinsics: `Call`](Call)
6//! - [Runtime API: `HandlesRuntimeApi`](../pallet_handles_runtime_api/trait.HandlesRuntimeApi.html)
7//! - [Custom RPC API: `HandlesApiServer`](../pallet_handles_rpc/trait.HandlesApiServer.html)
8//! - [Event Enum: `Event`](Event)
9//! - [Error Enum: `Error`](Error)
10#![doc = include_str!("../README.md")]
11//!
12//! ## Shuffled Sequences
13//!
14//! To limit the human value of low or particular suffixes, the pallet uses a shuffled sequence to choose a semi-randon suffix for a specific canonical handle string.
15//! The shuffle only requires storage of the current index, same as an ordered suffix system.
16//! The computational cost is greater the larger the index grows as it is lazy evaluation of the Rand32 given a deterministic seed generated from the canonical handle string.
17//! See more details at [`handles_utils::suffix`]
18//!
19// Substrate macros are tripping the clippy::expect_used lint.
20#![allow(clippy::expect_used)]
21#![cfg_attr(not(feature = "std"), no_std)]
22// Strong Documentation Lints
23#![deny(
24	rustdoc::broken_intra_doc_links,
25	rustdoc::missing_crate_level_docs,
26	rustdoc::invalid_codeblock_attributes,
27	missing_docs
28)]
29extern crate alloc;
30
31use alloc::{string::String, vec};
32
33#[cfg(feature = "runtime-benchmarks")]
34mod benchmarking;
35
36use handles_utils::{
37	converter::{convert_to_canonical, split_display_name, HANDLE_DELIMITER},
38	suffix::generate_unique_suffixes,
39	validator::{
40		consists_of_supported_unicode_character_sets, contains_blocked_characters,
41		is_reserved_canonical_handle,
42	},
43};
44
45#[cfg(test)]
46mod tests;
47
48use alloc::vec::Vec;
49#[cfg(feature = "runtime-benchmarks")]
50use common_primitives::benchmarks::MsaBenchmarkHelper;
51use common_primitives::{
52	handles::*,
53	msa::{MessageSourceId, MsaLookup, MsaValidator},
54	node::EIP712Encode,
55};
56use frame_support::{dispatch::DispatchResult, ensure, pallet_prelude::*, traits::Get};
57use frame_system::pallet_prelude::*;
58use numtoa::*;
59pub use pallet::*;
60use sp_core::crypto::AccountId32;
61use sp_runtime::{traits::Convert, DispatchError, MultiSignature};
62
63pub mod handles_signed_extension;
64
65use common_runtime::signature::check_signature;
66
67pub mod weights;
68pub use weights::*;
69
70impl<T: Config> HandleProvider for Pallet<T> {
71	fn get_handle_for_msa(msa_id: MessageSourceId) -> Option<HandleResponse> {
72		Self::get_handle_for_msa(msa_id)
73	}
74}
75
76#[frame_support::pallet]
77pub mod pallet {
78
79	use handles_utils::trim_and_collapse_whitespace;
80
81	use super::*;
82	#[pallet::config]
83	pub trait Config: frame_system::Config {
84		/// The overarching event type.
85		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
86
87		/// Weight information for extrinsics in this pallet.
88		type WeightInfo: WeightInfo;
89
90		/// AccountId truncated to 32 bytes
91		type ConvertIntoAccountId32: Convert<Self::AccountId, AccountId32>;
92
93		/// A type that will supply MSA related information
94		type MsaInfoProvider: MsaLookup + MsaValidator<AccountId = Self::AccountId>;
95
96		/// The minimum suffix value
97		#[pallet::constant]
98		type HandleSuffixMin: Get<SuffixRangeType>;
99
100		/// The maximum suffix value
101		#[pallet::constant]
102		type HandleSuffixMax: Get<SuffixRangeType>;
103
104		/// The number of blocks before a signature can be ejected from the PayloadSignatureRegistryList
105		#[pallet::constant]
106		type MortalityWindowSize: Get<u32>;
107
108		#[cfg(feature = "runtime-benchmarks")]
109		/// A set of helper functions for benchmarking.
110		type MsaBenchmarkHelper: MsaBenchmarkHelper<Self::AccountId>;
111	}
112
113	// Storage
114	#[pallet::pallet]
115	pub struct Pallet<T>(_);
116
117	/// - Keys: k1: `CanonicalBase`, k2: `HandleSuffix`
118	/// - Value: `MessageSourceId`
119	#[pallet::storage]
120	pub type CanonicalBaseHandleAndSuffixToMSAId<T: Config> = StorageDoubleMap<
121		_,
122		Blake2_128Concat,
123		CanonicalBase,
124		Twox64Concat,
125		HandleSuffix,
126		MessageSourceId,
127		OptionQuery,
128	>;
129
130	/// - Key: `MessageSourceId`
131	/// - Value: `DisplayHandle`
132	#[pallet::storage]
133	pub type MSAIdToDisplayName<T: Config> = StorageMap<
134		_,
135		Twox64Concat,
136		MessageSourceId,
137		(DisplayHandle, BlockNumberFor<T>),
138		OptionQuery,
139	>;
140
141	/// - Key: `CanonicalBase`
142	/// - Value: (Sequence Index, Suffix Min)
143	/// - Sequence Index: The index of the next suffix to be used for this handle
144	/// - Suffix Min: The minimum suffix value for this handle
145	#[pallet::storage]
146	pub type CanonicalBaseHandleToSuffixIndex<T: Config> = StorageMap<
147		_,
148		Blake2_128Concat,
149		CanonicalBase,
150		(SequenceIndex, SuffixRangeType),
151		OptionQuery,
152	>;
153
154	#[derive(PartialEq, Eq)] // for testing
155	#[pallet::error]
156	pub enum Error<T> {
157		/// Invalid handle encoding (should be UTF-8)
158		InvalidHandleEncoding,
159		/// Invalid handle byte length
160		InvalidHandleByteLength,
161		/// Invalid handle character length
162		InvalidHandleCharacterLength,
163		/// The handle name is reserved for chain use only
164		HandleIsNotAllowed,
165		/// The handle contains characters that are not allowed
166		HandleContainsBlockedCharacters,
167		/// The handle does not contain characters in the supported ranges of unicode characters
168		HandleDoesNotConsistOfSupportedCharacterSets,
169		/// Suffixes exhausted
170		SuffixesExhausted,
171		/// Invalid MSA
172		InvalidMessageSourceAccount,
173		/// Cryptographic signature failed verification
174		InvalidSignature,
175		/// The MSA already has a handle
176		MSAHandleAlreadyExists,
177		/// The MSA does not have a handle needed to retire it.
178		MSAHandleDoesNotExist,
179		/// The submitted proof has expired; the current block is less the expiration block
180		ProofHasExpired,
181		/// The submitted proof expiration block is too far in the future
182		ProofNotYetValid,
183		/// The handle is still in mortaility window
184		HandleWithinMortalityPeriod,
185		/// The handle is invalid
186		InvalidHandle,
187	}
188
189	#[pallet::event]
190	#[pallet::generate_deposit(pub (super) fn deposit_event)]
191	pub enum Event<T: Config> {
192		/// Deposited when a handle is claimed. [MSA id, display handle in UTF-8 bytes]
193		HandleClaimed {
194			/// MSA id of handle owner
195			msa_id: MessageSourceId,
196			/// UTF-8 string in bytes of the display handle
197			handle: Vec<u8>,
198		},
199		/// Deposited when a handle is retired. [MSA id, display handle in UTF-8 bytes]
200		HandleRetired {
201			/// MSA id of handle owner
202			msa_id: MessageSourceId,
203			/// UTF-8 string in bytes
204			handle: Vec<u8>,
205		},
206	}
207
208	impl<T: Config> Pallet<T> {
209		/// Gets the next suffix index for a canonical base.
210		///
211		/// This function takes a canonical base as input and returns the next available
212		/// suffix index for that handle. If no current suffix index is found for the handle,
213		/// it starts from 0. If a current suffix index is found, it increments it by 1 to
214		/// get the next suffix index. If the increment operation fails due to overflow, it
215		/// returns an error indicating that the suffixes are exhausted.
216		///
217		/// # Arguments
218		///
219		/// * `canonical_base` - The canonical base to get the next suffix index for.
220		///
221		/// # Returns
222		///
223		/// * `Ok(SequenceIndex)` - The next suffix index for the canonical base.
224		/// * `Err(DispatchError)` - The suffixes are exhausted.
225		pub fn get_next_suffix_index_for_canonical_handle(
226			canonical_base: CanonicalBase,
227		) -> Result<SequenceIndex, DispatchError> {
228			let next: SequenceIndex;
229			match CanonicalBaseHandleToSuffixIndex::<T>::get(canonical_base) {
230				None => {
231					next = 0;
232				},
233				Some((current, suffix_min)) => {
234					let current_suffix_min = T::HandleSuffixMin::get();
235					// if saved suffix min has changed, reset the suffix index
236					if current_suffix_min != suffix_min {
237						next = 0;
238					} else {
239						next = current.checked_add(1).ok_or(Error::<T>::SuffixesExhausted)?;
240					}
241				},
242			}
243
244			Ok(next)
245		}
246
247		/// Verifies that the signature is not expired.
248		///
249		/// This function takes a signature expiration block as input and verifies that
250		/// the signature is not expired. It returns an error if the signature is expired.
251		///
252		/// # Arguments
253		///
254		/// * `signature_expires_at` - The block number at which the signature expires.
255		///
256		/// # Returns
257		///
258		/// * `Ok(())` - The signature is not expired.
259		/// * `Err(DispatchError)` - The signature is expired.
260		pub fn verify_signature_mortality(
261			signature_expires_at: BlockNumberFor<T>,
262		) -> DispatchResult {
263			let current_block = frame_system::Pallet::<T>::block_number();
264
265			let mortality_period = Self::mortality_block_limit(current_block);
266			ensure!(mortality_period > signature_expires_at, Error::<T>::ProofNotYetValid);
267			ensure!(current_block < signature_expires_at, Error::<T>::ProofHasExpired);
268			Ok(())
269		}
270
271		/// Verifies the signature of a given payload, using the provided `signature`
272		/// and `signer` information.
273		///
274		/// # Arguments
275		///
276		/// * `signature` - The `MultiSignature` to verify against the payload.
277		/// * `signer` - The `T::AccountId` of the signer that signed the payload.
278		/// * `payload` - The payload to verify the signature against.
279		///
280		/// # Errors
281		/// * [`Error::InvalidSignature`]
282		pub fn verify_signed_payload<P>(
283			signature: &MultiSignature,
284			signer: &T::AccountId,
285			payload: &P,
286		) -> DispatchResult
287		where
288			P: Encode + EIP712Encode,
289		{
290			let key = T::ConvertIntoAccountId32::convert(signer.clone());
291
292			ensure!(check_signature(signature, key, payload), Error::<T>::InvalidSignature);
293
294			Ok(())
295		}
296
297		/// Verifies max user handle size in bytes to address potential panic condition
298		///
299		/// # Arguments
300		///
301		/// * `handle` - The user handle.
302		///
303		/// # Errors
304		/// * [`Error::InvalidHandleByteLength`]
305		pub fn verify_max_handle_byte_length(handle: Vec<u8>) -> DispatchResult {
306			ensure!(handle.len() as u32 <= HANDLE_BYTES_MAX, Error::<T>::InvalidHandleByteLength);
307			Ok(())
308		}
309
310		/// The furthest in the future a mortality_block value is allowed
311		/// to be for current_block
312		/// This is calculated to be past the risk of a replay attack
313		fn mortality_block_limit(current_block: BlockNumberFor<T>) -> BlockNumberFor<T> {
314			let mortality_size = T::MortalityWindowSize::get();
315			current_block + BlockNumberFor::<T>::from(mortality_size)
316		}
317		/// Generates a suffix for a canonical base, using the provided `canonical_base`
318		/// and `cursor` information.
319		///
320		/// # Arguments
321		///
322		/// * `canonical_base` - The canonical base to generate a suffix for.
323		/// * `cursor` - The cursor position in the sequence of suffixes to use for generation.
324		///
325		/// # Returns
326		///
327		/// The generated suffix as a `u16`.
328		///
329		pub fn generate_suffix_for_canonical_handle(
330			canonical_base: &str,
331			cursor: usize,
332		) -> Result<u16, DispatchError> {
333			let mut lazy_suffix_sequence = generate_unique_suffixes(
334				T::HandleSuffixMin::get(),
335				T::HandleSuffixMax::get(),
336				canonical_base,
337			);
338			match lazy_suffix_sequence.nth(cursor) {
339				Some(suffix) => Ok(suffix),
340				None => Err(Error::<T>::SuffixesExhausted.into()),
341			}
342		}
343	}
344
345	#[pallet::call]
346	impl<T: Config> Pallet<T> {
347		/// Claims a handle for a caller's MSA (Message Source Account) based on the provided payload.
348		/// This function performs several validations before claiming the handle, including checking
349		/// the size of the base_handle, ensuring the caller have valid MSA keys,
350		/// verifying the payload signature, and finally calling the internal `do_claim_handle` function
351		/// to claim the handle.
352		///
353		/// # Arguments
354		///
355		/// * `origin` - The origin of the caller.
356		/// * `msa_owner_key` - The public key of the MSA owner.
357		/// * `proof` - The multi-signature proof for the payload.
358		/// * `payload` - The payload containing the information needed to claim a new handle.
359		///
360		/// # Errors
361		///
362		/// This function may return an error as part of `DispatchResult` if any of the following
363		/// validations fail:
364		///
365		/// * [`Error::InvalidHandleByteLength`] - The base_handle size exceeds the maximum allowed size.
366		/// * [`Error::InvalidMessageSourceAccount`] - The caller does not have a valid  `MessageSourceId`.
367		/// * [`Error::InvalidSignature`] - The payload signature verification fails.
368		///
369		/// # Events
370		/// * [`Event::HandleClaimed`]
371		///
372		#[pallet::call_index(0)]
373		#[pallet::weight(T::WeightInfo::claim_handle(payload.base_handle.len() as u32))]
374		pub fn claim_handle(
375			origin: OriginFor<T>,
376			msa_owner_key: T::AccountId,
377			proof: MultiSignature,
378			payload: ClaimHandlePayload<BlockNumberFor<T>>,
379		) -> DispatchResult {
380			let _ = ensure_signed(origin)?;
381
382			// Validation: Check for base_handle size to address potential panic condition
383			Self::verify_max_handle_byte_length(payload.base_handle.clone())?;
384
385			// Validation: caller must already have a MSA id
386			let msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&msa_owner_key)
387				.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
388
389			// Validation: The signature is within the mortality window
390			Self::verify_signature_mortality(payload.expiration)?;
391
392			// Validation: Verify the payload was signed
393			Self::verify_signed_payload(&proof, &msa_owner_key, &payload)?;
394
395			let display_handle = Self::do_claim_handle(msa_id, payload)?;
396
397			Self::deposit_event(Event::HandleClaimed { msa_id, handle: display_handle.clone() });
398			Ok(())
399		}
400
401		/// Retire a handle for a given `DisplayHandle` owner.
402		///
403		/// # Arguments
404		///
405		/// * `origin` - The origin of the call.
406		///
407		/// # Errors
408		///
409		/// This function can return the following errors:
410		///
411		/// * `InvalidHandleByteLength` - If the length of the `payload.display_handle` exceeds the maximum allowed size.
412		/// * `InvalidMessageSourceAccount` - If caller of this extrinsic does not have a valid MSA (Message Source Account) ID.
413		///
414		/// # Events
415		/// * [`Event::HandleRetired`]
416		///
417		#[pallet::call_index(1)]
418		#[pallet::weight((T::WeightInfo::retire_handle(), DispatchClass::Normal, Pays::No))]
419		pub fn retire_handle(origin: OriginFor<T>) -> DispatchResult {
420			let msa_owner_key = ensure_signed(origin)?;
421
422			// Validation: The caller must already have a MSA id
423			let msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&msa_owner_key)
424				.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
425
426			let display_handle: Vec<u8> = Self::do_retire_handle(msa_id)?;
427
428			Self::deposit_event(Event::HandleRetired { msa_id, handle: display_handle });
429			Ok(())
430		}
431
432		/// Changes the handle for a caller's MSA (Message Source Account) based on the provided payload.
433		/// This function performs several validations before claiming the handle, including checking
434		/// the size of the base_handle, ensuring the caller has valid MSA keys,
435		/// verifying the payload signature, and finally calling the internal `do_retire_handle` and `do_claim_handle` functions
436		/// to retire the current handle and claim the new one.
437		///
438		/// # Arguments
439		///
440		/// * `origin` - The origin of the caller.
441		/// * `msa_owner_key` - The public key of the MSA owner.
442		/// * `proof` - The multi-signature proof for the payload.
443		/// * `payload` - The payload containing the information needed to change an existing handle.
444		///
445		/// # Errors
446		///
447		/// This function may return an error as part of `DispatchResult` if any of the following
448		/// validations fail:
449		///
450		/// * [`Error::InvalidHandleByteLength`] - The base_handle size exceeds the maximum allowed size.
451		/// * [`Error::InvalidMessageSourceAccount`] - The caller does not have a valid  `MessageSourceId`.
452		/// * [`Error::InvalidSignature`] - The payload signature verification fails.
453		///
454		/// # Events
455		/// * [`Event::HandleRetired`]
456		/// * [`Event::HandleClaimed`]
457		///
458		#[pallet::call_index(2)]
459		#[pallet::weight(T::WeightInfo::change_handle(payload.base_handle.len() as u32))]
460		pub fn change_handle(
461			origin: OriginFor<T>,
462			msa_owner_key: T::AccountId,
463			proof: MultiSignature,
464			payload: ClaimHandlePayload<BlockNumberFor<T>>,
465		) -> DispatchResult {
466			let _ = ensure_signed(origin)?;
467
468			// Validation: Check for base_handle size to address potential panic condition
469			Self::verify_max_handle_byte_length(payload.base_handle.clone())?;
470
471			// Validation: caller must already have a MSA id
472			let msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&msa_owner_key)
473				.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
474
475			// Validation: The signature is within the mortality window
476			Self::verify_signature_mortality(payload.expiration)?;
477
478			// Validation: Verify the payload was signed
479			Self::verify_signed_payload(&proof, &msa_owner_key, &payload)?;
480
481			// Get existing handle to retire
482			MSAIdToDisplayName::<T>::get(msa_id).ok_or(Error::<T>::MSAHandleDoesNotExist)?;
483
484			// Retire old handle
485			let old_display_handle: Vec<u8> = Self::do_retire_handle(msa_id)?;
486			Self::deposit_event(Event::HandleRetired { msa_id, handle: old_display_handle });
487
488			let display_handle = Self::do_claim_handle(msa_id, payload.clone())?;
489
490			Self::deposit_event(Event::HandleClaimed { msa_id, handle: display_handle.clone() });
491
492			Ok(())
493		}
494	}
495
496	impl<T: Config> Pallet<T> {
497		/// Retrieves a handle for a given MSA (MessageSourceId).
498		///
499		/// # Arguments
500		///
501		/// * `msa_id` - The MSA ID to retrieve the handle for.
502		///
503		/// # Returns
504		///
505		/// * `Option<HandleResponse>` - The handle response if the MSA ID is valid.
506		///
507		pub fn get_handle_for_msa(msa_id: MessageSourceId) -> Option<HandleResponse> {
508			let display_handle = match MSAIdToDisplayName::<T>::get(msa_id) {
509				Some((handle, _)) => handle,
510				_ => return None,
511			};
512			// convert to string
513			let display_handle_str = core::str::from_utf8(&display_handle)
514				.map_err(|_| Error::<T>::InvalidHandleEncoding)
515				.ok()?;
516
517			let (base_handle_str, suffix) = split_display_name(display_handle_str)?;
518			let base_handle = base_handle_str.as_bytes().to_vec();
519			// Convert base handle into a canonical base
520			let (_, canonical_base) =
521				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
522			Some(HandleResponse { base_handle, suffix, canonical_base: canonical_base.into() })
523		}
524
525		/// Get the next available suffixes for a given handle.
526		///
527		/// This function takes a `Vec<u8>` handle and generates the next available
528		/// suffixes for that handle. The number of suffixes to generate is determined
529		/// by the `count` parameter, which is of type `u16`.
530		///
531		/// # Arguments
532		///
533		/// * `handle` - The handle to generate the next available suffixes for.
534		/// * `count` - The number of suffixes to generate.
535		///
536		/// # Returns
537		///
538		/// * `PresumptiveSuffixesResponse` - The response containing the next available suffixes.
539		///
540		pub fn get_next_suffixes(
541			base_handle: BaseHandle,
542			count: u16,
543		) -> PresumptiveSuffixesResponse {
544			let base_handle_str = core::str::from_utf8(&base_handle).unwrap_or_default();
545
546			// Convert base handle into a canonical base
547			let (canonical_handle_str, canonical_base) =
548				Self::get_canonical_string_vec_from_base_handle(base_handle_str);
549
550			let suffix_index =
551				Self::get_next_suffix_index_for_canonical_handle(canonical_base.clone())
552					.unwrap_or_default();
553
554			let lazy_suffix_sequence = generate_unique_suffixes(
555				T::HandleSuffixMin::get(),
556				T::HandleSuffixMax::get(),
557				&canonical_handle_str,
558			)
559			.enumerate();
560
561			let mut suffixes: Vec<HandleSuffix> = vec![];
562
563			for (i, suffix) in lazy_suffix_sequence {
564				if i >= suffix_index as usize && i < (suffix_index as usize + count as usize) {
565					suffixes.push(suffix);
566				}
567				if suffixes.len() == count as usize {
568					break;
569				}
570			}
571			PresumptiveSuffixesResponse { base_handle: base_handle.into(), suffixes }
572		}
573
574		/// Check a base handle for validity and collect information on it
575		///
576		/// This function takes a `Vec<u8>` handle and checks to make sure it is:
577		/// - Valid
578		/// - Has suffixes remaining
579		///
580		/// It also returns the original input as well as the canonical version
581		///
582		/// # Arguments
583		///
584		/// * `handle` - The handle to check.
585		///
586		/// # Returns
587		///
588		/// * `CheckHandleResponse`
589		///
590		pub fn check_handle(base_handle: Vec<u8>) -> CheckHandleResponse {
591			let valid = Self::validate_handle(base_handle.to_vec());
592
593			if !valid {
594				return CheckHandleResponse { base_handle, ..Default::default() };
595			}
596
597			let base_handle_str = core::str::from_utf8(&base_handle).unwrap_or_default();
598
599			let base_handle_trimmed = trim_and_collapse_whitespace(base_handle_str);
600
601			// Convert base handle into a canonical base
602			let (_canonical_handle_str, canonical_base) =
603				Self::get_canonical_string_vec_from_base_handle(base_handle_str);
604
605			let suffix_index =
606				Self::get_next_suffix_index_for_canonical_handle(canonical_base.clone())
607					.unwrap_or_default();
608
609			let suffixes_available = suffix_index < T::HandleSuffixMax::get();
610
611			CheckHandleResponse {
612				base_handle: base_handle_trimmed.into(),
613				suffix_index,
614				suffixes_available,
615				valid,
616				canonical_base: canonical_base.into(),
617			}
618		}
619
620		/// Retrieve a `MessageSourceId` for a given handle.
621		///
622		/// # Arguments
623		///
624		/// * `display_handle` - The full display handle to retrieve the `MessageSourceId` for.
625		///
626		/// # Returns
627		///
628		/// * `Option<MessageSourceId>` - The `MessageSourceId` if the handle is valid.
629		///
630		pub fn get_msa_id_for_handle(display_handle: DisplayHandle) -> Option<MessageSourceId> {
631			let display_handle_str = core::str::from_utf8(&display_handle).unwrap_or_default();
632			let (base_handle_str, suffix) = split_display_name(display_handle_str)?;
633			// Convert base handle into a canonical base
634			let (_, canonical_base) =
635				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
636			CanonicalBaseHandleAndSuffixToMSAId::<T>::get(canonical_base, suffix)
637		}
638
639		/// Claims a handle for a given MSA (MessageSourceId) by validating and storing the base handle,
640		/// generating a canonical base, generating a suffix, and composing the full display handle.
641		///
642		/// # Arguments
643		///
644		/// * `msa_id` - The MSA (MessageSourceId) to claim the handle for.
645		/// * `payload` - The payload containing the base handle to claim.
646		///
647		/// # Returns
648		///
649		/// * `DisplayHandle` - The full display handle.
650		///
651		pub fn do_claim_handle(
652			msa_id: MessageSourceId,
653			payload: ClaimHandlePayload<BlockNumberFor<T>>,
654		) -> Result<Vec<u8>, DispatchError> {
655			// Validation: The MSA must not already have a handle associated with it
656			ensure!(
657				MSAIdToDisplayName::<T>::get(msa_id).is_none(),
658				Error::<T>::MSAHandleAlreadyExists
659			);
660
661			let base_handle_str = Self::validate_base_handle(payload.base_handle)?;
662
663			// Convert base handle into a canonical base
664			let (canonical_handle_str, canonical_base) =
665				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
666
667			Self::validate_canonical_handle(&canonical_handle_str)?;
668
669			// Generate suffix from the next available index into the suffix sequence
670			let suffix_sequence_index =
671				Self::get_next_suffix_index_for_canonical_handle(canonical_base.clone())?;
672
673			let suffix = Self::generate_suffix_for_canonical_handle(
674				&canonical_handle_str,
675				suffix_sequence_index as usize,
676			)?;
677
678			let display_handle = Self::build_full_display_handle(&base_handle_str, suffix)?;
679
680			// Store canonical base and suffix to MSA id
681			CanonicalBaseHandleAndSuffixToMSAId::<T>::insert(
682				canonical_base.clone(),
683				suffix,
684				msa_id,
685			);
686			// Store canonical base to suffix sequence index
687			CanonicalBaseHandleToSuffixIndex::<T>::set(
688				canonical_base.clone(),
689				Some((suffix_sequence_index, T::HandleSuffixMin::get())),
690			);
691
692			// Store the full display handle to MSA id
693			MSAIdToDisplayName::<T>::insert(msa_id, (display_handle.clone(), payload.expiration));
694
695			Ok(display_handle.into_inner())
696		}
697
698		/// Checks that handle base string is valid before canonicalization
699		fn validate_base_handle(base_handle: Vec<u8>) -> Result<String, DispatchError> {
700			// Convert base handle to UTF-8 string slice while validating.
701			let base_handle_str =
702				String::from_utf8(base_handle).map_err(|_| Error::<T>::InvalidHandleEncoding)?;
703
704			// Validation: The handle length must be valid.
705			// Note: the count() can panic but won't because the base_handle byte length is already checked
706			let len = base_handle_str.chars().count() as u32;
707
708			// Validation: `BaseHandle` character length must be within range
709			ensure!(
710				(HANDLE_CHARS_MIN..=HANDLE_CHARS_MAX).contains(&len),
711				Error::<T>::InvalidHandleCharacterLength
712			);
713
714			ensure!(
715				!contains_blocked_characters(&base_handle_str),
716				Error::<T>::HandleContainsBlockedCharacters
717			);
718			Ok(base_handle_str)
719		}
720
721		/// Validate that the canonical handle:
722		/// - contains characters ONLY in supported ranges
723		/// - Does NOT contain reserved words
724		/// - Is between (inclusive) `HANDLE_CHARS_MIN` and `HANDLE_CHARS_MAX`
725		fn validate_canonical_handle(canonical_base_handle_str: &str) -> DispatchResult {
726			// Validation: The handle must consist of characters not containing reserved words or blocked characters
727			ensure!(
728				consists_of_supported_unicode_character_sets(canonical_base_handle_str),
729				Error::<T>::HandleDoesNotConsistOfSupportedCharacterSets
730			);
731			ensure!(
732				!is_reserved_canonical_handle(canonical_base_handle_str),
733				Error::<T>::HandleIsNotAllowed
734			);
735
736			let len = canonical_base_handle_str.chars().count() as u32;
737
738			// Validation: `Canonical` character length must be within range
739			ensure!(
740				(HANDLE_CHARS_MIN..=HANDLE_CHARS_MAX).contains(&len),
741				Error::<T>::InvalidHandleCharacterLength
742			);
743
744			Ok(())
745		}
746
747		/// Creates a full display handle by combining a base handle string with supplied suffix
748		///
749		/// # Arguments
750		///
751		/// * `base_handle_str` - The base handle string.
752		/// * `suffix` - The numeric suffix .
753		///
754		/// # Returns
755		///
756		/// * `DisplayHandle` - The full display handle.
757		///
758		pub fn build_full_display_handle(
759			base_handle: &str,
760			suffix: HandleSuffix,
761		) -> Result<DisplayHandle, DispatchError> {
762			let base_handle_trimmed = trim_and_collapse_whitespace(base_handle);
763			let mut full_handle_vec: Vec<u8> = vec![];
764			full_handle_vec.extend(base_handle_trimmed.as_bytes());
765			full_handle_vec.push(HANDLE_DELIMITER as u8); // The delimiter
766			let mut buff = [0u8; SUFFIX_MAX_DIGITS];
767			full_handle_vec.extend(suffix.numtoa(10, &mut buff));
768			let res = full_handle_vec.try_into().map_err(|_| Error::<T>::InvalidHandleEncoding)?;
769			Ok(res)
770		}
771
772		/// Retires a handle associated with a given MessageSourceId (MSA) in the dispatch module.
773		///
774		/// # Arguments
775		///
776		/// * `msa_id` - The MSA (MessageSourceId) to retire the handle for.
777		///
778		/// # Returns
779		///
780		/// * `DispatchResult` - Returns `Ok` if the handle was successfully retired.
781		pub fn do_retire_handle(msa_id: MessageSourceId) -> Result<Vec<u8>, DispatchError> {
782			// Validation: The MSA must already have a handle associated with it
783			let handle_from_state =
784				MSAIdToDisplayName::<T>::get(msa_id).ok_or(Error::<T>::MSAHandleDoesNotExist)?;
785			let display_name_handle = handle_from_state.0;
786			let display_name_str = core::str::from_utf8(&display_name_handle)
787				.map_err(|_| Error::<T>::InvalidHandleEncoding)?;
788
789			let (base_handle_str, suffix_num) =
790				split_display_name(display_name_str).ok_or(Error::<T>::InvalidHandle)?;
791
792			// Convert base handle into a canonical base
793			let (_, canonical_base) =
794				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
795
796			// Remove handle from storage but not from CanonicalBaseHandleToSuffixIndex because retired handles can't be reused
797			MSAIdToDisplayName::<T>::remove(msa_id);
798			CanonicalBaseHandleAndSuffixToMSAId::<T>::remove(canonical_base, suffix_num);
799
800			Ok(display_name_str.as_bytes().to_vec())
801		}
802
803		/// Checks whether the supplied handle passes all the checks performed by a
804		/// claim_handle call.
805		/// # Returns
806		/// * true if it is valid or false if invalid
807		pub fn validate_handle(handle: Vec<u8>) -> bool {
808			match Self::validate_base_handle(handle) {
809				Ok(base_handle_str) => {
810					// Convert base handle into a canonical base
811					let (canonical_handle_str, _) =
812						Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
813
814					Self::validate_canonical_handle(&canonical_handle_str).is_ok()
815				},
816				_ => false,
817			}
818		}
819
820		/// Converts a base handle to a canonical base.
821		fn get_canonical_string_vec_from_base_handle(
822			base_handle_str: &str,
823		) -> (String, CanonicalBase) {
824			let canonical_handle_str = convert_to_canonical(base_handle_str);
825			let canonical_handle_vec = canonical_handle_str.as_bytes().to_vec();
826			let canonical_base: CanonicalBase = canonical_handle_vec.try_into().unwrap_or_default();
827			(canonical_handle_str, canonical_base)
828		}
829	}
830}