pallet_msa/
lib.rs

1//! Message Source Account Management
2//!
3//! ## Quick Links
4//! - [Configuration: `Config`](Config)
5//! - [Extrinsics: `Call`](Call)
6//! - [Runtime API: `MsaRuntimeApi`](../pallet_msa_runtime_api/trait.MsaRuntimeApi.html)
7//! - [Custom RPC API: `MsaApiServer`](../pallet_msa_rpc/trait.MsaApiServer.html)
8//! - [Event Enum: `Event`](Event)
9//! - [Error Enum: `Error`](Error)
10#![doc = include_str!("../README.md")]
11//!
12//! ## Implementations
13//!
14//! - [`MsaLookup`](../common_primitives/msa/trait.MsaLookup.html): Functions for accessing MSAs.
15//! - [`MsaValidator`](../common_primitives/msa/trait.MsaValidator.html): Functions for validating MSAs.
16//! - [`ProviderLookup`](../common_primitives/msa/trait.ProviderLookup.html): Functions for accessing Provider info.
17//! - [`DelegationValidator`](../common_primitives/msa/trait.DelegationValidator.html): Functions for validating delegations.
18//! - [`SchemaGrantValidator`](../common_primitives/msa/trait.SchemaGrantValidator.html): Functions for validating schema grants.
19//!
20//! ## Assumptions
21//!
22//! - Total MSA keys should be less than the constant `Config::MSA::MaxPublicKeysPerMsa`.
23//! - Maximum schemas, for which any single provider has publishing rights on behalf of a single user, must be less than `Config::MSA::MaxSchemaGrantsPerDelegation`
24#![allow(clippy::expect_used)]
25#![cfg_attr(not(feature = "std"), no_std)]
26// Strong Documentation Lints
27#![deny(
28	rustdoc::broken_intra_doc_links,
29	rustdoc::missing_crate_level_docs,
30	rustdoc::invalid_codeblock_attributes,
31	missing_docs
32)]
33
34extern crate alloc;
35use cid::Cid;
36use core::fmt::Debug;
37use frame_support::{
38	dispatch::{DispatchInfo, DispatchResult, PostDispatchInfo},
39	pallet_prelude::*,
40	traits::{
41		tokens::{
42			fungible::{Inspect as InspectFungible, Mutate},
43			Fortitude, Preservation,
44		},
45		Get, IsSubType,
46	},
47};
48use lazy_static::lazy_static;
49use parity_scale_codec::{Decode, Encode};
50
51use common_runtime::signature::check_signature;
52
53#[cfg(feature = "runtime-benchmarks")]
54use common_primitives::benchmarks::{MsaBenchmarkHelper, RegisterProviderBenchmarkHelper};
55
56use alloc::{boxed::Box, vec, vec::Vec};
57use common_primitives::{
58	capacity::TargetValidator,
59	cid::compute_cid_v1,
60	handles::HandleProvider,
61	msa::*,
62	node::{EIP712Encode, ProposalProvider},
63	schema::{SchemaId, SchemaValidator},
64	signatures::{AccountAddressMapper, EthereumAddressMapper},
65};
66use frame_system::pallet_prelude::*;
67pub use pallet::*;
68use scale_info::TypeInfo;
69use sp_core::crypto::AccountId32;
70use sp_io::hashing::keccak_256;
71#[allow(unused)]
72use sp_runtime::{
73	traits::{
74		AsSystemOriginSigner, BlockNumberProvider, Convert, DispatchInfoOf, Dispatchable,
75		PostDispatchInfoOf, TransactionExtension, ValidateResult, Zero,
76	},
77	ArithmeticError, DispatchError, MultiSignature, Weight,
78};
79pub use types::{
80	AddKeyData, AddProvider, ApplicationIndex, AuthorizedKeyData, PermittedDelegationSchemas,
81	RecoveryCommitment, RecoveryCommitmentPayload,
82};
83pub use weights::*;
84
85/// Offchain storage for MSA pallet
86pub mod offchain_storage;
87use crate::types::{LogoCid, PayloadTypeDiscriminator, RecoveryHash};
88pub use offchain_storage::*;
89
90#[cfg(feature = "runtime-benchmarks")]
91mod benchmarking;
92
93#[cfg(test)]
94mod tests;
95
96pub mod types;
97
98pub mod weights;
99
100/// Migrations
101#[allow(missing_docs)]
102pub mod migration;
103
104#[frame_support::pallet]
105pub mod pallet {
106	use crate::types::RecoveryHash;
107
108	use super::*;
109
110	#[pallet::config]
111	pub trait Config: frame_system::Config {
112		/// The overarching event type.
113		#[allow(deprecated)]
114		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
115
116		/// Weight information for extrinsics in this pallet.
117		type WeightInfo: WeightInfo;
118
119		/// AccountId truncated to 32 bytes
120		type ConvertIntoAccountId32: Convert<Self::AccountId, AccountId32>;
121
122		/// Maximum count of keys allowed per MSA
123		#[pallet::constant]
124		type MaxPublicKeysPerMsa: Get<u8>;
125
126		/// Maximum count of schemas granted for publishing data per Provider
127		#[pallet::constant]
128		type MaxSchemaGrantsPerDelegation: Get<u32>;
129
130		/// Maximum provider name size allowed per MSA association
131		#[pallet::constant]
132		type MaxProviderNameSize: Get<u32> + Clone + Debug + PartialEq + Eq;
133
134		/// A type that will supply schema related information.
135		type SchemaValidator: SchemaValidator<SchemaId>;
136
137		/// A type that will supply `Handle` related information.
138		type HandleProvider: HandleProvider;
139
140		/// The number of blocks before a signature can be ejected from the PayloadSignatureRegistryList
141		#[pallet::constant]
142		type MortalityWindowSize: Get<u32>;
143
144		/// The maximum number of signatures that can be stored in PayloadSignatureRegistryList.
145		#[pallet::constant]
146		type MaxSignaturesStored: Get<Option<u32>>;
147
148		/// The origin that is allowed to create providers via governance
149		type CreateProviderViaGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
150
151		/// The origin that is allowed to approve recovery providers
152		type RecoveryProviderApprovalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
153
154		/// The runtime call dispatch type.
155		type Proposal: Parameter
156			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
157			+ From<Call<Self>>;
158
159		/// The Council proposal provider interface
160		type ProposalProvider: ProposalProvider<Self::AccountId, Self::Proposal>;
161
162		/// Currency type for managing MSA account balances
163		type Currency: Mutate<Self::AccountId> + InspectFungible<Self::AccountId>;
164
165		/// Maximum language code size for a locale
166		#[pallet::constant]
167		type MaxLanguageCodeSize: Get<u32> + Clone + Debug + PartialEq + Eq;
168
169		/// Maximum logo size for a locale
170		#[pallet::constant]
171		type MaxLogoSize: Get<u32> + Clone + Debug + PartialEq + Eq;
172
173		/// Maximum cid size for a locale
174		#[pallet::constant]
175		type MaxLogoCidSize: Get<u32> + Clone + Debug + PartialEq + Eq;
176
177		/// Total number of locales supported
178		#[pallet::constant]
179		type MaxLocaleCount: Get<u32> + Clone + Debug + PartialEq + Eq;
180	}
181
182	/// Storage version for the MSA pallet.
183	pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
184
185	#[pallet::pallet]
186	#[pallet::storage_version(STORAGE_VERSION)]
187	pub struct Pallet<T>(_);
188
189	/// Storage type for the current MSA identifier maximum.
190	/// We need to track this value because the identifier maximum
191	/// is incremented each time a new identifier is created.
192	/// - Value: The current maximum MSA Id
193	#[pallet::storage]
194	pub type CurrentMsaIdentifierMaximum<T> = StorageValue<_, MessageSourceId, ValueQuery>;
195
196	/// Storage type for mapping the relationship between a Delegator and its Provider.
197	/// - Keys: Delegator MSA, Provider MSA
198	/// - Value: [`Delegation`](common_primitives::msa::Delegation)
199	#[pallet::storage]
200	pub type DelegatorAndProviderToDelegation<T: Config> = StorageDoubleMap<
201		_,
202		Twox64Concat,
203		DelegatorId,
204		Twox64Concat,
205		ProviderId,
206		Delegation<SchemaId, BlockNumberFor<T>, T::MaxSchemaGrantsPerDelegation>,
207		OptionQuery,
208	>;
209
210	/// Storage type for approved recovery providers
211	/// - Key: Provider MSA Id
212	/// - Value: [`bool`]
213	#[pallet::storage]
214	pub type RecoveryProviders<T: Config> =
215		StorageMap<_, Twox64Concat, ProviderId, bool, OptionQuery>;
216
217	/// Provider registration information
218	/// - Key: Provider MSA Id
219	/// - Value: [`ProviderRegistryEntry`](common_primitives::msa::ProviderRegistryEntry)
220	#[deprecated(
221		note = "Use ProviderToRegistryEntryV2 instead, this will removed from frequency version 1.17.6"
222	)]
223	#[pallet::storage]
224	pub type ProviderToRegistryEntry<T: Config> = StorageMap<
225		_,
226		Twox64Concat,
227		ProviderId,
228		crate::migration::v1::ProviderRegistryEntry<T::MaxProviderNameSize>,
229		OptionQuery,
230	>;
231
232	/// Provider registration information
233	/// - Key: Provider MSA Id
234	/// - Value: [`ProviderRegistryEntry`](common_primitives::msa::ProviderRegistryEntry)
235	#[pallet::storage]
236	pub type ProviderToRegistryEntryV2<T: Config> = StorageMap<
237		_,
238		Twox64Concat,
239		ProviderId,
240		ProviderRegistryEntry<
241			T::MaxProviderNameSize,
242			T::MaxLanguageCodeSize,
243			T::MaxLogoCidSize,
244			T::MaxLocaleCount,
245		>,
246		OptionQuery,
247	>;
248
249	/// Storage type for key to MSA information
250	/// - Key: AccountId
251	/// - Value: [`MessageSourceId`]
252	#[pallet::storage]
253	pub type PublicKeyToMsaId<T: Config> =
254		StorageMap<_, Twox64Concat, T::AccountId, MessageSourceId, OptionQuery>;
255
256	/// Storage type for a reference counter of the number of keys associated to an MSA
257	/// - Key: MSA Id
258	/// - Value: [`u8`] Counter of Keys associated with the MSA
259	#[pallet::storage]
260	pub(super) type PublicKeyCountForMsaId<T: Config> =
261		StorageMap<_, Twox64Concat, MessageSourceId, u8, ValueQuery>;
262
263	/// PayloadSignatureRegistryList is used to prevent replay attacks for extrinsics
264	/// that take an externally-signed payload.
265	/// For this to work, the payload must include a mortality block number, which
266	/// is used in lieu of a monotonically increasing nonce.
267	///
268	/// The list is forwardly linked. (Example has a list size of 3)
269	/// - signature, forward pointer -> n = new signature
270	/// - 1,2 -> 2,3 (oldest)
271	/// - 2,3 -> 3,4
272	/// - 3,4 -> 4,5
273	/// -   5 -> n (newest in pointer data)
274	/// ### Storage
275	/// - Key: Signature
276	/// - Value: Tuple
277	///     - `BlockNumber` when the keyed signature can be ejected from the registry
278	///     - [`MultiSignature`] the forward linked list pointer. This pointer is the next "newest" value.
279	#[pallet::storage]
280	pub(super) type PayloadSignatureRegistryList<T: Config> = StorageMap<
281		_,                                   // prefix
282		Twox64Concat,                        // hasher for key1
283		MultiSignature, // An externally-created Signature for an external payload, provided by an extrinsic
284		(BlockNumberFor<T>, MultiSignature), // An actual flipping block number and the oldest signature at write time
285		OptionQuery,                         // The type for the query
286		GetDefault,                          // OnEmpty return type, defaults to None
287		T::MaxSignaturesStored,              // Maximum total signatures to store
288	>;
289
290	/// This is the pointer for the Payload Signature Registry
291	/// Contains the pointers to the data stored in the `PayloadSignatureRegistryList`
292	/// - Value: [`SignatureRegistryPointer`]
293	#[pallet::storage]
294	pub(super) type PayloadSignatureRegistryPointer<T: Config> =
295		StorageValue<_, SignatureRegistryPointer<BlockNumberFor<T>>>;
296
297	/// A temporary storage for looking up relevant events from off-chain index
298	/// At the start of the next block this storage is set to 0
299	#[pallet::storage]
300	#[pallet::whitelist_storage]
301	pub(super) type OffchainIndexEventCount<T: Config> = StorageValue<_, u16, ValueQuery>;
302
303	/// Storage type for mapping MSA IDs to their Recovery Commitments
304	/// - Key: MessageSourceId
305	/// - Value: Recovery Commitment (32 bytes)
306	#[pallet::storage]
307	pub type MsaIdToRecoveryCommitment<T: Config> =
308		StorageMap<_, Twox64Concat, MessageSourceId, RecoveryCommitment, OptionQuery>;
309
310	/// Storage type for ApprovedLogos
311	/// - key: Logo Cid
312	/// - value: Logo bytes
313	#[pallet::storage]
314	#[pallet::getter(fn approved_logos)]
315	pub type ApprovedLogos<T: Config> =
316		StorageMap<_, Twox64Concat, LogoCid<T>, BoundedVec<u8, T::MaxLogoSize>, OptionQuery>;
317
318	/// Monotonically increasing index of applications for a given provider.
319	/// Starts at 0 when provider first gets an application approved.
320	#[pallet::storage]
321	#[pallet::getter(fn next_application_index)]
322	pub(super) type NextApplicationIndex<T: Config> =
323		StorageMap<_, Twox64Concat, ProviderId, ApplicationIndex, ValueQuery>;
324
325	/// Mapping of (ProviderId, ApplicationIndex) → Application details.
326	/// This is where the actual application data is stored.
327	#[pallet::storage]
328	#[pallet::getter(fn provider_applications)]
329	pub(super) type ProviderToApplicationRegistry<T: Config> = StorageDoubleMap<
330		_,
331		Twox64Concat,
332		ProviderId,
333		Twox64Concat,
334		ApplicationIndex,
335		ApplicationContext<
336			T::MaxProviderNameSize,
337			T::MaxLanguageCodeSize,
338			T::MaxLogoCidSize,
339			T::MaxLocaleCount,
340		>,
341		OptionQuery,
342	>;
343
344	#[pallet::event]
345	#[pallet::generate_deposit(pub (super) fn deposit_event)]
346	pub enum Event<T: Config> {
347		/// A new Message Service Account was created with a new MessageSourceId
348		MsaCreated {
349			/// The MSA for the Event
350			msa_id: MessageSourceId,
351
352			/// The key added to the MSA
353			key: T::AccountId,
354		},
355		/// An AccountId has been associated with a MessageSourceId
356		PublicKeyAdded {
357			/// The MSA for the Event
358			msa_id: MessageSourceId,
359
360			/// The key added to the MSA
361			key: T::AccountId,
362		},
363		/// An AccountId had all permissions revoked from its MessageSourceId
364		PublicKeyDeleted {
365			/// The key no longer approved for the associated MSA
366			key: T::AccountId,
367		},
368		/// A delegation relationship was added with the given provider and delegator
369		DelegationGranted {
370			/// The Provider MSA Id
371			provider_id: ProviderId,
372
373			/// The Delegator MSA Id
374			delegator_id: DelegatorId,
375		},
376		/// A Provider-MSA relationship was registered
377		ProviderCreated {
378			/// The MSA id associated with the provider
379			provider_id: ProviderId,
380		},
381		/// The Delegator revoked its delegation to the Provider
382		DelegationRevoked {
383			/// The Provider MSA Id
384			provider_id: ProviderId,
385
386			/// The Delegator MSA Id
387			delegator_id: DelegatorId,
388		},
389		/// The MSA has been retired.
390		MsaRetired {
391			/// The MSA id for the Event
392			msa_id: MessageSourceId,
393		},
394		/// A an update to the delegation occurred (ex. schema permissions where updated).
395		DelegationUpdated {
396			/// The Provider MSA Id
397			provider_id: ProviderId,
398
399			/// The Delegator MSA Id
400			delegator_id: DelegatorId,
401		},
402		/// A Recovery Commitment was added to an MSA
403		RecoveryCommitmentAdded {
404			/// The MSA owner key that added the commitment
405			who: T::AccountId,
406
407			/// The MSA id for the Event
408			msa_id: MessageSourceId,
409
410			/// The Recovery Commitment that was added
411			recovery_commitment: RecoveryCommitment,
412		},
413		/// A recovery provider has been approved.
414		RecoveryProviderApproved {
415			/// The provider account ID
416			provider_id: ProviderId,
417		},
418		/// A recovery provider has been removed.
419		RecoveryProviderRemoved {
420			/// The provider account ID
421			provider_id: ProviderId,
422		},
423		/// An account was recovered with a new control key
424		AccountRecovered {
425			/// The MSA id that was recovered
426			msa_id: MessageSourceId,
427			/// The recovery provider that performed the recovery
428			recovery_provider: ProviderId,
429			/// The new control key added to the MSA
430			new_control_key: T::AccountId,
431		},
432		/// A Recovery Commitment was invalidated after use or removal
433		RecoveryCommitmentInvalidated {
434			/// The MSA id for which the commitment was invalidated
435			msa_id: MessageSourceId,
436			/// The Recovery Commitment that was invalidated
437			recovery_commitment: RecoveryCommitment,
438		},
439		/// Application for provider created
440		ApplicationCreated {
441			/// The MSA id associated with the provider
442			provider_id: ProviderId,
443			/// The application id for the created application
444			application_id: ApplicationIndex,
445		},
446		/// Application updated for provider
447		ApplicationContextUpdated {
448			/// The MSA id associated with the provider
449			provider_id: ProviderId,
450			/// The application id for the updated application
451			application_id: Option<ApplicationIndex>,
452		},
453		/// Provider registry is updated
454		ProviderUpdated {
455			/// The MSA id associated with the provider
456			provider_id: ProviderId,
457		},
458	}
459
460	#[pallet::error]
461	pub enum Error<T> {
462		/// Tried to add a key that was already registered to an MSA
463		KeyAlreadyRegistered,
464
465		/// MsaId values have reached the maximum
466		MsaIdOverflow,
467
468		/// Cryptographic signature verification failed
469		MsaOwnershipInvalidSignature,
470
471		/// Ony the MSA Owner may perform the operation
472		NotMsaOwner,
473
474		/// Cryptographic signature failed verification
475		InvalidSignature,
476
477		/// Only the KeyOwner may perform the operation
478		NotKeyOwner,
479
480		/// An operation was attempted with an unknown Key
481		NoKeyExists,
482
483		/// The number of key values has reached its maximum
484		KeyLimitExceeded,
485
486		/// A transaction's Origin (AccountId) may not remove itself
487		InvalidSelfRemoval,
488
489		/// An MSA may not be its own delegate
490		InvalidSelfProvider,
491
492		/// An invalid schema Id was provided
493		InvalidSchemaId,
494
495		/// The delegation relationship already exists for the given MSA Ids
496		DuplicateProvider,
497
498		/// Cryptographic signature verification failed for adding the Provider as delegate
499		AddProviderSignatureVerificationFailed,
500
501		/// Origin attempted to add a delegate for someone else's MSA
502		UnauthorizedDelegator,
503
504		/// Origin attempted to add a different delegate than what was in the payload
505		UnauthorizedProvider,
506
507		/// The operation was attempted with a revoked delegation
508		DelegationRevoked,
509
510		/// The operation was attempted with an unknown delegation
511		DelegationNotFound,
512
513		/// The MSA id submitted for provider creation has already been associated with a provider
514		DuplicateProviderRegistryEntry,
515
516		/// The maximum length for a provider name has been exceeded
517		ExceedsMaxProviderNameSize,
518
519		/// The maximum number of schema grants has been exceeded
520		ExceedsMaxSchemaGrantsPerDelegation,
521
522		/// Provider is not permitted to publish for given schema_id
523		SchemaNotGranted,
524
525		/// The operation was attempted with a non-provider MSA
526		ProviderNotRegistered,
527
528		/// The submitted proof has expired; the current block is less the expiration block
529		ProofHasExpired,
530
531		/// The submitted proof expiration block is too far in the future
532		ProofNotYetValid,
533
534		/// Attempted to add a signature when the signature is already in the registry
535		SignatureAlreadySubmitted,
536
537		/// Cryptographic signature verification failed for proving ownership of new public-key.
538		NewKeyOwnershipInvalidSignature,
539
540		/// Attempted to request validity of schema permission or delegation in the future.
541		CannotPredictValidityPastCurrentBlock,
542
543		/// Attempted to add a new signature to a full signature registry
544		SignatureRegistryLimitExceeded,
545
546		/// Attempted to add a new signature to a corrupt signature registry
547		SignatureRegistryCorrupted,
548
549		/// Account has no/insufficient balance to withdraw
550		InsufficientBalanceToWithdraw,
551
552		/// Fund transfer error
553		UnexpectedTokenTransferError,
554
555		/// The caller is not authorized as a recovery provider
556		NotAuthorizedRecoveryProvider,
557
558		/// The provided recovery commitment does not match the stored one
559		InvalidRecoveryCommitment,
560
561		/// No recovery commitment exists for the given MSA
562		NoRecoveryCommitment,
563
564		/// Invalid cid provided for the provider
565		InvalidCid,
566
567		/// Unsupported CID version
568		UnsupportedCidVersion,
569
570		/// Invalid Language Code provided for the provider
571		InvalidBCP47LanguageCode,
572
573		/// Duplicate application registry entry
574		DuplicateApplicationRegistryEntry,
575
576		/// Logo cid not approved
577		LogoCidNotApproved,
578
579		/// Invalid logo bytes that do not match the approved logo
580		InvalidLogoBytes,
581
582		/// Application not found for given application_id
583		ApplicationNotFound,
584	}
585
586	impl<T: Config> BlockNumberProvider for Pallet<T> {
587		type BlockNumber = BlockNumberFor<T>;
588
589		fn current_block_number() -> Self::BlockNumber {
590			frame_system::Pallet::<T>::block_number()
591		}
592	}
593
594	#[pallet::hooks]
595	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
596		fn on_initialize(_block_number: BlockNumberFor<T>) -> Weight {
597			<OffchainIndexEventCount<T>>::set(0u16);
598			let migration_weight = crate::migration::v2::on_initialize_migration::<T>(); // allocates 1 read and 1 write for any access of `MSAEventCount` in every block
599			T::DbWeight::get().reads_writes(1u64, 1u64).saturating_add(migration_weight)
600		}
601
602		fn offchain_worker(block_number: BlockNumberFor<T>) {
603			log::info!("Running offchain workers! {block_number:?}");
604			do_offchain_worker::<T>(block_number)
605		}
606	}
607
608	#[pallet::call]
609	impl<T: Config> Pallet<T> {
610		/// Creates an MSA for the Origin (sender of the transaction).  Origin is assigned an MSA ID.
611		///
612		/// # Events
613		/// * [`Event::MsaCreated`]
614		///
615		/// # Errors
616		///
617		/// * [`Error::KeyAlreadyRegistered`] - MSA is already registered to the Origin.
618		///
619		#[pallet::call_index(0)]
620		#[pallet::weight(T::WeightInfo::create())]
621		pub fn create(origin: OriginFor<T>) -> DispatchResult {
622			let public_key = ensure_signed(origin)?;
623
624			let (new_msa_id, new_public_key) = Self::create_account(public_key)?;
625
626			let event = Event::MsaCreated { msa_id: new_msa_id, key: new_public_key };
627			offchain_index_event::<T>(Some(&event), new_msa_id);
628			Self::deposit_event(event);
629			Ok(())
630		}
631
632		/// `Origin` MSA creates an MSA on behalf of `delegator_key`, creates a Delegation with the `delegator_key`'s MSA as the Delegator and `origin` as `Provider`. Deposits events [`MsaCreated`](Event::MsaCreated) and [`DelegationGranted`](Event::DelegationGranted).
633		///
634		/// # Remarks
635		/// * Origin MUST be the provider
636		/// * Signatures should be over the [`AddProvider`] struct
637		///
638		/// # Events
639		/// * [`Event::MsaCreated`]
640		/// * [`Event::DelegationGranted`]
641		///
642		/// # Errors
643		///
644		/// * [`Error::UnauthorizedProvider`] - payload's MSA does not match given provider MSA.
645		/// * [`Error::InvalidSignature`] - `proof` verification fails; `delegator_key` must have signed `add_provider_payload`
646		/// * [`Error::NoKeyExists`] - there is no MSA for `origin`.
647		/// * [`Error::KeyAlreadyRegistered`] - there is already an MSA for `delegator_key`.
648		/// * [`Error::ProviderNotRegistered`] - the a non-provider MSA is used as the provider
649		/// * [`Error::ProofNotYetValid`] - `add_provider_payload` expiration is too far in the future
650		/// * [`Error::ProofHasExpired`] - `add_provider_payload` expiration is in the past
651		/// * [`Error::SignatureAlreadySubmitted`] - signature has already been used
652		///
653		#[pallet::call_index(1)]
654		#[pallet::weight(T::WeightInfo::create_sponsored_account_with_delegation(
655		add_provider_payload.schema_ids.len() as u32
656		))]
657		pub fn create_sponsored_account_with_delegation(
658			origin: OriginFor<T>,
659			delegator_key: T::AccountId,
660			proof: MultiSignature,
661			add_provider_payload: AddProvider,
662		) -> DispatchResult {
663			let provider_key = ensure_signed(origin)?;
664
665			ensure!(
666				Self::verify_signature(&proof, &delegator_key, &add_provider_payload),
667				Error::<T>::InvalidSignature
668			);
669
670			Self::register_signature(&proof, add_provider_payload.expiration.into())?;
671
672			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
673			ensure!(
674				add_provider_payload.authorized_msa_id == provider_msa_id,
675				Error::<T>::UnauthorizedProvider
676			);
677
678			// Verify that the provider is a registered provider
679			ensure!(
680				Self::is_registered_provider(provider_msa_id),
681				Error::<T>::ProviderNotRegistered
682			);
683
684			let (new_delegator_msa_id, new_delegator_public_key) =
685				Self::create_account(delegator_key)?;
686			Self::add_provider(
687				ProviderId(provider_msa_id),
688				DelegatorId(new_delegator_msa_id),
689				add_provider_payload.schema_ids,
690			)?;
691			let event =
692				Event::MsaCreated { msa_id: new_delegator_msa_id, key: new_delegator_public_key };
693			offchain_index_event::<T>(Some(&event), new_delegator_msa_id);
694			Self::deposit_event(event);
695			Self::deposit_event(Event::DelegationGranted {
696				delegator_id: DelegatorId(new_delegator_msa_id),
697				provider_id: ProviderId(provider_msa_id),
698			});
699			Ok(())
700		}
701
702		/// Adds an association between MSA id and ProviderRegistryEntry. As of now, the
703		/// only piece of metadata we are recording is provider name.
704		///
705		/// # Events
706		/// * [`Event::ProviderCreated`]
707		///
708		/// # Errors
709		/// * [`Error::NoKeyExists`] - origin does not have an MSA
710		/// * [`Error::ExceedsMaxProviderNameSize`] - Too long of a provider name
711		/// * [`Error::DuplicateProviderRegistryEntry`] - a ProviderRegistryEntry associated with the given MSA id already exists.
712		#[pallet::call_index(2)]
713		#[pallet::weight(T::WeightInfo::create_provider())]
714		pub fn create_provider(origin: OriginFor<T>, provider_name: Vec<u8>) -> DispatchResult {
715			let provider_key = ensure_signed(origin)?;
716			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
717			let bounded_name: BoundedVec<u8, T::MaxProviderNameSize> =
718				provider_name.try_into().map_err(|_| Error::<T>::ExceedsMaxProviderNameSize)?;
719
720			let entry = ProviderRegistryEntry { default_name: bounded_name, ..Default::default() };
721			Self::ensure_correct_cids(&entry)?;
722			Self::upsert_provider_for(provider_msa_id, entry, false)?;
723			Self::deposit_event(Event::ProviderCreated {
724				provider_id: ProviderId(provider_msa_id),
725			});
726			Ok(())
727		}
728
729		/// Creates a new Delegation for an existing MSA, with `origin` as the Provider and `delegator_key` is the delegator.
730		/// Since it is being sent on the Delegator's behalf, it requires the Delegator to authorize the new Delegation.
731		///
732		/// # Remarks
733		/// * Origin MUST be the provider
734		/// * Signatures should be over the [`AddProvider`] struct
735		///
736		/// # Events
737		/// * [`Event::DelegationGranted`]
738		///
739		/// # Errors
740		/// * [`Error::AddProviderSignatureVerificationFailed`] - `origin`'s MSA ID does not equal `add_provider_payload.authorized_msa_id`.
741		/// * [`Error::DuplicateProvider`] - there is already a Delegation for `origin` MSA and `delegator_key` MSA.
742		/// * [`Error::UnauthorizedProvider`] - `add_provider_payload.authorized_msa_id`  does not match MSA ID of `delegator_key`.
743		/// * [`Error::InvalidSelfProvider`] - Cannot delegate to the same MSA
744		/// * [`Error::InvalidSignature`] - `proof` verification fails; `delegator_key` must have signed `add_provider_payload`
745		/// * [`Error::NoKeyExists`] - there is no MSA for `origin` or `delegator_key`.
746		/// * [`Error::ProviderNotRegistered`] - the a non-provider MSA is used as the provider
747		/// * [`Error::UnauthorizedDelegator`] - Origin attempted to add a delegate for someone else's MSA
748		///
749		#[pallet::call_index(3)]
750		#[pallet::weight(T::WeightInfo::grant_delegation(add_provider_payload.schema_ids.len() as u32))]
751		pub fn grant_delegation(
752			origin: OriginFor<T>,
753			delegator_key: T::AccountId,
754			proof: MultiSignature,
755			add_provider_payload: AddProvider,
756		) -> DispatchResult {
757			let provider_key = ensure_signed(origin)?;
758
759			ensure!(
760				Self::verify_signature(&proof, &delegator_key, &add_provider_payload),
761				Error::<T>::AddProviderSignatureVerificationFailed
762			);
763
764			Self::register_signature(&proof, add_provider_payload.expiration.into())?;
765			let (provider_id, delegator_id) =
766				Self::ensure_valid_registered_provider(&delegator_key, &provider_key)?;
767
768			ensure!(
769				add_provider_payload.authorized_msa_id == provider_id.0,
770				Error::<T>::UnauthorizedDelegator
771			);
772
773			Self::upsert_schema_permissions(
774				provider_id,
775				delegator_id,
776				add_provider_payload.schema_ids,
777			)?;
778			Self::deposit_event(Event::DelegationGranted { delegator_id, provider_id });
779
780			Ok(())
781		}
782
783		/// Delegator (Origin) MSA terminates a delegation relationship with the `Provider` MSA. Deposits event[`DelegationRevoked`](Event::DelegationRevoked).
784		///
785		/// # Events
786		/// * [`Event::DelegationRevoked`]
787		///
788		/// # Errors
789		///
790		/// * [`Error::NoKeyExists`] - origin does not have an MSA
791		/// * [`Error::DelegationRevoked`] - the delegation has already been revoked.
792		/// * [`Error::DelegationNotFound`] - there is not delegation relationship between Origin and Delegator or Origin and Delegator are the same.
793		///
794		#[pallet::call_index(4)]
795		#[pallet::weight((T::WeightInfo::revoke_delegation_by_delegator(), DispatchClass::Normal, Pays::No))]
796		pub fn revoke_delegation_by_delegator(
797			origin: OriginFor<T>,
798			#[pallet::compact] provider_msa_id: MessageSourceId,
799		) -> DispatchResult {
800			let who = ensure_signed(origin)?;
801
802			match PublicKeyToMsaId::<T>::get(&who) {
803				Some(delegator_msa_id) => {
804					let delegator_id = DelegatorId(delegator_msa_id);
805					let provider_id = ProviderId(provider_msa_id);
806					Self::revoke_provider(provider_id, delegator_id)?;
807					Self::deposit_event(Event::DelegationRevoked { delegator_id, provider_id });
808				},
809				None => {
810					log::error!(
811						"TransactionExtension did not catch invalid MSA for account {who:?}, "
812					);
813				},
814			}
815
816			Ok(())
817		}
818
819		/// Adds a new public key to an existing Message Source Account (MSA). This functionality enables the MSA owner to manage multiple keys
820		/// for their account or rotate keys for enhanced security.
821		///
822		/// The `origin` parameter represents the account from which the function is called and can be either the MSA owner's account or a delegated provider's account,
823		/// depending on the intended use.
824		///
825		/// The function requires two signatures: `msa_owner_proof` and `new_key_owner_proof`, which serve as proofs of ownership for the existing MSA
826		/// and the new public key account, respectively.  This ensures that a new key cannot be added without permission of both the MSA owner and the owner of the new key.
827		///
828		/// The necessary information for the key addition, the new public key and the MSA ID, is contained in the `add_key_payload` parameter of type [AddKeyData].
829		/// It also contains an expiration block number for both proofs, ensuring they are valid and must be greater than the current block.
830		///
831		/// # Events
832		/// * [`Event::PublicKeyAdded`]
833		///
834		/// # Errors
835		///
836		/// * [`Error::MsaOwnershipInvalidSignature`] - `key` is not a valid signer of the provided `add_key_payload`.
837		/// * [`Error::NewKeyOwnershipInvalidSignature`] - `key` is not a valid signer of the provided `add_key_payload`.
838		/// * [`Error::NoKeyExists`] - the MSA id for the account in `add_key_payload` does not exist.
839		/// * [`Error::NotMsaOwner`] - Origin's MSA is not the same as 'add_key_payload` MSA. Essentially you can only add a key to your own MSA.
840		/// * [`Error::ProofHasExpired`] - the current block is less than the `expired` block number set in `AddKeyData`.
841		/// * [`Error::ProofNotYetValid`] - the `expired` block number set in `AddKeyData` is greater than the current block number plus mortality_block_limit().
842		/// * [`Error::SignatureAlreadySubmitted`] - signature has already been used.
843		///
844		#[pallet::call_index(5)]
845		#[pallet::weight(T::WeightInfo::add_public_key_to_msa())]
846		pub fn add_public_key_to_msa(
847			origin: OriginFor<T>,
848			msa_owner_public_key: T::AccountId,
849			msa_owner_proof: MultiSignature,
850			new_key_owner_proof: MultiSignature,
851			add_key_payload: AddKeyData<T>,
852		) -> DispatchResult {
853			let _ = ensure_signed(origin)?;
854
855			ensure!(
856				Self::verify_signature(&msa_owner_proof, &msa_owner_public_key, &add_key_payload),
857				Error::<T>::MsaOwnershipInvalidSignature
858			);
859
860			ensure!(
861				Self::verify_signature(
862					&new_key_owner_proof,
863					&add_key_payload.new_public_key,
864					&add_key_payload
865				),
866				Error::<T>::NewKeyOwnershipInvalidSignature
867			);
868
869			Self::register_signature(&msa_owner_proof, add_key_payload.expiration)?;
870			Self::register_signature(&new_key_owner_proof, add_key_payload.expiration)?;
871
872			let msa_id = add_key_payload.msa_id;
873
874			Self::ensure_msa_owner(&msa_owner_public_key, msa_id)?;
875
876			Self::add_key(msa_id, &add_key_payload.new_public_key.clone())?;
877
878			let event =
879				Event::PublicKeyAdded { msa_id, key: add_key_payload.new_public_key.clone() };
880			offchain_index_event::<T>(Some(&event), msa_id);
881			Self::deposit_event(event);
882
883			Ok(())
884		}
885
886		/// Remove a key associated with an MSA by expiring it at the current block.
887		///
888		/// # Remarks
889		/// * Removal of key deletes the association of the key with the MSA.
890		/// * The key can be re-added to same or another MSA if needed.
891		///
892		/// # Events
893		/// * [`Event::PublicKeyDeleted`]
894		///
895		/// # Errors
896		/// * [`Error::InvalidSelfRemoval`] - `origin` and `key` are the same.
897		/// * [`Error::NotKeyOwner`] - `origin` does not own the MSA ID associated with `key`.
898		/// * [`Error::NoKeyExists`] - `origin` or `key` are not associated with `origin`'s MSA ID.
899		///
900		#[pallet::call_index(6)]
901		#[pallet::weight((T::WeightInfo::delete_msa_public_key(), DispatchClass::Normal, Pays::No))]
902		pub fn delete_msa_public_key(
903			origin: OriginFor<T>,
904			public_key_to_delete: T::AccountId,
905		) -> DispatchResult {
906			let who = ensure_signed(origin)?;
907
908			match PublicKeyToMsaId::<T>::get(&who) {
909				Some(who_msa_id) => {
910					Self::delete_key_for_msa(who_msa_id, &public_key_to_delete)?;
911
912					// Deposit the event
913					let event = Event::PublicKeyDeleted { key: public_key_to_delete };
914					offchain_index_event::<T>(Some(&event), who_msa_id);
915					Self::deposit_event(event);
916				},
917				None => {
918					log::error!(
919						"TransactionExtension did not catch invalid MSA for account {who:?}"
920					);
921				},
922			}
923			Ok(())
924		}
925
926		/// Provider MSA terminates Delegation with a Delegator MSA by expiring the Delegation at the current block.
927		///
928		/// # Events
929		/// * [`Event::DelegationRevoked`]
930		///
931		/// # Errors
932		///
933		/// * [`Error::NoKeyExists`] - `provider_key` does not have an MSA key.
934		/// * [`Error::DelegationRevoked`] - delegation is already revoked
935		/// * [`Error::DelegationNotFound`] - no Delegation found between origin MSA and delegator MSA.
936		///
937		#[pallet::call_index(7)]
938		#[pallet::weight((T::WeightInfo::revoke_delegation_by_provider(), DispatchClass::Normal, Pays::No))]
939		pub fn revoke_delegation_by_provider(
940			origin: OriginFor<T>,
941			#[pallet::compact] delegator: MessageSourceId,
942		) -> DispatchResult {
943			let who = ensure_signed(origin)?;
944
945			// Revoke delegation relationship entry in the delegation registry by expiring it
946			// at the current block
947			// validity checks are in TransactionExtension so in theory this should never error.
948			match PublicKeyToMsaId::<T>::get(&who) {
949				Some(msa_id) => {
950					let provider_id = ProviderId(msa_id);
951					let delegator_id = DelegatorId(delegator);
952					Self::revoke_provider(provider_id, delegator_id)?;
953					Self::deposit_event(Event::DelegationRevoked { provider_id, delegator_id })
954				},
955				None => {
956					log::error!(
957						"TransactionExtension did not catch invalid MSA for account {who:?}"
958					);
959				},
960			}
961
962			Ok(())
963		}
964
965		// REMOVED grant_schema_permissions() at call index 8
966		// REMOVED revoke_schema_permissions() at call index 9
967
968		/// Retires a MSA
969		///
970		/// When a user wants to disassociate themselves from Frequency, they can retire their MSA for free provided that:
971		///  (1) They own the MSA
972		///  (2) The MSA is not a registered provider.
973		///  (3) They retire their user handle (if they have one)
974		///  (4) There is only one account key
975		///  (5) The user has already deleted all delegations to providers
976		///
977		/// This does not currently remove any messages related to the MSA.
978		///
979		/// # Events
980		/// * [`Event::PublicKeyDeleted`]
981		/// * [`Event::MsaRetired`]
982		///
983		/// # Errors
984		/// * [`Error::NoKeyExists`] - `delegator` does not have an MSA key.
985		///
986		#[pallet::call_index(10)]
987		#[pallet::weight((T::WeightInfo::retire_msa(), DispatchClass::Normal, Pays::No))]
988		pub fn retire_msa(origin: OriginFor<T>) -> DispatchResult {
989			// Check and get the account id from the origin
990			let who = ensure_signed(origin)?;
991
992			// Delete the last and only account key and deposit the "PublicKeyDeleted" event
993			// check for valid MSA is in TransactionExtension.
994			match PublicKeyToMsaId::<T>::get(&who) {
995				Some(msa_id) => {
996					Self::delete_key_for_msa(msa_id, &who)?;
997					let event = Event::PublicKeyDeleted { key: who };
998					offchain_index_event::<T>(Some(&event), msa_id);
999					Self::deposit_event(event);
1000					Self::deposit_event(Event::MsaRetired { msa_id });
1001				},
1002				None => {
1003					log::error!(
1004						"TransactionExtension did not catch invalid MSA for account {who:?}"
1005					);
1006				},
1007			}
1008			Ok(())
1009		}
1010
1011		/// Propose to be a provider.  Creates a proposal for council approval to create a provider from a MSA
1012		///
1013		/// # Errors
1014		/// - [`NoKeyExists`](Error::NoKeyExists) - If there is not MSA for `origin`.
1015		/// - [`Error::ExceedsMaxProviderNameSize`] - Too long of a provider name
1016		#[pallet::call_index(11)]
1017		#[pallet::weight(T::WeightInfo::propose_to_be_provider_v2())]
1018		#[allow(deprecated)]
1019		#[deprecated(
1020			note = "please use `propose_to_be_provider_v2`, which supports additional provider metadata"
1021		)]
1022		pub fn propose_to_be_provider(
1023			origin: OriginFor<T>,
1024			provider_name: Vec<u8>,
1025		) -> DispatchResult {
1026			let bounded_name: BoundedVec<u8, T::MaxProviderNameSize> =
1027				provider_name.try_into().map_err(|_| Error::<T>::ExceedsMaxProviderNameSize)?;
1028
1029			let entry = ProviderRegistryEntry { default_name: bounded_name, ..Default::default() };
1030
1031			Self::propose_to_be_provider_v2(origin, entry)
1032		}
1033
1034		/// Create a provider by means of governance approval
1035		///
1036		/// # Events
1037		/// * [`Event::ProviderCreated`]
1038		///
1039		/// # Errors
1040		/// * [`Error::NoKeyExists`] - account does not have an MSA
1041		/// * [`Error::ExceedsMaxProviderNameSize`] - Too long of a provider name
1042		/// * [`Error::DuplicateProviderRegistryEntry`] - a ProviderRegistryEntry associated with the given MSA id already exists.
1043		#[pallet::call_index(12)]
1044		#[pallet::weight(T::WeightInfo::create_provider_via_governance_v2(0u32, 0u32))]
1045		#[allow(deprecated)]
1046		#[deprecated(
1047			note = "please use `create_provider_via_governance_v2`, which supports additional provider metadata"
1048		)]
1049		pub fn create_provider_via_governance(
1050			origin: OriginFor<T>,
1051			provider_key: T::AccountId,
1052			provider_name: Vec<u8>,
1053		) -> DispatchResult {
1054			let bounded_name: BoundedVec<u8, T::MaxProviderNameSize> =
1055				provider_name.try_into().map_err(|_| Error::<T>::ExceedsMaxProviderNameSize)?;
1056
1057			let entry = ProviderRegistryEntry { default_name: bounded_name, ..Default::default() };
1058
1059			Self::create_provider_via_governance_v2(origin, provider_key, entry)
1060		}
1061
1062		/// A generic endpoint to replay any offchain related event to fix any potential issues
1063		#[pallet::call_index(13)]
1064		#[pallet::weight(T::WeightInfo::reindex_offchain())]
1065		pub fn reindex_offchain(
1066			origin: OriginFor<T>,
1067			event: OffchainReplayEvent<T>,
1068		) -> DispatchResult {
1069			let _ = ensure_signed(origin)?;
1070			match event {
1071				OffchainReplayEvent::MsaPallet(MsaOffchainReplayEvent::KeyReIndex {
1072					msa_id,
1073					index_key,
1074				}) => {
1075					// don't need to check existence of msa_id since it would get checked on offchain side
1076					match index_key {
1077						Some(key) => {
1078							let event = Event::PublicKeyAdded { msa_id, key };
1079							offchain_index_event::<T>(Some(&event), msa_id);
1080						},
1081						None => {
1082							offchain_index_event::<T>(None, msa_id);
1083						},
1084					}
1085				},
1086			}
1087
1088			Ok(())
1089		}
1090
1091		/// Withdraw all available tokens from the account associated with the MSA, to the account of the caller.
1092		///
1093		/// The `origin` parameter represents the account from which the function is called and must be the account receiving the tokens.
1094		///
1095		/// The function requires one signature: `msa_owner_proof`, which serve as proof that the owner of the MSA authorizes the transaction.
1096		///
1097		/// The necessary information for the withdrawal authorization, the destination account and the MSA ID, are contained in the `authorization_payload` parameter of type [AddKeyData].
1098		/// It also contains an expiration block number for the proof, ensuring it is valid and must be greater than the current block.
1099		///
1100		/// # Events
1101		/// * [`pallet_balances::Event::<T,I>::Transfer`](https://docs.rs/pallet-balances/latest/pallet_balances/pallet/enum.Event.html#variant.Transfer) - Transfer token event
1102		///
1103		/// # Errors
1104		///
1105		/// * [`Error::ProofHasExpired`] - the current block is less than the `expired` block number set in `AddKeyData`.
1106		/// * [`Error::ProofNotYetValid`] - the `expired` block number set in `AddKeyData` is greater than the current block number plus mortality_block_limit().
1107		/// * [`Error::SignatureAlreadySubmitted`] - signature has already been used.
1108		/// * [`Error::InsufficientBalanceToWithdraw`] - the MSA account has not balance to withdraw
1109		/// * [`Error::UnexpectedTokenTransferError`] - the token transfer failed
1110		///
1111		#[pallet::call_index(14)]
1112		#[pallet::weight((T::WeightInfo::withdraw_tokens(), DispatchClass::Normal, Pays::No))]
1113		pub fn withdraw_tokens(
1114			origin: OriginFor<T>,
1115			_msa_owner_public_key: T::AccountId,
1116			msa_owner_proof: MultiSignature,
1117			authorization_payload: AuthorizedKeyData<T>,
1118		) -> DispatchResult {
1119			let public_key = ensure_signed(origin)?;
1120
1121			Self::register_signature(&msa_owner_proof, authorization_payload.expiration)?;
1122
1123			let msa_id = authorization_payload.msa_id;
1124
1125			// - Get account address for MSA
1126			let msa_address = Self::msa_id_to_eth_address(msa_id);
1127
1128			// - Convert to AccountId
1129			let mut bytes = &EthereumAddressMapper::to_bytes32(&msa_address.0)[..];
1130			let msa_account_id = T::AccountId::decode(&mut bytes).map_err(|_| {
1131				log::error!("Failed to decode MSA account ID from Ethereum address");
1132				Error::<T>::NoKeyExists
1133			})?;
1134
1135			// Get balance to transfer
1136			let msa_balance = T::Currency::reducible_balance(
1137				&msa_account_id,
1138				Preservation::Expendable,
1139				Fortitude::Polite,
1140			);
1141			ensure!(msa_balance > Zero::zero(), Error::<T>::InsufficientBalanceToWithdraw);
1142
1143			// Transfer balance to the caller
1144			let result = <T as pallet::Config>::Currency::transfer(
1145				&msa_account_id,
1146				&public_key,
1147				msa_balance,
1148				Preservation::Expendable,
1149			);
1150			ensure!(result.is_ok(), Error::<T>::UnexpectedTokenTransferError);
1151
1152			Ok(())
1153		}
1154
1155		/// Adds a Recovery Commitment to an MSA. The Recovery Commitment is a cryptographic commitment
1156		/// that can be used later in a recovery process to prove ownership or authorization.
1157		///
1158		/// This function allows the owner of an MSA to create a Recovery Commitment hash that is stored
1159		/// on-chain. The commitment must be signed by the MSA owner to prove authorization.
1160		///
1161		/// # Remarks
1162		/// * The `origin` can be any signed account but the `msa_owner_key` must be the actual owner
1163		/// * Signatures should be over the [`RecoveryCommitmentPayload`] struct
1164		/// * The Recovery Commitment is stored as a hash mapping from MSA ID to commitment value
1165		///
1166		/// # Events
1167		/// * [`Event::RecoveryCommitmentAdded`]
1168		///
1169		/// # Errors
1170		/// * [`Error::InvalidSignature`] - `proof` verification fails; `msa_owner_key` must have signed `payload`
1171		/// * [`Error::NoKeyExists`] - there is no MSA for `msa_owner_key`
1172		/// * [`Error::ProofNotYetValid`] - `payload` expiration is too far in the future
1173		/// * [`Error::ProofHasExpired`] - `payload` expiration is in the past
1174		/// * [`Error::SignatureAlreadySubmitted`] - signature has already been used
1175		///
1176		#[pallet::call_index(15)]
1177		#[pallet::weight(T::WeightInfo::add_recovery_commitment())]
1178		pub fn add_recovery_commitment(
1179			origin: OriginFor<T>,
1180			msa_owner_key: T::AccountId,
1181			proof: MultiSignature,
1182			payload: RecoveryCommitmentPayload<T>,
1183		) -> DispatchResult {
1184			let _origin_key = ensure_signed(origin)?;
1185
1186			// Verify that the MsaId owner has signed the payload
1187			ensure!(
1188				Self::verify_signature(&proof, &msa_owner_key, &payload),
1189				Error::<T>::InvalidSignature
1190			);
1191
1192			// Register the signature to prevent replay attacks
1193			Self::register_signature(&proof, payload.expiration)?;
1194
1195			// Recover the MSA ID from the msa_owner_key
1196			let msa_id = Self::ensure_valid_msa_key(&msa_owner_key)?;
1197
1198			// Store the new RecoveryCommitment
1199			MsaIdToRecoveryCommitment::<T>::insert(msa_id, payload.recovery_commitment);
1200			Self::deposit_event(Event::RecoveryCommitmentAdded {
1201				who: msa_owner_key,
1202				msa_id,
1203				recovery_commitment: payload.recovery_commitment,
1204			});
1205
1206			Ok(())
1207		}
1208
1209		/// Approves a recovery provider via governance.
1210		/// Only governance can approve recovery providers.
1211		///
1212		/// # Events
1213		/// * [`Event::RecoveryProviderApproved`]
1214		///
1215		/// # Errors
1216		/// * [`DispatchError::BadOrigin`] - Caller is not authorized to approve recovery providers.
1217		///
1218		#[pallet::call_index(16)]
1219		#[pallet::weight(T::WeightInfo::approve_recovery_provider())]
1220		pub fn approve_recovery_provider(
1221			origin: OriginFor<T>,
1222			provider_key: T::AccountId,
1223		) -> DispatchResult {
1224			T::RecoveryProviderApprovalOrigin::ensure_origin(origin)?;
1225
1226			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
1227			ensure!(
1228				Self::is_registered_provider(provider_msa_id),
1229				Error::<T>::ProviderNotRegistered
1230			);
1231
1232			// If the provider is already approved, do nothing
1233			if Self::is_approved_recovery_provider(&ProviderId(provider_msa_id)) {
1234				return Ok(());
1235			}
1236
1237			RecoveryProviders::<T>::insert(ProviderId(provider_msa_id), true);
1238
1239			Self::deposit_event(Event::RecoveryProviderApproved {
1240				provider_id: ProviderId(provider_msa_id),
1241			});
1242
1243			Ok(())
1244		}
1245
1246		/// Removes a recovery provider via governance.
1247		/// Only governance can remove recovery providers.
1248		///
1249		/// # Events
1250		/// * [`Event::RecoveryProviderRemoved`]
1251		///
1252		/// # Errors
1253		///
1254		/// * [`DispatchError::BadOrigin`] - Caller is not authorized to remove recovery providers.
1255		///
1256		#[pallet::call_index(17)]
1257		#[pallet::weight(T::WeightInfo::remove_recovery_provider())]
1258		pub fn remove_recovery_provider(
1259			origin: OriginFor<T>,
1260			provider: ProviderId,
1261		) -> DispatchResult {
1262			T::RecoveryProviderApprovalOrigin::ensure_origin(origin)?;
1263
1264			RecoveryProviders::<T>::remove(provider);
1265			Self::deposit_event(Event::RecoveryProviderRemoved { provider_id: provider });
1266			Ok(())
1267		}
1268
1269		/// Recover an MSA account with a new control key.
1270		/// This extrinsic is called by approved Recovery Providers after verifying
1271		/// the user's Recovery Intermediary Hashes and Authentication Contact.
1272		///
1273		/// # Events
1274		/// * [`Event::AccountRecovered`]
1275		/// * [`Event::RecoveryCommitmentInvalidated`]
1276		///
1277		/// # Errors
1278		/// * [`Error::NotAuthorizedRecoveryProvider`] - Caller is not an approved Recovery Provider.
1279		/// * [`Error::ProviderNotRegistered`] - Recovery Provider is not registered.
1280		/// * [`Error::NoRecoveryCommitment`] - No Recovery Commitment exists for the MSA.
1281		/// * [`Error::InvalidRecoveryCommitment`] - Recovery Commitment does not match.
1282		///
1283		#[pallet::call_index(18)]
1284		#[pallet::weight(T::WeightInfo::recover_account())]
1285		pub fn recover_account(
1286			origin: OriginFor<T>,
1287			intermediary_hash_a: RecoveryHash,
1288			intermediary_hash_b: RecoveryHash,
1289			new_control_key_proof: MultiSignature,
1290			add_key_payload: AddKeyData<T>,
1291		) -> DispatchResult {
1292			let provider_key = ensure_signed(origin)?;
1293
1294			let provider_msa_id = Self::ensure_approved_recovery_provider(&provider_key)?;
1295
1296			let recovery_commitment = Self::ensure_valid_recovery_commitment(
1297				intermediary_hash_a,
1298				intermediary_hash_b,
1299				add_key_payload.msa_id,
1300			)?;
1301
1302			Self::ensure_valid_new_key_owner(&new_control_key_proof, &add_key_payload)?;
1303
1304			Self::add_key(add_key_payload.msa_id, &add_key_payload.new_public_key.clone())?;
1305
1306			let event = Event::PublicKeyAdded {
1307				msa_id: add_key_payload.msa_id,
1308				key: add_key_payload.new_public_key.clone(),
1309			};
1310			offchain_index_event::<T>(Some(&event), add_key_payload.msa_id);
1311			Self::deposit_event(event);
1312
1313			// Invalidate the recovery commitment (single-use requirement)
1314			MsaIdToRecoveryCommitment::<T>::remove(add_key_payload.msa_id);
1315
1316			// Emit events
1317			Self::deposit_event(Event::AccountRecovered {
1318				msa_id: add_key_payload.msa_id,
1319				recovery_provider: ProviderId(provider_msa_id),
1320				new_control_key: add_key_payload.new_public_key.clone(),
1321			});
1322
1323			Self::deposit_event(Event::RecoveryCommitmentInvalidated {
1324				msa_id: add_key_payload.msa_id,
1325				recovery_commitment,
1326			});
1327
1328			Ok(())
1329		}
1330
1331		/// Propose to be a provider.  Creates a proposal for council approval to create a provider from a MSA
1332		///
1333		/// # Errors
1334		/// - [`NoKeyExists`](Error::NoKeyExists) - If there is not MSA for `origin`.
1335		#[pallet::call_index(19)]
1336		#[pallet::weight(T::WeightInfo::propose_to_be_provider_v2())]
1337		pub fn propose_to_be_provider_v2(
1338			origin: OriginFor<T>,
1339			payload: ProviderRegistryEntry<
1340				T::MaxProviderNameSize,
1341				T::MaxLanguageCodeSize,
1342				T::MaxLogoCidSize,
1343				T::MaxLocaleCount,
1344			>,
1345		) -> DispatchResult {
1346			let proposer = ensure_signed(origin)?;
1347			Self::ensure_valid_msa_key(&proposer)?;
1348
1349			let proposal: Box<T::Proposal> = Box::new(
1350				(Call::<T>::create_provider_via_governance_v2 {
1351					provider_key: proposer.clone(),
1352					payload,
1353				})
1354				.into(),
1355			);
1356			let threshold = 1;
1357			T::ProposalProvider::propose(proposer, threshold, proposal)?;
1358			Ok(())
1359		}
1360
1361		/// Create a provider by means of governance approval
1362		///
1363		/// # Events
1364		/// * [`Event::ProviderCreated`]
1365		///
1366		/// # Errors
1367		/// * [`Error::NoKeyExists`] - account does not have an MSA
1368		/// * [`Error::DuplicateProviderRegistryEntry`] - a ProviderRegistryEntry associated with the given MSA id already exists.
1369		/// * [`Error::InvalidCid`] - If the provided CID is invalid.
1370		/// * [`Error::InvalidBCP47LanguageCode`] - If the provided BCP 47 language code is invalid.
1371		#[pallet::call_index(20)]
1372		#[pallet::weight(T::WeightInfo::create_provider_via_governance_v2(
1373			payload.localized_names.len() as u32,
1374			payload.localized_logo_250_100_png_cids.len() as u32,
1375		))]
1376		pub fn create_provider_via_governance_v2(
1377			origin: OriginFor<T>,
1378			provider_key: T::AccountId,
1379			payload: ProviderRegistryEntry<
1380				T::MaxProviderNameSize,
1381				T::MaxLanguageCodeSize,
1382				T::MaxLogoCidSize,
1383				T::MaxLocaleCount,
1384			>,
1385		) -> DispatchResult {
1386			T::CreateProviderViaGovernanceOrigin::ensure_origin(origin)?;
1387			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
1388			Self::ensure_correct_cids(&payload)?;
1389			Self::upsert_provider_for(provider_msa_id, payload, false)?;
1390			Self::deposit_event(Event::ProviderCreated {
1391				provider_id: ProviderId(provider_msa_id),
1392			});
1393			Ok(())
1394		}
1395
1396		/// Propose to add application enable provider to add multiple applications on frequency.
1397		/// Provider initiates a governance proposal to add the application.
1398		///
1399		/// # Errors
1400		/// - [`NoKeyExists`](Error::NoKeyExists) - If there is not MSA for `origin`.
1401		/// * [`Error::InvalidCid`] - If the provided CID is invalid.
1402		/// * [`Error::InvalidBCP47LanguageCode`] - If the provided BCP 47 language code is invalid.
1403		#[pallet::call_index(21)]
1404		#[pallet::weight(T::WeightInfo::propose_to_add_application(
1405			payload.localized_names.len() as u32,
1406			payload.localized_logo_250_100_png_cids.len() as u32,
1407		))]
1408		pub fn propose_to_add_application(
1409			origin: OriginFor<T>,
1410			payload: ApplicationContext<
1411				T::MaxProviderNameSize,
1412				T::MaxLanguageCodeSize,
1413				T::MaxLogoCidSize,
1414				T::MaxLocaleCount,
1415			>,
1416		) -> DispatchResult {
1417			let proposer = ensure_signed(origin)?;
1418			let provider_msa_id = Self::ensure_valid_msa_key(&proposer)?;
1419			ensure!(
1420				Self::is_registered_provider(provider_msa_id),
1421				Error::<T>::ProviderNotRegistered
1422			);
1423			let proposal: Box<T::Proposal> = Box::new(
1424				(Call::<T>::create_application_via_governance {
1425					provider_key: proposer.clone(),
1426					payload,
1427				})
1428				.into(),
1429			);
1430			let threshold = 1;
1431			T::ProposalProvider::propose(proposer, threshold, proposal)?;
1432			Ok(())
1433		}
1434
1435		/// Create application via governance approval
1436		///
1437		/// # Events
1438		/// * [`Event::ApplicationCreated`]
1439		///
1440		/// # Errors
1441		/// * [`Error::NoKeyExists`] - account does not have an MSA
1442		/// * [`Error::DuplicateApplicationRegistryEntry`] - an ApplicationRegistryEntry associated with the given MSA id already exists.
1443		/// * [`Error::InvalidCid`] - If the provided CID is invalid.
1444		/// * [`Error::InvalidBCP47LanguageCode`] - If the provided BCP 47 language code is invalid.
1445		#[pallet::call_index(22)]
1446		#[pallet::weight(T::WeightInfo::create_application_via_governance(
1447			payload.localized_names.len() as u32,
1448			payload.localized_logo_250_100_png_cids.len() as u32,
1449		))]
1450		pub fn create_application_via_governance(
1451			origin: OriginFor<T>,
1452			provider_key: T::AccountId,
1453			payload: ApplicationContext<
1454				T::MaxProviderNameSize,
1455				T::MaxLanguageCodeSize,
1456				T::MaxLogoCidSize,
1457				T::MaxLocaleCount,
1458			>,
1459		) -> DispatchResult {
1460			T::CreateProviderViaGovernanceOrigin::ensure_origin(origin)?;
1461			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
1462			ensure!(
1463				Self::is_registered_provider(provider_msa_id),
1464				Error::<T>::ProviderNotRegistered
1465			);
1466			Self::ensure_correct_cids(&payload)?;
1467			let application_id =
1468				Self::create_application_for(ProviderId(provider_msa_id), payload)?;
1469			Self::deposit_event(Event::ApplicationCreated {
1470				provider_id: ProviderId(provider_msa_id),
1471				application_id,
1472			});
1473			Ok(())
1474		}
1475
1476		/// Upload logo bytes for a approved image in `ApprovedLogos`
1477		///
1478		/// * [`Error::NoKeyExists`] - If there is not MSA for `origin`.
1479		/// * [`Error::ProviderNotRegistered`] - If the provider is not registered.
1480		/// * [`Error::InvalidCid`] - If the provided CID is invalid and not in approved logos.
1481		/// * [`Error::LogoCidNotApproved`] - If the logo CID is not in the approved logos list.
1482		///
1483		#[pallet::call_index(23)]
1484		#[pallet::weight(T::WeightInfo::upload_logo())]
1485		pub fn upload_logo(
1486			origin: OriginFor<T>,
1487			logo_cid: BoundedVec<u8, T::MaxLogoCidSize>,
1488			logo_bytes: BoundedVec<u8, T::MaxLogoSize>,
1489		) -> DispatchResult {
1490			let provider_key = ensure_signed(origin)?;
1491			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
1492			ensure!(
1493				Self::is_registered_provider(provider_msa_id),
1494				Error::<T>::ProviderNotRegistered
1495			);
1496			ensure!(ApprovedLogos::<T>::contains_key(&logo_cid), Error::<T>::LogoCidNotApproved);
1497
1498			let input_cid_binary = Self::validate_cid(&logo_cid)?;
1499			let computed_cid_binary =
1500				compute_cid_v1(logo_bytes.as_slice()).ok_or(Error::<T>::InvalidLogoBytes)?;
1501			ensure!(input_cid_binary == computed_cid_binary, Error::<T>::InvalidLogoBytes);
1502			ApprovedLogos::<T>::insert(&logo_cid, logo_bytes);
1503
1504			Self::deposit_event(Event::ApplicationContextUpdated {
1505				provider_id: ProviderId(provider_msa_id),
1506				application_id: None,
1507			});
1508			Ok(())
1509		}
1510
1511		/// Update a provider by means of governance approval
1512		///
1513		/// # Events
1514		/// * [`Event::ProviderUpdated`]
1515		///
1516		/// # Errors
1517		/// * [`Error::NoKeyExists`] - account does not have an MSA
1518		/// * [`Error::InvalidCid`] - If the provided CID is invalid.
1519		/// * [`Error::InvalidBCP47LanguageCode`] - If the provided BCP 47 language code is invalid.
1520		#[pallet::call_index(24)]
1521		#[pallet::weight(T::WeightInfo::update_provider_via_governance(
1522			payload.localized_names.len() as u32,
1523			payload.localized_logo_250_100_png_cids.len() as u32,
1524		))]
1525		#[allow(clippy::useless_conversion)]
1526		pub fn update_provider_via_governance(
1527			origin: OriginFor<T>,
1528			provider_key: T::AccountId,
1529			payload: ProviderRegistryEntry<
1530				T::MaxProviderNameSize,
1531				T::MaxLanguageCodeSize,
1532				T::MaxLogoCidSize,
1533				T::MaxLocaleCount,
1534			>,
1535		) -> DispatchResultWithPostInfo {
1536			T::CreateProviderViaGovernanceOrigin::ensure_origin(origin)?;
1537			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
1538			Self::ensure_correct_cids(&payload)?;
1539			let base_weight = T::WeightInfo::update_provider_via_governance(
1540				payload.localized_names.len() as u32,
1541				payload.localized_logo_250_100_png_cids.len() as u32,
1542			);
1543			let total_logos_removed = Self::upsert_provider_for(provider_msa_id, payload, true)?;
1544			Self::deposit_event(Event::ProviderUpdated {
1545				provider_id: ProviderId(provider_msa_id),
1546			});
1547			Self::refund_logo_removal_weight_by_count(total_logos_removed, base_weight)
1548		}
1549
1550		/// Propose to update provider registry via governance
1551		///
1552		/// # Arguments
1553		/// * `origin` - The origin of the call
1554		/// * `provider_key` - The key of the provider to update
1555		/// * `payload` - The new provider data
1556		///
1557		/// # Errors
1558		/// * [`Error::NoKeyExists`] - If there is not MSA for `origin`.
1559		/// * [`Error::ProviderNotRegistered`] - If the provider is not registered.
1560		/// * [`Error::InvalidCid`] - If the provided CID is invalid.
1561		/// * [`Error::InvalidBCP47LanguageCode`] - If the provided BCP 47 language code is invalid.
1562		#[pallet::call_index(25)]
1563		#[pallet::weight(T::WeightInfo::propose_to_update_provider(
1564			payload.localized_names.len() as u32,
1565			payload.localized_logo_250_100_png_cids.len() as u32,
1566		))]
1567		pub fn propose_to_update_provider(
1568			origin: OriginFor<T>,
1569			payload: ProviderRegistryEntry<
1570				T::MaxProviderNameSize,
1571				T::MaxLanguageCodeSize,
1572				T::MaxLogoCidSize,
1573				T::MaxLocaleCount,
1574			>,
1575		) -> DispatchResult {
1576			let proposer = ensure_signed(origin)?;
1577			let provider_msa_id = Self::ensure_valid_msa_key(&proposer)?;
1578			ensure!(
1579				Self::is_registered_provider(provider_msa_id),
1580				Error::<T>::ProviderNotRegistered
1581			);
1582			let proposal: Box<T::Proposal> = Box::new(
1583				(Call::<T>::update_provider_via_governance {
1584					provider_key: proposer.clone(),
1585					payload,
1586				})
1587				.into(),
1588			);
1589			let threshold = 1;
1590			T::ProposalProvider::propose(proposer, threshold, proposal)?;
1591			Ok(())
1592		}
1593
1594		/// Update application via governance for given existing `ApplicationIndex`
1595		///
1596		/// # Arguments
1597		/// * `origin` - The origin of the call
1598		/// * `provider_key` - The key of the provider to update
1599		/// * `application_index` - The index of the application to update
1600		/// * `payload` - The new application data
1601		///
1602		/// # Errors
1603		/// * [`Error::NoKeyExists`] - If there is not MSA for `origin`.
1604		/// * [`Error::ApplicationNotFound`] - If the application is not registered.
1605		/// * [`Error::InvalidCid`] - If the provided CID is invalid.
1606		/// * [`Error::InvalidBCP47LanguageCode`] - If the provided BCP 47 language code is invalid.
1607		#[pallet::call_index(26)]
1608		#[pallet::weight(T::WeightInfo::update_application_via_governance(
1609			payload.localized_names.len() as u32,
1610			payload.localized_logo_250_100_png_cids.len() as u32,
1611		))]
1612		#[allow(clippy::useless_conversion)]
1613		pub fn update_application_via_governance(
1614			origin: OriginFor<T>,
1615			provider_key: T::AccountId,
1616			application_index: ApplicationIndex,
1617			payload: ApplicationContext<
1618				T::MaxProviderNameSize,
1619				T::MaxLanguageCodeSize,
1620				T::MaxLogoCidSize,
1621				T::MaxLocaleCount,
1622			>,
1623		) -> DispatchResultWithPostInfo {
1624			T::CreateProviderViaGovernanceOrigin::ensure_origin(origin)?;
1625			let provider_msa_id = Self::ensure_valid_msa_key(&provider_key)?;
1626			ensure!(
1627				Self::is_registered_provider(provider_msa_id),
1628				Error::<T>::ProviderNotRegistered
1629			);
1630			Self::ensure_correct_cids(&payload)?;
1631			let base_weight = T::WeightInfo::update_application_via_governance(
1632				payload.localized_names.len() as u32,
1633				payload.localized_logo_250_100_png_cids.len() as u32,
1634			);
1635			let total_logos_removed = Self::upsert_application_for(
1636				ProviderId(provider_msa_id),
1637				application_index,
1638				payload,
1639			)?;
1640			Self::deposit_event(Event::ApplicationContextUpdated {
1641				provider_id: ProviderId(provider_msa_id),
1642				application_id: Some(application_index),
1643			});
1644			Self::refund_logo_removal_weight_by_count(total_logos_removed, base_weight)
1645		}
1646
1647		/// Propose to update application via governance for a given `ApplicationIndex`
1648		///
1649		/// # Arguments
1650		/// * `origin` - The origin of the call
1651		/// * `application_index` - The index of the application to update
1652		/// * `payload` - The new application data
1653		///
1654		/// # Errors
1655		/// * [`Error::NoKeyExists`] - If there is not MSA for `origin`.
1656		/// * [`Error::ApplicationNotFound`] - If the application is not registered.
1657		/// * [`Error::InvalidCid`] - If the provided CID is invalid.
1658		/// * [`Error::InvalidBCP47LanguageCode`] - If the provided BCP 47 language code is invalid.
1659		/// * [`Error::ApplicationNotFound`] - If the application is not registered.
1660		#[pallet::call_index(27)]
1661		#[pallet::weight(T::WeightInfo::propose_to_update_application(
1662			payload.localized_names.len() as u32,
1663			payload.localized_logo_250_100_png_cids.len() as u32,
1664		))]
1665		pub fn propose_to_update_application(
1666			origin: OriginFor<T>,
1667			application_index: ApplicationIndex,
1668			payload: ApplicationContext<
1669				T::MaxProviderNameSize,
1670				T::MaxLanguageCodeSize,
1671				T::MaxLogoCidSize,
1672				T::MaxLocaleCount,
1673			>,
1674		) -> DispatchResult {
1675			let proposer = ensure_signed(origin)?;
1676			let provider_msa_id = Self::ensure_valid_msa_key(&proposer)?;
1677			ensure!(
1678				Self::is_registered_provider(provider_msa_id),
1679				Error::<T>::ProviderNotRegistered
1680			);
1681			ensure!(
1682				ProviderToApplicationRegistry::<T>::contains_key(
1683					ProviderId(provider_msa_id),
1684					application_index
1685				),
1686				Error::<T>::ApplicationNotFound
1687			);
1688			Self::ensure_correct_cids(&payload)?;
1689			let proposal: Box<T::Proposal> = Box::new(
1690				(Call::<T>::update_application_via_governance {
1691					provider_key: proposer.clone(),
1692					application_index,
1693					payload,
1694				})
1695				.into(),
1696			);
1697			let threshold = 1;
1698			T::ProposalProvider::propose(proposer, threshold, proposal)?;
1699			Ok(())
1700		}
1701
1702		/// Create application allows creating an application registry without governance
1703		/// This call is blocked in release mode
1704		#[pallet::call_index(28)]
1705		#[pallet::weight(T::WeightInfo::create_application_via_governance(
1706			payload.localized_names.len() as u32,
1707			payload.localized_logo_250_100_png_cids.len() as u32,
1708		))]
1709		pub fn create_application(
1710			origin: OriginFor<T>,
1711			payload: ApplicationContext<
1712				T::MaxProviderNameSize,
1713				T::MaxLanguageCodeSize,
1714				T::MaxLogoCidSize,
1715				T::MaxLocaleCount,
1716			>,
1717		) -> DispatchResult {
1718			let provider_account = ensure_signed(origin)?;
1719			let provider_msa_id = Self::ensure_valid_msa_key(&provider_account)?;
1720			ensure!(
1721				Self::is_registered_provider(provider_msa_id),
1722				Error::<T>::ProviderNotRegistered
1723			);
1724			Self::ensure_correct_cids(&payload)?;
1725			let application_id =
1726				Self::create_application_for(ProviderId(provider_msa_id), payload)?;
1727			Self::deposit_event(Event::ApplicationCreated {
1728				provider_id: ProviderId(provider_msa_id),
1729				application_id,
1730			});
1731			Ok(())
1732		}
1733	}
1734}
1735
1736impl<T: Config> Pallet<T> {
1737	/// Check if a recovery provider is approved
1738	///
1739	/// # Arguments
1740	/// * `provider`: The provider to check
1741	///
1742	/// # Returns
1743	/// * [`bool`] - True if the provider is approved, false otherwise
1744	pub fn is_approved_recovery_provider(provider: &ProviderId) -> bool {
1745		RecoveryProviders::<T>::get(provider).unwrap_or(false)
1746	}
1747
1748	/// Compute the Recovery Commitment from Intermediary Hashes
1749	///
1750	/// # Arguments
1751	/// * `intermediary_hash_a`: Hash of the Recovery Secret
1752	/// * `intermediary_hash_b`: Hash of the Recovery Secret + Authentication Contact
1753	///
1754	/// # Returns
1755	/// * [`RecoveryCommitment`] - The computed Recovery Commitment
1756	pub fn compute_recovery_commitment(
1757		intermediary_hash_a: RecoveryHash,
1758		intermediary_hash_b: RecoveryHash,
1759	) -> RecoveryCommitment {
1760		let mut input = Vec::with_capacity(64);
1761		input.extend_from_slice(&intermediary_hash_a);
1762		input.extend_from_slice(&intermediary_hash_b);
1763		keccak_256(&input)
1764	}
1765
1766	/// Ensure that the provider is approved for recovery operations
1767	///
1768	/// # Arguments
1769	/// * `provider_key`: The provider's account key
1770	///
1771	/// # Returns
1772	/// * [`MessageSourceId`] - The provider's MSA ID if approved
1773	///
1774	/// # Errors
1775	/// * [`Error::NoKeyExists`] - Provider key is not associated with an MSA
1776	/// * [`Error::NotAuthorizedRecoveryProvider`] - Provider is not approved for recovery
1777	fn ensure_approved_recovery_provider(
1778		provider_key: &T::AccountId,
1779	) -> Result<MessageSourceId, DispatchError> {
1780		let provider_msa_id = Self::ensure_valid_msa_key(provider_key)?;
1781
1782		ensure!(
1783			Self::is_approved_recovery_provider(&ProviderId(provider_msa_id)),
1784			Error::<T>::NotAuthorizedRecoveryProvider
1785		);
1786
1787		Ok(provider_msa_id)
1788	}
1789
1790	/// Ensure that the recovery commitment is valid
1791	///
1792	/// # Arguments
1793	/// * `intermediary_hash_a`: Hash of the Recovery Secret
1794	/// * `intermediary_hash_b`: Hash of the Recovery Secret + Authentication Contact
1795	/// * `msa_id`: The MSA ID to verify the commitment for
1796	///
1797	/// # Returns
1798	/// * [`RecoveryCommitment`] - The validated recovery commitment
1799	///
1800	/// # Errors
1801	/// * [`Error::NoRecoveryCommitment`] - No Recovery Commitment exists for the MSA
1802	/// * [`Error::InvalidRecoveryCommitment`] - Recovery Commitment does not match
1803	fn ensure_valid_recovery_commitment(
1804		intermediary_hash_a: RecoveryHash,
1805		intermediary_hash_b: RecoveryHash,
1806		msa_id: MessageSourceId,
1807	) -> Result<RecoveryCommitment, DispatchError> {
1808		let recovery_commitment: RecoveryCommitment =
1809			Self::compute_recovery_commitment(intermediary_hash_a, intermediary_hash_b);
1810
1811		let stored_commitment: RecoveryCommitment =
1812			MsaIdToRecoveryCommitment::<T>::get(msa_id).ok_or(Error::<T>::NoRecoveryCommitment)?;
1813
1814		ensure!(recovery_commitment == stored_commitment, Error::<T>::InvalidRecoveryCommitment);
1815
1816		Ok(recovery_commitment)
1817	}
1818
1819	/// Ensure that the new key owner signature is valid
1820	///
1821	/// # Arguments
1822	/// * `new_control_key_proof`: Signature proof from the new control key
1823	/// * `add_key_payload`: Payload containing the new key and related data
1824	///
1825	/// # Errors
1826	/// * [`Error::NewKeyOwnershipInvalidSignature`] - Invalid signature from new key owner
1827	/// * [`Error::SignatureAlreadySubmitted`] - Signature has already been used
1828	fn ensure_valid_new_key_owner(
1829		new_control_key_proof: &MultiSignature,
1830		add_key_payload: &AddKeyData<T>,
1831	) -> DispatchResult {
1832		// The original MSA owner key is not available, therefore we cannot reuse the `add_public_key_to_msa` logic
1833		// to verify the signatures, only the new control key signature is verified.
1834		ensure!(
1835			Self::verify_signature(
1836				new_control_key_proof,
1837				&add_key_payload.new_public_key,
1838				add_key_payload
1839			),
1840			Error::<T>::NewKeyOwnershipInvalidSignature
1841		);
1842		Self::register_signature(new_control_key_proof, add_key_payload.expiration)?;
1843
1844		Ok(())
1845	}
1846
1847	/// Create the account for the `key`
1848	///
1849	/// # Errors
1850	/// * [`Error::MsaIdOverflow`]
1851	/// * [`Error::KeyLimitExceeded`]
1852	/// * [`Error::KeyAlreadyRegistered`]
1853	///
1854	pub fn create_account(
1855		key: T::AccountId,
1856	) -> Result<(MessageSourceId, T::AccountId), DispatchError> {
1857		let next_msa_id = Self::get_next_msa_id()?;
1858		Self::add_key(next_msa_id, &key)?;
1859		let _ = Self::set_msa_identifier(next_msa_id);
1860
1861		Ok((next_msa_id, key))
1862	}
1863
1864	/// Generate the next MSA Id
1865	///
1866	/// # Errors
1867	/// * [`Error::MsaIdOverflow`]
1868	///
1869	pub fn get_next_msa_id() -> Result<MessageSourceId, DispatchError> {
1870		let next = CurrentMsaIdentifierMaximum::<T>::get()
1871			.checked_add(1)
1872			.ok_or(Error::<T>::MsaIdOverflow)?;
1873
1874		Ok(next)
1875	}
1876
1877	/// Set the current identifier in storage
1878	pub fn set_msa_identifier(identifier: MessageSourceId) -> DispatchResult {
1879		CurrentMsaIdentifierMaximum::<T>::set(identifier);
1880
1881		Ok(())
1882	}
1883
1884	/// Create Register Provider
1885	pub fn create_registered_provider(
1886		provider_id: ProviderId,
1887		payload: ProviderRegistryEntry<
1888			T::MaxProviderNameSize,
1889			T::MaxLanguageCodeSize,
1890			T::MaxLogoCidSize,
1891			T::MaxLocaleCount,
1892		>,
1893	) -> DispatchResult {
1894		ProviderToRegistryEntryV2::<T>::try_mutate(
1895			provider_id,
1896			|maybe_metadata| -> DispatchResult {
1897				ensure!(
1898					maybe_metadata.take().is_none(),
1899					Error::<T>::DuplicateProviderRegistryEntry
1900				);
1901				*maybe_metadata = Some(payload);
1902				Ok(())
1903			},
1904		)
1905	}
1906
1907	/// Adds a list of schema permissions to a delegation relationship.
1908	pub fn grant_permissions_for_schemas(
1909		delegator_id: DelegatorId,
1910		provider_id: ProviderId,
1911		schema_ids: Vec<SchemaId>,
1912	) -> DispatchResult {
1913		Self::try_mutate_delegation(delegator_id, provider_id, |delegation, is_new_delegation| {
1914			ensure!(!is_new_delegation, Error::<T>::DelegationNotFound);
1915			Self::ensure_all_schema_ids_are_valid(&schema_ids)?;
1916
1917			PermittedDelegationSchemas::<T>::try_insert_schemas(delegation, schema_ids)?;
1918
1919			Ok(())
1920		})
1921	}
1922
1923	/// Revokes a list of schema permissions from a delegation relationship.
1924	pub fn revoke_permissions_for_schemas(
1925		delegator_id: DelegatorId,
1926		provider_id: ProviderId,
1927		schema_ids: Vec<SchemaId>,
1928	) -> DispatchResult {
1929		Self::try_mutate_delegation(delegator_id, provider_id, |delegation, is_new_delegation| {
1930			ensure!(!is_new_delegation, Error::<T>::DelegationNotFound);
1931			Self::ensure_all_schema_ids_are_valid(&schema_ids)?;
1932
1933			let current_block = frame_system::Pallet::<T>::block_number();
1934
1935			PermittedDelegationSchemas::<T>::try_get_mut_schemas(
1936				delegation,
1937				schema_ids,
1938				current_block,
1939			)?;
1940
1941			Ok(())
1942		})
1943	}
1944
1945	/// Add a new key to the MSA
1946	///
1947	/// # Errors
1948	/// * [`Error::KeyLimitExceeded`]
1949	/// * [`Error::KeyAlreadyRegistered`]
1950	///
1951	pub fn add_key(msa_id: MessageSourceId, key: &T::AccountId) -> DispatchResult {
1952		PublicKeyToMsaId::<T>::try_mutate(key, |maybe_msa_id| {
1953			ensure!(maybe_msa_id.is_none(), Error::<T>::KeyAlreadyRegistered);
1954			*maybe_msa_id = Some(msa_id);
1955
1956			// Increment the key counter
1957			<PublicKeyCountForMsaId<T>>::try_mutate(msa_id, |key_count| {
1958				// key_count:u8 should default to 0 if it does not exist
1959				let incremented_key_count =
1960					key_count.checked_add(1).ok_or(ArithmeticError::Overflow)?;
1961
1962				ensure!(
1963					incremented_key_count <= T::MaxPublicKeysPerMsa::get(),
1964					Error::<T>::KeyLimitExceeded
1965				);
1966
1967				*key_count = incremented_key_count;
1968				Ok(())
1969			})
1970		})
1971	}
1972
1973	/// Check that schema ids are all valid
1974	///
1975	/// # Errors
1976	/// * [`Error::InvalidSchemaId`]
1977	/// * [`Error::ExceedsMaxSchemaGrantsPerDelegation`]
1978	///
1979	pub fn ensure_all_schema_ids_are_valid(schema_ids: &[SchemaId]) -> DispatchResult {
1980		ensure!(
1981			schema_ids.len() <= T::MaxSchemaGrantsPerDelegation::get() as usize,
1982			Error::<T>::ExceedsMaxSchemaGrantsPerDelegation
1983		);
1984
1985		let are_schemas_valid = T::SchemaValidator::are_all_schema_ids_valid(schema_ids);
1986
1987		ensure!(are_schemas_valid, Error::<T>::InvalidSchemaId);
1988
1989		Ok(())
1990	}
1991
1992	/// Returns if provider is registered by checking if the [`ProviderToRegistryEntryV2`] contains the MSA id
1993	pub fn is_registered_provider(msa_id: MessageSourceId) -> bool {
1994		ProviderToRegistryEntryV2::<T>::contains_key(ProviderId(msa_id))
1995	}
1996
1997	/// Checks that a provider and delegator keys are valid
1998	/// and that a provider and delegator are not the same
1999	/// and that a provider has authorized a delegator to create a delegation relationship.
2000	///
2001	/// # Errors
2002	/// * [`Error::ProviderNotRegistered`]
2003	/// * [`Error::InvalidSelfProvider`]
2004	/// * [`Error::NoKeyExists`]
2005	///
2006	pub fn ensure_valid_registered_provider(
2007		delegator_key: &T::AccountId,
2008		provider_key: &T::AccountId,
2009	) -> Result<(ProviderId, DelegatorId), DispatchError> {
2010		let provider_msa_id = Self::ensure_valid_msa_key(provider_key)?;
2011		let delegator_msa_id = Self::ensure_valid_msa_key(delegator_key)?;
2012
2013		// Ensure that the delegator is not the provider.  You cannot delegate to yourself.
2014		ensure!(delegator_msa_id != provider_msa_id, Error::<T>::InvalidSelfProvider);
2015
2016		// Verify that the provider is a registered provider
2017		ensure!(Self::is_registered_provider(provider_msa_id), Error::<T>::ProviderNotRegistered);
2018
2019		Ok((provider_msa_id.into(), delegator_msa_id.into()))
2020	}
2021
2022	/// Checks that the MSA for `who` is the same as `msa_id`
2023	///
2024	/// # Errors
2025	/// * [`Error::NotMsaOwner`]
2026	/// * [`Error::NoKeyExists`]
2027	///
2028	pub fn ensure_msa_owner(who: &T::AccountId, msa_id: MessageSourceId) -> DispatchResult {
2029		let provider_msa_id = Self::ensure_valid_msa_key(who)?;
2030		ensure!(provider_msa_id == msa_id, Error::<T>::NotMsaOwner);
2031
2032		Ok(())
2033	}
2034
2035	/// Verify the `signature` was signed by `signer` on `payload` by a wallet
2036	/// Note the `wrap_binary_data` follows the Polkadot wallet pattern of wrapping with `<Byte>` tags.
2037	///
2038	/// # Errors
2039	/// * [`Error::InvalidSignature`]
2040	///
2041	pub fn verify_signature<P>(
2042		signature: &MultiSignature,
2043		signer: &T::AccountId,
2044		payload: &P,
2045	) -> bool
2046	where
2047		P: Encode + EIP712Encode,
2048	{
2049		let key = T::ConvertIntoAccountId32::convert((*signer).clone());
2050
2051		check_signature(signature, key, payload)
2052	}
2053
2054	/// Add a provider to a delegator with the default permissions
2055	///
2056	/// # Errors
2057	/// * [`Error::ExceedsMaxSchemaGrantsPerDelegation`]
2058	///
2059	pub fn add_provider(
2060		provider_id: ProviderId,
2061		delegator_id: DelegatorId,
2062		schema_ids: Vec<SchemaId>,
2063	) -> DispatchResult {
2064		Self::try_mutate_delegation(delegator_id, provider_id, |delegation, is_new_delegation| {
2065			ensure!(is_new_delegation, Error::<T>::DuplicateProvider);
2066			Self::ensure_all_schema_ids_are_valid(&schema_ids)?;
2067
2068			PermittedDelegationSchemas::<T>::try_insert_schemas(delegation, schema_ids)?;
2069
2070			Ok(())
2071		})
2072	}
2073
2074	/// Modify delegation's schema permissions
2075	///
2076	/// # Errors
2077	/// * [`Error::ExceedsMaxSchemaGrantsPerDelegation`]
2078	pub fn upsert_schema_permissions(
2079		provider_id: ProviderId,
2080		delegator_id: DelegatorId,
2081		schema_ids: Vec<SchemaId>,
2082	) -> DispatchResult {
2083		Self::try_mutate_delegation(delegator_id, provider_id, |delegation, _is_new_delegation| {
2084			Self::ensure_all_schema_ids_are_valid(&schema_ids)?;
2085
2086			// Create revoke and insert lists
2087			let mut revoke_ids: Vec<SchemaId> = Vec::new();
2088			let mut update_ids: Vec<SchemaId> = Vec::new();
2089			let mut insert_ids: Vec<SchemaId> = Vec::new();
2090
2091			let existing_keys = delegation.schema_permissions.keys();
2092
2093			for existing_schema_id in existing_keys {
2094				if !schema_ids.contains(existing_schema_id) {
2095					if let Some(block) = delegation.schema_permissions.get(existing_schema_id) {
2096						if *block == BlockNumberFor::<T>::zero() {
2097							revoke_ids.push(*existing_schema_id);
2098						}
2099					}
2100				}
2101			}
2102			for schema_id in &schema_ids {
2103				if !delegation.schema_permissions.contains_key(schema_id) {
2104					insert_ids.push(*schema_id);
2105				} else {
2106					update_ids.push(*schema_id);
2107				}
2108			}
2109
2110			let current_block = frame_system::Pallet::<T>::block_number();
2111
2112			// Revoke any that are not in the new list that are not already revoked
2113			PermittedDelegationSchemas::<T>::try_get_mut_schemas(
2114				delegation,
2115				revoke_ids,
2116				current_block,
2117			)?;
2118
2119			// Update any that are in the list but are not new
2120			PermittedDelegationSchemas::<T>::try_get_mut_schemas(
2121				delegation,
2122				update_ids,
2123				BlockNumberFor::<T>::zero(),
2124			)?;
2125
2126			// Insert any new ones that are not in the existing list
2127			PermittedDelegationSchemas::<T>::try_insert_schemas(delegation, insert_ids)?;
2128			delegation.revoked_at = BlockNumberFor::<T>::zero();
2129			Ok(())
2130		})
2131	}
2132
2133	/// Adds an association between MSA id and ProviderRegistryEntry. As of now, the
2134	/// only piece of metadata we are recording is provider name.
2135	///
2136	/// # Errors
2137	/// * [`Error::DuplicateProviderRegistryEntry`] - a ProviderRegistryEntry associated with the given MSA id already exists.
2138	///
2139	pub fn upsert_provider_for(
2140		provider_msa_id: MessageSourceId,
2141		payload: ProviderRegistryEntry<
2142			T::MaxProviderNameSize,
2143			T::MaxLanguageCodeSize,
2144			T::MaxLogoCidSize,
2145			T::MaxLocaleCount,
2146		>,
2147		is_update: bool,
2148	) -> Result<u32, DispatchError> {
2149		let mut total_logos_removed = 0;
2150		ProviderToRegistryEntryV2::<T>::try_mutate(
2151			ProviderId(provider_msa_id),
2152			|maybe_metadata| -> DispatchResult {
2153				if !is_update {
2154					ensure!(
2155						maybe_metadata.take().is_none(),
2156						Error::<T>::DuplicateProviderRegistryEntry
2157					);
2158				} else {
2159					total_logos_removed = Self::remove_logo_storage(maybe_metadata.as_ref())?;
2160				}
2161				Self::update_logo_storage(&payload)?;
2162
2163				*maybe_metadata = Some(payload);
2164				Ok(())
2165			},
2166		)
2167		.map(|_| total_logos_removed)
2168	}
2169
2170	/// Adds an association between Provider MSA id and ProviderToApplicationRegistryEntry
2171	///
2172	/// # Errors
2173	/// * [`Error::DuplicateApplicationRegistryEntry`] - a ProviderToApplicationRegistryEntry associated with the given Provider MSA id already exists.
2174	pub fn create_application_for(
2175		provider_msa_id: ProviderId,
2176		payload: ApplicationContext<
2177			T::MaxProviderNameSize,
2178			T::MaxLanguageCodeSize,
2179			T::MaxLogoCidSize,
2180			T::MaxLocaleCount,
2181		>,
2182	) -> Result<ApplicationIndex, DispatchError> {
2183		Self::update_logo_storage(&payload)?;
2184		let next_application_index = NextApplicationIndex::<T>::get(provider_msa_id);
2185		ensure!(
2186			!ProviderToApplicationRegistry::<T>::contains_key(
2187				provider_msa_id,
2188				next_application_index
2189			),
2190			Error::<T>::DuplicateApplicationRegistryEntry
2191		);
2192		ProviderToApplicationRegistry::<T>::insert(
2193			provider_msa_id,
2194			next_application_index,
2195			payload,
2196		);
2197		NextApplicationIndex::<T>::insert(provider_msa_id, next_application_index + 1);
2198		Ok(next_application_index)
2199	}
2200
2201	/// Updates an existing application from `ProviderToApplicationRegistry`
2202	///
2203	/// # Errors
2204	/// * [`Error::ApplicationNotFound`]
2205	pub fn upsert_application_for(
2206		provider_msa_id: ProviderId,
2207		application_index: ApplicationIndex,
2208		payload: ApplicationContext<
2209			T::MaxProviderNameSize,
2210			T::MaxLanguageCodeSize,
2211			T::MaxLogoCidSize,
2212			T::MaxLocaleCount,
2213		>,
2214	) -> Result<u32, DispatchError> {
2215		ensure!(
2216			ProviderToApplicationRegistry::<T>::contains_key(provider_msa_id, application_index),
2217			Error::<T>::ApplicationNotFound
2218		);
2219		let mut total_logos_removed = 0;
2220		ProviderToApplicationRegistry::<T>::try_mutate(
2221			provider_msa_id,
2222			application_index,
2223			|maybe_metadata| -> DispatchResult {
2224				total_logos_removed = Self::remove_logo_storage(maybe_metadata.as_ref())?;
2225				Self::update_logo_storage(&payload)?;
2226				*maybe_metadata = Some(payload);
2227				Ok(())
2228			},
2229		)
2230		.map(|_| total_logos_removed)
2231	}
2232
2233	/// Mutates the delegation relationship storage item only when the supplied function returns an 'Ok()' result.
2234	/// The callback function 'f' takes the value (a delegation) and a reference to a boolean variable. This callback
2235	/// sets the boolean variable to 'true' if the value is to be inserted and to 'false' if it is to be updated.
2236	pub fn try_mutate_delegation<R, E: From<DispatchError>>(
2237		delegator_id: DelegatorId,
2238		provider_id: ProviderId,
2239		f: impl FnOnce(
2240			&mut Delegation<SchemaId, BlockNumberFor<T>, T::MaxSchemaGrantsPerDelegation>,
2241			bool,
2242		) -> Result<R, E>,
2243	) -> Result<R, E> {
2244		DelegatorAndProviderToDelegation::<T>::try_mutate_exists(
2245			delegator_id,
2246			provider_id,
2247			|maybe_delegation_info| {
2248				let is_new = maybe_delegation_info.is_none();
2249				let mut delegation = maybe_delegation_info.take().unwrap_or_default();
2250
2251				let result = f(&mut delegation, is_new)?;
2252
2253				// only set the value if execution of 'f' is successful
2254				*maybe_delegation_info = Some(delegation);
2255				Ok(result)
2256			},
2257		)
2258	}
2259
2260	/// Deletes a key associated with a given MSA
2261	///
2262	/// # Errors
2263	/// * [`Error::NoKeyExists`]
2264	///
2265	pub fn delete_key_for_msa(msa_id: MessageSourceId, key: &T::AccountId) -> DispatchResult {
2266		PublicKeyToMsaId::<T>::try_mutate_exists(key, |maybe_msa_id| {
2267			ensure!(maybe_msa_id.is_some(), Error::<T>::NoKeyExists);
2268
2269			// Delete the key if it exists
2270			*maybe_msa_id = None;
2271
2272			<PublicKeyCountForMsaId<T>>::try_mutate_exists(msa_id, |key_count| {
2273				match key_count {
2274					Some(1) => *key_count = None,
2275					Some(count) => *count = *count - 1u8,
2276					None => (),
2277				}
2278
2279				Ok(())
2280			})
2281		})
2282	}
2283
2284	/// Revoke the grant for permissions from the delegator to the provider
2285	///
2286	/// # Errors
2287	/// * [`Error::DelegationRevoked`]
2288	/// * [`Error::DelegationNotFound`]
2289	///
2290	pub fn revoke_provider(provider_id: ProviderId, delegator_id: DelegatorId) -> DispatchResult {
2291		DelegatorAndProviderToDelegation::<T>::try_mutate_exists(
2292			delegator_id,
2293			provider_id,
2294			|maybe_info| -> DispatchResult {
2295				let mut info = maybe_info.take().ok_or(Error::<T>::DelegationNotFound)?;
2296
2297				ensure!(
2298					info.revoked_at == BlockNumberFor::<T>::default(),
2299					Error::<T>::DelegationRevoked
2300				);
2301
2302				let current_block = frame_system::Pallet::<T>::block_number();
2303				info.revoked_at = current_block;
2304				*maybe_info = Some(info);
2305				Ok(())
2306			},
2307		)?;
2308
2309		Ok(())
2310	}
2311
2312	/// Retrieves the MSA Id for a given `AccountId`
2313	pub fn get_owner_of(key: &T::AccountId) -> Option<MessageSourceId> {
2314		PublicKeyToMsaId::<T>::get(key)
2315	}
2316
2317	/// Retrieve MSA Id associated with `key` or return `NoKeyExists`
2318	pub fn ensure_valid_msa_key(key: &T::AccountId) -> Result<MessageSourceId, DispatchError> {
2319		let msa_id = PublicKeyToMsaId::<T>::get(key).ok_or(Error::<T>::NoKeyExists)?;
2320		Ok(msa_id)
2321	}
2322
2323	/// Get a list of Schema Ids that a provider has been granted access to
2324	///
2325	/// # Errors
2326	/// * [`Error::DelegationNotFound`]
2327	/// * [`Error::SchemaNotGranted`]
2328	///
2329	pub fn get_granted_schemas_by_msa_id(
2330		delegator: DelegatorId,
2331		provider: Option<ProviderId>,
2332	) -> Result<Vec<DelegationResponse<SchemaId, BlockNumberFor<T>>>, DispatchError> {
2333		let delegations = match provider {
2334			Some(provider_id) => vec![(
2335				provider_id,
2336				Self::get_delegation_of(delegator, provider_id)
2337					.ok_or(Error::<T>::DelegationNotFound)?,
2338			)],
2339			None => DelegatorAndProviderToDelegation::<T>::iter_prefix(delegator).collect(),
2340		};
2341
2342		let mut result = vec![];
2343		for (provider_id, provider_info) in delegations {
2344			let schema_permissions = provider_info.schema_permissions;
2345			// checking only if this is called for a specific provider
2346			if provider.is_some() && schema_permissions.is_empty() {
2347				return Err(Error::<T>::SchemaNotGranted.into());
2348			}
2349
2350			let mut schema_list = Vec::new();
2351			for (schema_id, revoked_at) in schema_permissions {
2352				if provider_info.revoked_at > BlockNumberFor::<T>::zero() &&
2353					(revoked_at > provider_info.revoked_at ||
2354						revoked_at == BlockNumberFor::<T>::zero())
2355				{
2356					schema_list
2357						.push(SchemaGrant { schema_id, revoked_at: provider_info.revoked_at });
2358				} else {
2359					schema_list.push(SchemaGrant { schema_id, revoked_at });
2360				}
2361			}
2362
2363			result.push(DelegationResponse { provider_id, permissions: schema_list });
2364		}
2365
2366		Ok(result)
2367	}
2368
2369	/// Converts an MSA ID into a synthetic Ethereum address (raw 20-byte format) by
2370	/// taking the last 20 bytes of the keccak256 hash of the following:
2371	/// [0..1]: 0xD9 (first byte of the keccak256 hash of the domain prefix "Frequency")
2372	/// [1..9]:   u64 (big endian)
2373	/// [9..41]:  keccack256("MSA Generated")
2374	pub fn msa_id_to_eth_address(id: MessageSourceId) -> H160 {
2375		/// First byte of the keccak256 hash of the domain prefix "Frequency"
2376		/// This "domain separator" ensures that the generated address will not collide with Ethereum addresses
2377		/// generated by the standard 'CREATE2' opcode.
2378		const DOMAIN_PREFIX: u8 = 0xD9;
2379
2380		lazy_static! {
2381			/// Salt used to generate MSA addresses
2382			static ref MSA_ADDRESS_SALT: [u8; 32] = keccak_256(b"MSA Generated");
2383		}
2384		let input_value = id.to_be_bytes();
2385
2386		let mut hash_input = [0u8; 41];
2387		hash_input[0] = DOMAIN_PREFIX;
2388		hash_input[1..9].copy_from_slice(&input_value);
2389		hash_input[9..].copy_from_slice(&(*MSA_ADDRESS_SALT));
2390
2391		let hash = keccak_256(&hash_input);
2392		H160::from_slice(&hash[12..])
2393	}
2394
2395	/// Returns a boolean indicating whether the given Ethereum address was generated from the given MSA ID.
2396	pub fn validate_eth_address_for_msa(address: &H160, msa_id: MessageSourceId) -> bool {
2397		let generated_address = Self::msa_id_to_eth_address(msa_id);
2398		*address == generated_address
2399	}
2400
2401	/// Converts a 20-byte synthetic Ethereum address into a checksummed string format,
2402	/// using ERC-55 checksum rules.
2403	/// Formats a 20-byte address into an EIP-55 checksummed `0x...` string.
2404	pub fn eth_address_to_checksummed_string(address: &H160) -> alloc::string::String {
2405		let addr_bytes = address.0;
2406		let addr_hex = hex::encode(addr_bytes);
2407		let hash = keccak_256(addr_hex.as_bytes());
2408
2409		let mut result = alloc::string::String::with_capacity(42);
2410		result.push_str("0x");
2411
2412		for (i, c) in addr_hex.chars().enumerate() {
2413			let hash_byte = hash[i / 2];
2414			let bit = if i % 2 == 0 { (hash_byte >> 4) & 0xf } else { hash_byte & 0xf };
2415
2416			result.push(if c.is_ascii_hexdigit() && c.is_ascii_alphabetic() {
2417				if bit >= 8 {
2418					c.to_ascii_uppercase()
2419				} else {
2420					c
2421				}
2422			} else {
2423				c
2424			});
2425		}
2426
2427		result
2428	}
2429
2430	/// Adds a signature to the `PayloadSignatureRegistryList`
2431	/// Check that mortality_block is within bounds. If so, proceed and add the new entry.
2432	/// Raises `SignatureAlreadySubmitted` if the signature exists in the registry.
2433	/// Raises `SignatureRegistryLimitExceeded` if the oldest signature of the list has not yet expired.
2434	///
2435	/// # Errors
2436	/// * [`Error::ProofNotYetValid`]
2437	/// * [`Error::ProofHasExpired`]
2438	/// * [`Error::SignatureAlreadySubmitted`]
2439	/// * [`Error::SignatureRegistryLimitExceeded`]
2440	///
2441	pub fn register_signature(
2442		signature: &MultiSignature,
2443		signature_expires_at: BlockNumberFor<T>,
2444	) -> DispatchResult {
2445		let current_block =
2446			Self::check_signature_against_registry(signature, signature_expires_at)?;
2447
2448		Self::enqueue_signature(signature, signature_expires_at, current_block)
2449	}
2450
2451	/// Check that mortality_block is within bounds.
2452	/// Raises `SignatureAlreadySubmitted` if the signature exists in the registry.
2453	///
2454	/// # Errors
2455	/// * [`Error::ProofNotYetValid`]
2456	/// * [`Error::ProofHasExpired`]
2457	/// * [`Error::SignatureAlreadySubmitted`]
2458	///
2459	pub fn check_signature_against_registry(
2460		signature: &MultiSignature,
2461		signature_expires_at: BlockNumberFor<T>,
2462	) -> Result<BlockNumberFor<T>, DispatchError> {
2463		let current_block: BlockNumberFor<T> = frame_system::Pallet::<T>::block_number();
2464
2465		let max_lifetime = Self::mortality_block_limit(current_block);
2466		ensure!(max_lifetime > signature_expires_at, Error::<T>::ProofNotYetValid);
2467		ensure!(current_block < signature_expires_at, Error::<T>::ProofHasExpired);
2468
2469		// Make sure it is not in the registry
2470		ensure!(
2471			!<PayloadSignatureRegistryList<T>>::contains_key(signature),
2472			Error::<T>::SignatureAlreadySubmitted
2473		);
2474		if let Some(signature_pointer) = PayloadSignatureRegistryPointer::<T>::get() {
2475			ensure!(signature_pointer.newest != *signature, Error::<T>::SignatureAlreadySubmitted);
2476		}
2477
2478		Ok(current_block)
2479	}
2480
2481	/// Do the actual enqueuing into the list storage and update the pointer
2482	///
2483	/// The signature registry consist of two storage items:
2484	/// - `PayloadSignatureRegistryList` - a linked list of signatures, in which each entry
2485	///   points to the next newest signature. The list is stored as a mapping of
2486	///   `MultiSignature` to a tuple of `(BlockNumberFor<T>, MultiSignature)`.
2487	///   The tuple contains the expiration block number and the next signature in the list.
2488	/// - `PayloadSignatureRegistryPointer` - a struct containing the newest signature,
2489	///   the oldest signature, the count of signatures, and the expiration block number of the newest signature.
2490	///
2491	/// NOTE: 'newest' and 'oldest' refer to the order in which the signatures were added to the list,
2492	/// which is not necessarily the order in which they expire.
2493	///
2494	/// Example: (key [signature], value [next newest signature])
2495	/// - `1,2 (oldest)`
2496	/// - `2,3`
2497	/// - `3,4`
2498	/// - 4 (newest in pointer storage)`
2499	///
2500	// DEVELOPER NOTE: As currently implemented, the signature registry list will continue to grow until it reaches
2501	// the maximum number of signatures, at which point it will remain at that size, only ever replacing the oldest
2502	// signature with the newest one. This is a trade-off between storage space and performance.
2503	fn enqueue_signature(
2504		signature: &MultiSignature,
2505		signature_expires_at: BlockNumberFor<T>,
2506		current_block: BlockNumberFor<T>,
2507	) -> DispatchResult {
2508		// Get the current pointer, or if this is the initialization, generate an empty pointer
2509		let pointer =
2510			PayloadSignatureRegistryPointer::<T>::get().unwrap_or(SignatureRegistryPointer {
2511				newest: signature.clone(),
2512				newest_expires_at: signature_expires_at,
2513				oldest: signature.clone(),
2514				count: 0,
2515			});
2516
2517		// Make sure it is not sitting as the `newest` in the pointer
2518		ensure!(
2519			!(pointer.count != 0 && pointer.newest.eq(signature)),
2520			Error::<T>::SignatureAlreadySubmitted
2521		);
2522
2523		// Default to the current oldest signature in case we are still filling up
2524		let mut oldest: MultiSignature = pointer.oldest.clone();
2525
2526		// We are now wanting to overwrite prior signatures
2527		let is_registry_full: bool = pointer.count == T::MaxSignaturesStored::get().unwrap_or(0);
2528
2529		// Maybe remove the oldest signature and update the oldest
2530		if is_registry_full {
2531			let (expire_block_number, next_oldest) =
2532				PayloadSignatureRegistryList::<T>::get(pointer.oldest.clone())
2533					.ok_or(Error::<T>::SignatureRegistryCorrupted)?;
2534
2535			ensure!(
2536				current_block.gt(&expire_block_number),
2537				Error::<T>::SignatureRegistryLimitExceeded
2538			);
2539
2540			// Move the oldest in the list to the next oldest signature
2541			oldest = next_oldest.clone();
2542
2543			<PayloadSignatureRegistryList<T>>::remove(pointer.oldest);
2544		}
2545
2546		// Add the newest signature if we are not the first
2547		if pointer.count != 0 {
2548			<PayloadSignatureRegistryList<T>>::insert(
2549				pointer.newest,
2550				(pointer.newest_expires_at, signature.clone()),
2551			);
2552		}
2553
2554		// Update the pointer.newest to have the signature that just came in
2555		PayloadSignatureRegistryPointer::<T>::put(SignatureRegistryPointer {
2556			// The count doesn't change if list is full
2557			count: if is_registry_full { pointer.count } else { pointer.count + 1 },
2558			newest: signature.clone(),
2559			newest_expires_at: signature_expires_at,
2560			oldest,
2561		});
2562
2563		Ok(())
2564	}
2565
2566	/// The furthest in the future a mortality_block value is allowed
2567	/// to be for current_block
2568	/// This is calculated to be past the risk of a replay attack
2569	fn mortality_block_limit(current_block: BlockNumberFor<T>) -> BlockNumberFor<T> {
2570		let mortality_size = T::MortalityWindowSize::get();
2571		current_block + BlockNumberFor::<T>::from(mortality_size)
2572	}
2573
2574	/// Validates a CID to conform to IPFS CIDv1 (or higher) formatting (does not validate decoded CID fields)
2575	///
2576	/// # Errors
2577	/// * [`Error::UnsupportedCidVersion`] - CID version is not supported (V0)
2578	/// * [`Error::InvalidCid`] - Unable to parse provided CID
2579	///
2580	fn validate_cid(in_cid: &[u8]) -> Result<Vec<u8>, DispatchError> {
2581		// Decode SCALE encoded CID into string slice
2582		let cid_str: &str = core::str::from_utf8(in_cid).map_err(|_| Error::<T>::InvalidCid)?;
2583		ensure!(cid_str.len() > 2, Error::<T>::InvalidCid);
2584		// starts_with handles Unicode multibyte characters safely
2585		ensure!(!cid_str.starts_with("Qm"), Error::<T>::UnsupportedCidVersion);
2586
2587		// Assume it's a multibase-encoded string. Decode it to a byte array so we can parse the CID.
2588		let cid_b = multibase::decode(cid_str).map_err(|_| Error::<T>::InvalidCid)?.1;
2589		ensure!(Cid::read_bytes(&cid_b[..]).is_ok(), Error::<T>::InvalidCid);
2590
2591		Ok(cid_b)
2592	}
2593
2594	/// Insert default logo and localized logos into storage `ApprovedLogos`
2595	fn update_logo_storage(
2596		payload: &ProviderRegistryEntry<
2597			T::MaxProviderNameSize,
2598			T::MaxLanguageCodeSize,
2599			T::MaxLogoCidSize,
2600			T::MaxLocaleCount,
2601		>,
2602	) -> DispatchResult {
2603		// store default logo CID if any
2604		if !payload.default_logo_250_100_png_cid.is_empty() {
2605			ApprovedLogos::<T>::insert(
2606				payload.default_logo_250_100_png_cid.clone(),
2607				BoundedVec::new(),
2608			);
2609		}
2610
2611		// store localized logos CIDs if any
2612		for (_, localized_cid) in &payload.localized_logo_250_100_png_cids {
2613			if !localized_cid.is_empty() {
2614				ApprovedLogos::<T>::insert(localized_cid, BoundedVec::new());
2615			}
2616		}
2617
2618		Ok(())
2619	}
2620
2621	/// Remove default logo and localized logos from storage `ApprovedLogos`
2622	fn remove_logo_storage(
2623		existing_payload: Option<
2624			&ProviderRegistryEntry<
2625				T::MaxProviderNameSize,
2626				T::MaxLanguageCodeSize,
2627				T::MaxLogoCidSize,
2628				T::MaxLocaleCount,
2629			>,
2630		>,
2631	) -> Result<u32, DispatchError> {
2632		let mut total_logo_removed = 0;
2633		if let Some(payload) = existing_payload {
2634			// remove default logo CID if any
2635			if !payload.default_logo_250_100_png_cid.is_empty() {
2636				total_logo_removed += 1;
2637				ApprovedLogos::<T>::remove(&payload.default_logo_250_100_png_cid);
2638			}
2639			// remove localized logos CIDs if any
2640			for (_, localized_cid) in &payload.localized_logo_250_100_png_cids {
2641				if !localized_cid.is_empty() {
2642					total_logo_removed += 1;
2643					ApprovedLogos::<T>::remove(localized_cid);
2644				}
2645			}
2646		}
2647		Ok(total_logo_removed)
2648	}
2649
2650	/// Checks if cid for logo and localized logos is valid
2651	fn ensure_correct_cids(
2652		payload: &ProviderRegistryEntry<
2653			T::MaxProviderNameSize,
2654			T::MaxLanguageCodeSize,
2655			T::MaxLogoCidSize,
2656			T::MaxLocaleCount,
2657		>,
2658	) -> DispatchResult {
2659		// Validate default logo CID only if non-empty
2660		if !payload.default_logo_250_100_png_cid.is_empty() {
2661			Self::validate_cid(&payload.default_logo_250_100_png_cid)?;
2662		}
2663
2664		// Validate each localized logo CID only if non-empty
2665		for (lang_code, localized_cid) in &payload.localized_logo_250_100_png_cids {
2666			// First validate the language code
2667			let code_str = core::str::from_utf8(lang_code)
2668				.map_err(|_| Error::<T>::InvalidBCP47LanguageCode)?;
2669
2670			if !Self::is_valid_bcp47(code_str) {
2671				return Err(Error::<T>::InvalidBCP47LanguageCode.into());
2672			}
2673
2674			// Then validate the CID if it's not empty
2675			if !localized_cid.is_empty() {
2676				Self::validate_cid(localized_cid)?;
2677			}
2678		}
2679
2680		// Validate each localized name
2681		for (lang_code, _) in &payload.localized_names {
2682			// First validate the language code
2683			let code_str = core::str::from_utf8(lang_code)
2684				.map_err(|_| Error::<T>::InvalidBCP47LanguageCode)?;
2685			if !Self::is_valid_bcp47(code_str) {
2686				return Err(Error::<T>::InvalidBCP47LanguageCode.into());
2687			}
2688		}
2689
2690		Ok(())
2691	}
2692
2693	/// Checks if the provided BCP-47 language code is valid.
2694	fn is_valid_bcp47(code: &str) -> bool {
2695		// Must not be empty
2696		if code.is_empty() {
2697			return false;
2698		}
2699		// No leading, trailing, or consecutive dashes
2700		if code.starts_with('-') || code.ends_with('-') || code.contains("--") {
2701			return false;
2702		}
2703		for part in code.split('-') {
2704			let len = part.len();
2705			if len < 2 || !part.chars().all(|c| c.is_ascii_alphanumeric()) {
2706				return false;
2707			}
2708		}
2709		true
2710	}
2711
2712	/// Retrieves the provider or application context including logos and localized name if any
2713	pub fn get_provider_application_context(
2714		provider_id: ProviderId,
2715		application_id: Option<ApplicationIndex>,
2716		locale: Option<Vec<u8>>,
2717	) -> Option<ProviderApplicationContext> {
2718		let bounded_locale = locale.and_then(|loc| BoundedVec::try_from(loc).ok());
2719		let provider_or_application_registry = match application_id {
2720			Some(app_id) => ProviderToApplicationRegistry::<T>::get(provider_id, app_id)?,
2721			None => ProviderToRegistryEntryV2::<T>::get(provider_id)?,
2722		};
2723		let default_name = provider_or_application_registry.default_name.to_vec();
2724		let default_logo_cid = provider_or_application_registry.default_logo_250_100_png_cid;
2725		// Default logo bytes
2726		let default_logo_250_100_png_bytes: Option<Vec<u8>> =
2727			ApprovedLogos::<T>::get(default_logo_cid).map(|bv| bv.to_vec());
2728		let mut localized_name: Option<Vec<u8>> = None;
2729		// Localized logo bytes if any
2730		let localized_logo_250_100_png_bytes: Option<Vec<u8>> = bounded_locale.and_then(|locale| {
2731			// Localized name if any
2732			localized_name = provider_or_application_registry
2733				.localized_names
2734				.get(&locale)
2735				.map(|bv| bv.to_vec());
2736
2737			provider_or_application_registry
2738				.localized_logo_250_100_png_cids
2739				.get(&locale)
2740				.and_then(|cid| ApprovedLogos::<T>::get(cid).map(|bv| bv.to_vec()))
2741		});
2742
2743		Some(ProviderApplicationContext {
2744			default_name,
2745			provider_id,
2746			application_id,
2747			default_logo_250_100_png_bytes,
2748			localized_logo_250_100_png_bytes,
2749			localized_name,
2750		})
2751	}
2752
2753	/// Refund weights for logos not removed from worst case scenario
2754	fn refund_logo_removal_weight_by_count(
2755		total_logos_removed: u32,
2756		base_weight: Weight,
2757	) -> DispatchResultWithPostInfo {
2758		// Weight adjustment: refund weight for logos that were NOT removed
2759		let max_logos_benchmark_assumed = T::MaxLocaleCount::get();
2760		let removal_over_charged_by =
2761			max_logos_benchmark_assumed.saturating_sub(total_logos_removed);
2762		let weight_per_logo_removal = T::DbWeight::get().writes(1);
2763		let weight_to_refund =
2764			weight_per_logo_removal.saturating_mul(removal_over_charged_by as u64);
2765		let actual_weight_used = base_weight.saturating_sub(weight_to_refund);
2766		Ok(PostDispatchInfo { actual_weight: Some(actual_weight_used), pays_fee: Pays::Yes })
2767	}
2768}
2769
2770#[cfg(feature = "runtime-benchmarks")]
2771impl<T: Config> MsaBenchmarkHelper<T::AccountId> for Pallet<T> {
2772	/// adds delegation relationship with permitted schema ids
2773	fn set_delegation_relationship(
2774		provider: ProviderId,
2775		delegator: DelegatorId,
2776		schemas: Vec<SchemaId>,
2777	) -> DispatchResult {
2778		Self::add_provider(provider, delegator, schemas)?;
2779		Ok(())
2780	}
2781
2782	/// adds a new key to specified msa
2783	fn add_key(msa_id: MessageSourceId, key: T::AccountId) -> DispatchResult {
2784		Self::add_key(msa_id, &key)?;
2785		Ok(())
2786	}
2787}
2788
2789#[cfg(feature = "runtime-benchmarks")]
2790impl<T: Config> RegisterProviderBenchmarkHelper for Pallet<T> {
2791	/// Create a registered provider for benchmarks
2792	fn create(provider_id: MessageSourceId, name: Vec<u8>) -> DispatchResult {
2793		let name = BoundedVec::<u8, T::MaxProviderNameSize>::try_from(name).expect("error");
2794		let payload = ProviderRegistryEntry {
2795			default_name: name,
2796			localized_names: BoundedBTreeMap::new(),
2797			default_logo_250_100_png_cid: BoundedVec::new(),
2798			localized_logo_250_100_png_cids: BoundedBTreeMap::new(),
2799		};
2800		Self::create_registered_provider(provider_id.into(), payload)?;
2801
2802		Ok(())
2803	}
2804}
2805
2806impl<T: Config> MsaLookup for Pallet<T> {
2807	type AccountId = T::AccountId;
2808
2809	fn get_msa_id(key: &Self::AccountId) -> Option<MessageSourceId> {
2810		Self::get_owner_of(key)
2811	}
2812}
2813
2814impl<T: Config> MsaValidator for Pallet<T> {
2815	type AccountId = T::AccountId;
2816
2817	fn ensure_valid_msa_key(key: &T::AccountId) -> Result<MessageSourceId, DispatchError> {
2818		Self::ensure_valid_msa_key(key)
2819	}
2820}
2821
2822impl<T: Config> ProviderLookup for Pallet<T> {
2823	type BlockNumber = BlockNumberFor<T>;
2824	type MaxSchemaGrantsPerDelegation = T::MaxSchemaGrantsPerDelegation;
2825	type SchemaId = SchemaId;
2826
2827	fn get_delegation_of(
2828		delegator: DelegatorId,
2829		provider: ProviderId,
2830	) -> Option<Delegation<SchemaId, Self::BlockNumber, Self::MaxSchemaGrantsPerDelegation>> {
2831		DelegatorAndProviderToDelegation::<T>::get(delegator, provider)
2832	}
2833}
2834
2835impl<T: Config> DelegationValidator for Pallet<T> {
2836	type BlockNumber = BlockNumberFor<T>;
2837	type MaxSchemaGrantsPerDelegation = T::MaxSchemaGrantsPerDelegation;
2838	type SchemaId = SchemaId;
2839
2840	/// Check that the delegator has an active delegation to the provider.
2841	/// `block_number`: Provide `None` to know if the delegation is active at the current block.
2842	///                 Provide Some(N) to know if the delegation was or will be active at block N.
2843	///
2844	/// # Errors
2845	/// * [`Error::DelegationNotFound`]
2846	/// * [`Error::DelegationRevoked`]
2847	/// * [`Error::CannotPredictValidityPastCurrentBlock`]
2848	///
2849	fn ensure_valid_delegation(
2850		provider_id: ProviderId,
2851		delegator_id: DelegatorId,
2852		block_number: Option<BlockNumberFor<T>>,
2853	) -> Result<
2854		Delegation<SchemaId, BlockNumberFor<T>, T::MaxSchemaGrantsPerDelegation>,
2855		DispatchError,
2856	> {
2857		let info = DelegatorAndProviderToDelegation::<T>::get(delegator_id, provider_id)
2858			.ok_or(Error::<T>::DelegationNotFound)?;
2859		let current_block = frame_system::Pallet::<T>::block_number();
2860		let requested_block = match block_number {
2861			Some(block_number) => {
2862				ensure!(
2863					current_block >= block_number,
2864					Error::<T>::CannotPredictValidityPastCurrentBlock
2865				);
2866				block_number
2867			},
2868			None => current_block,
2869		};
2870
2871		if info.revoked_at == BlockNumberFor::<T>::zero() {
2872			return Ok(info);
2873		}
2874		ensure!(info.revoked_at >= requested_block, Error::<T>::DelegationRevoked);
2875
2876		Ok(info)
2877	}
2878}
2879
2880impl<T: Config> TargetValidator for Pallet<T> {
2881	fn validate(target: MessageSourceId) -> bool {
2882		Self::is_registered_provider(target)
2883	}
2884}
2885
2886impl<T: Config> SchemaGrantValidator<BlockNumberFor<T>> for Pallet<T> {
2887	/// Check if provider is allowed to publish for a given schema_id for a given delegator
2888	///
2889	/// # Errors
2890	/// * [`Error::DelegationNotFound`]
2891	/// * [`Error::DelegationRevoked`]
2892	/// * [`Error::SchemaNotGranted`]
2893	/// * [`Error::CannotPredictValidityPastCurrentBlock`]
2894	///
2895	fn ensure_valid_schema_grant(
2896		provider: ProviderId,
2897		delegator: DelegatorId,
2898		schema_id: SchemaId,
2899		block_number: BlockNumberFor<T>,
2900	) -> DispatchResult {
2901		let provider_info = Self::ensure_valid_delegation(provider, delegator, Some(block_number))?;
2902
2903		let schema_permission_revoked_at_block_number = provider_info
2904			.schema_permissions
2905			.get(&schema_id)
2906			.ok_or(Error::<T>::SchemaNotGranted)?;
2907
2908		if *schema_permission_revoked_at_block_number == BlockNumberFor::<T>::zero() {
2909			return Ok(());
2910		}
2911
2912		ensure!(
2913			block_number <= *schema_permission_revoked_at_block_number,
2914			Error::<T>::SchemaNotGranted
2915		);
2916
2917		Ok(())
2918	}
2919}
2920
2921impl<T: Config> MsaKeyProvider for Pallet<T> {
2922	type AccountId = T::AccountId;
2923	// Returns true if ALL of the following are true:
2924	// - The new key is Ethereum-compatible
2925	// - Msa exists
2926	// - The stored msa_id for the key == `msa_id`
2927	// - It has only one key associated with it
2928	fn key_eligible_for_subsidized_addition(
2929		old_key: Self::AccountId,
2930		new_key: Self::AccountId,
2931		msa_id: MessageSourceId,
2932	) -> bool {
2933		let new_address32 = T::ConvertIntoAccountId32::convert((new_key).clone());
2934		if EthereumAddressMapper::is_ethereum_address(&new_address32) {
2935			if let Some(stored_msa_id) = Self::get_msa_id(&old_key) {
2936				return stored_msa_id == msa_id && PublicKeyCountForMsaId::<T>::get(msa_id).eq(&1u8);
2937			}
2938		}
2939		false
2940	}
2941}
2942
2943/// The TransactionExtension trait is implemented on CheckFreeExtrinsicUse to validate that a provider
2944/// has not already been revoked if the calling extrinsic is revoking a provider to an MSA. The
2945/// purpose of this is to ensure that the revoke_delegation_by_delegator extrinsic cannot be
2946/// repeatedly called and flood the network.
2947#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
2948#[scale_info(skip_type_params(T))]
2949pub struct CheckFreeExtrinsicUse<T: Config + Send + Sync>(PhantomData<T>);
2950
2951impl<T: Config + Send + Sync> CheckFreeExtrinsicUse<T> {
2952	/// Validates the delegation by making sure that the MSA ids used are valid and the delegation is
2953	/// is still active. Returns a `ValidTransaction` or wrapped [`ValidityError`]
2954	/// # Arguments:
2955	/// * `account_id`: the account id of the delegator that is revoking the delegation relationship
2956	/// *  `provider_msa_id` the MSA ID of the provider (the "other end" of the delegation).
2957	///
2958	/// # Errors
2959	/// * [`ValidityError::InvalidMsaKey`] - if  `account_id` does not have an MSA
2960	/// * [`ValidityError::InvalidDelegation`] - if the delegation with `delegator_msa_id` is invalid
2961	///
2962	pub fn validate_delegation_by_delegator(
2963		account_id: &T::AccountId,
2964		provider_msa_id: &MessageSourceId,
2965	) -> TransactionValidity {
2966		const TAG_PREFIX: &str = "DelegatorDelegationRevocation";
2967		let delegator_msa_id: DelegatorId = Pallet::<T>::ensure_valid_msa_key(account_id)
2968			.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8))?
2969			.into();
2970		let provider_msa_id = ProviderId(*provider_msa_id);
2971
2972		Pallet::<T>::ensure_valid_delegation(provider_msa_id, delegator_msa_id, None)
2973			.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidDelegation as u8))?;
2974		ValidTransaction::with_tag_prefix(TAG_PREFIX).and_provides(account_id).build()
2975	}
2976
2977	/// Validates the delegation by making sure that the MSA ids used are valid and that the delegation
2978	/// is still active. Returns a `ValidTransaction` or wrapped [`ValidityError`]
2979	/// # Arguments:
2980	/// * `account_id`: the account id of the provider that is revoking the delegation relationship
2981	/// *  `delegator_msa_id` the MSA ID of the delegator (the "other end" of the delegation).
2982	///
2983	/// # Errors
2984	/// * [`ValidityError::InvalidMsaKey`] - if  `account_id` does not have an MSA
2985	/// * [`ValidityError::InvalidDelegation`] - if the delegation with `delegator_msa_id` is invalid
2986	///
2987	pub fn validate_delegation_by_provider(
2988		account_id: &T::AccountId,
2989		delegator_msa_id: &MessageSourceId,
2990	) -> TransactionValidity {
2991		const TAG_PREFIX: &str = "ProviderDelegationRevocation";
2992
2993		let provider_msa_id: ProviderId = Pallet::<T>::ensure_valid_msa_key(account_id)
2994			.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8))?
2995			.into();
2996		let delegator_msa_id = DelegatorId(*delegator_msa_id);
2997
2998		// Verify the delegation exists and is active
2999		Pallet::<T>::ensure_valid_delegation(provider_msa_id, delegator_msa_id, None)
3000			.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidDelegation as u8))?;
3001		ValidTransaction::with_tag_prefix(TAG_PREFIX).and_provides(account_id).build()
3002	}
3003
3004	/// Validates that a key being revoked is both valid and owned by a valid MSA account.
3005	/// Returns a `ValidTransaction` or wrapped [`ValidityError::InvalidMsaKey`]
3006	/// Arguments:
3007	/// * `signing_public_key`: the account id calling for revoking the key, and which
3008	///   owns the msa also associated with `key`
3009	/// * `public_key_to_delete`: the account id to revoke as an access key for account_id's msa
3010	///
3011	/// # Errors
3012	/// * [`ValidityError::InvalidSelfRemoval`] - if `signing_public_key` and `public_key_to_delete` are the same.
3013	/// * [`ValidityError::InvalidMsaKey`] - if  `account_id` does not have an MSA or if
3014	///   'public_key_to_delete' does not have an MSA.
3015	/// * [`ValidityError::NotKeyOwner`] - if the `signing_public_key` and `public_key_to_delete` do not belong to the same MSA ID.
3016	pub fn validate_key_delete(
3017		signing_public_key: &T::AccountId,
3018		public_key_to_delete: &T::AccountId,
3019	) -> TransactionValidity {
3020		const TAG_PREFIX: &str = "KeyRevocation";
3021
3022		ensure!(
3023			signing_public_key != public_key_to_delete,
3024			InvalidTransaction::Custom(ValidityError::InvalidSelfRemoval as u8)
3025		);
3026
3027		let maybe_owner_msa_id: MessageSourceId =
3028			Pallet::<T>::ensure_valid_msa_key(signing_public_key)
3029				.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8))?;
3030
3031		let msa_id_for_key_to_delete: MessageSourceId =
3032			Pallet::<T>::ensure_valid_msa_key(public_key_to_delete)
3033				.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8))?;
3034
3035		ensure!(
3036			maybe_owner_msa_id == msa_id_for_key_to_delete,
3037			InvalidTransaction::Custom(ValidityError::NotKeyOwner as u8)
3038		);
3039
3040		ValidTransaction::with_tag_prefix(TAG_PREFIX)
3041			.and_provides(signing_public_key)
3042			.build()
3043	}
3044
3045	/// Validates that a MSA being retired exists, does not belong to a registered provider,
3046	/// that `account_id` is the only access key associated with the MSA,
3047	/// does not have a token balance associated with it,
3048	/// and that there are no delegations to providers.
3049	/// Returns a `ValidTransaction` or wrapped [`ValidityError]
3050	/// # Arguments:
3051	/// * account_id: the account id associated with the MSA to retire
3052	///
3053	/// # Errors
3054	/// * [`ValidityError::InvalidMsaKey`]
3055	/// * [`ValidityError::InvalidRegisteredProviderCannotBeRetired`]
3056	/// * [`ValidityError::InvalidMoreThanOneKeyExists`]
3057	/// * [`ValidityError::InvalidNonZeroProviderDelegations`]
3058	/// * [`ValidityError::InvalidMsaHoldingTokenCannotBeRetired`]
3059	///
3060	pub fn ensure_msa_can_retire(account_id: &T::AccountId) -> TransactionValidity {
3061		const TAG_PREFIX: &str = "MSARetirement";
3062		let msa_id = Pallet::<T>::ensure_valid_msa_key(account_id)
3063			.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8))?;
3064
3065		ensure!(
3066			!Pallet::<T>::is_registered_provider(msa_id),
3067			InvalidTransaction::Custom(
3068				ValidityError::InvalidRegisteredProviderCannotBeRetired as u8
3069			)
3070		);
3071
3072		let msa_handle = T::HandleProvider::get_handle_for_msa(msa_id);
3073		ensure!(
3074			msa_handle.is_none(),
3075			InvalidTransaction::Custom(ValidityError::HandleNotRetired as u8)
3076		);
3077
3078		let key_count = PublicKeyCountForMsaId::<T>::get(msa_id);
3079		ensure!(
3080			key_count == 1,
3081			InvalidTransaction::Custom(ValidityError::InvalidMoreThanOneKeyExists as u8)
3082		);
3083
3084		let delegator_id = DelegatorId(msa_id);
3085		let has_active_delegations: bool = DelegatorAndProviderToDelegation::<T>::iter_key_prefix(
3086			delegator_id,
3087		)
3088		.any(|provider_id| {
3089			Pallet::<T>::ensure_valid_delegation(provider_id, delegator_id, None).is_ok()
3090		});
3091
3092		ensure!(
3093			!has_active_delegations,
3094			InvalidTransaction::Custom(ValidityError::InvalidNonZeroProviderDelegations as u8)
3095		);
3096
3097		// - Get account address for MSA
3098		let msa_address = Pallet::<T>::msa_id_to_eth_address(msa_id);
3099
3100		// - Convert to AccountId
3101		let mut bytes = &EthereumAddressMapper::to_bytes32(&msa_address.0)[..];
3102		let msa_account_id = T::AccountId::decode(&mut bytes).map_err(|_| {
3103			log::error!("Failed to decode MSA account ID from Ethereum address");
3104			InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8)
3105		})?;
3106
3107		// - Check that the MSA does not have a token balance
3108		let msa_balance = T::Currency::reducible_balance(
3109			&msa_account_id,
3110			Preservation::Expendable,
3111			Fortitude::Polite,
3112		);
3113		ensure!(
3114			msa_balance == Zero::zero(),
3115			InvalidTransaction::Custom(ValidityError::InvalidMsaHoldingTokenCannotBeRetired as u8)
3116		);
3117
3118		ValidTransaction::with_tag_prefix(TAG_PREFIX).and_provides(account_id).build()
3119	}
3120
3121	/// Validates that a request to withdraw tokens from an MSA passes the following checks:
3122	/// - Receiver (origin) is NOT a control key for any MSA
3123	/// - Signed payload matches MSA ID and signature validation
3124	/// - Signature is not a duplicate
3125	/// - MSA has tokens available to be withdrawn
3126	///
3127	/// # Errors
3128	///
3129	/// `[ValidityError::NotKeyOwner]` - transaction origin does not match the authorized public key
3130	/// `[ValidityError::MsaOwnershipInvalidSignature]` - payload verification failed (bad signature, duplicate signature, invalid payload, payload/signature mismatch)
3131	/// `[ValidityError::IneligibleOrigin]` - transaction origin is an MSA control key
3132	/// `[ValidityError::InsufficientBalanceToWithdraw]` - MSA balance is zero
3133	/// `[ValidityError::InvalidMsaKey]` - signing MSA control key does not match MSA ID in payload
3134	///
3135	pub fn validate_msa_token_withdrawal(
3136		receiver_account_id: &T::AccountId,
3137		msa_owner_public_key: &T::AccountId,
3138		msa_owner_proof: &MultiSignature,
3139		authorization_payload: &AuthorizedKeyData<T>,
3140	) -> TransactionValidity {
3141		const TAG_PREFIX: &str = "MsaTokenWithdrawal";
3142
3143		ensure!(
3144			*receiver_account_id == authorization_payload.authorized_public_key,
3145			InvalidTransaction::Custom(ValidityError::NotKeyOwner as u8)
3146		);
3147
3148		ensure!(
3149			authorization_payload.discriminant == PayloadTypeDiscriminator::AuthorizedKeyData,
3150			InvalidTransaction::Custom(ValidityError::MsaOwnershipInvalidSignature as u8)
3151		);
3152		ensure!(
3153			Pallet::<T>::verify_signature(
3154				msa_owner_proof,
3155				msa_owner_public_key,
3156				authorization_payload
3157			),
3158			InvalidTransaction::Custom(ValidityError::MsaOwnershipInvalidSignature as u8)
3159		);
3160
3161		// Origin must NOT be an MSA control key
3162		ensure!(
3163			!PublicKeyToMsaId::<T>::contains_key(receiver_account_id),
3164			InvalidTransaction::Custom(ValidityError::IneligibleOrigin as u8)
3165		);
3166
3167		Pallet::<T>::check_signature_against_registry(
3168			msa_owner_proof,
3169			authorization_payload.expiration,
3170		)
3171		.map_err(|_| {
3172			InvalidTransaction::Custom(ValidityError::MsaOwnershipInvalidSignature as u8)
3173		})?;
3174
3175		let msa_id = authorization_payload.msa_id;
3176
3177		Pallet::<T>::ensure_msa_owner(msa_owner_public_key, msa_id)
3178			.map_err(|_| InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8))?;
3179
3180		// - Get account address for MSA
3181		let msa_address = Pallet::<T>::msa_id_to_eth_address(msa_id);
3182
3183		// - Convert to AccountId
3184		let mut bytes = &EthereumAddressMapper::to_bytes32(&msa_address.0)[..];
3185		let msa_account_id = T::AccountId::decode(&mut bytes).map_err(|_| {
3186			log::error!("Failed to decode MSA account ID from Ethereum address");
3187			InvalidTransaction::Custom(ValidityError::InvalidMsaKey as u8)
3188		})?;
3189
3190		// - Check that the MSA has a balance to withdraw
3191		let msa_balance = T::Currency::reducible_balance(
3192			&msa_account_id,
3193			Preservation::Expendable,
3194			Fortitude::Polite,
3195		);
3196		ensure!(
3197			msa_balance > Zero::zero(),
3198			InvalidTransaction::Custom(ValidityError::InsufficientBalanceToWithdraw as u8)
3199		);
3200		ValidTransaction::with_tag_prefix(TAG_PREFIX)
3201			.and_provides(receiver_account_id)
3202			.build()
3203	}
3204}
3205
3206/// Errors related to the validity of the CheckFreeExtrinsicUse signed extension.
3207pub enum ValidityError {
3208	/// Delegation to provider is not found or expired.
3209	InvalidDelegation,
3210	/// MSA key as been revoked.
3211	InvalidMsaKey,
3212	/// Cannot retire a registered provider MSA
3213	InvalidRegisteredProviderCannotBeRetired,
3214	/// More than one account key exists for the MSA during retire attempt
3215	InvalidMoreThanOneKeyExists,
3216	/// A transaction's Origin (AccountId) may not remove itself
3217	InvalidSelfRemoval,
3218	/// NotKeyOwner
3219	NotKeyOwner,
3220	/// InvalidNonZeroProviderDelegations
3221	InvalidNonZeroProviderDelegations,
3222	/// HandleNotRetired
3223	HandleNotRetired,
3224	/// Cryptographic signature verification failed
3225	MsaOwnershipInvalidSignature,
3226	/// MSA balance is zero
3227	InsufficientBalanceToWithdraw,
3228	/// Origin is ineleligible for the current transaction
3229	IneligibleOrigin,
3230	/// Cannot retire an MSA that has a token balance
3231	InvalidMsaHoldingTokenCannotBeRetired,
3232}
3233
3234impl<T: Config + Send + Sync> CheckFreeExtrinsicUse<T> {
3235	/// Create new `TransactionExtension` to check runtime version.
3236	pub fn new() -> Self {
3237		Self(PhantomData)
3238	}
3239}
3240
3241impl<T: Config + Send + Sync> core::fmt::Debug for CheckFreeExtrinsicUse<T> {
3242	#[cfg(feature = "std")]
3243	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
3244		write!(f, "CheckFreeExtrinsicUse<{:?}>", self.0)
3245	}
3246	#[cfg(not(feature = "std"))]
3247	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
3248		Ok(())
3249	}
3250}
3251
3252/// The info passed between the validate and prepare steps for the `CheckFreeExtrinsicUse` extension.
3253#[derive(RuntimeDebugNoBound)]
3254pub enum Val {
3255	/// Valid transaction, no weight refund.
3256	Valid,
3257	/// Weight refund for the transaction.
3258	Refund(Weight),
3259}
3260
3261/// The info passed between the prepare and post-dispatch steps for the `CheckFreeExtrinsicUse` extension.
3262#[derive(RuntimeDebugNoBound)]
3263pub enum Pre {
3264	/// Valid transaction, no weight refund.
3265	Valid,
3266	/// Weight refund for the transaction.
3267	Refund(Weight),
3268}
3269
3270impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for CheckFreeExtrinsicUse<T>
3271where
3272	T::RuntimeCall: Dispatchable<Info = DispatchInfo> + IsSubType<Call<T>>,
3273	<T as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + Clone,
3274{
3275	const IDENTIFIER: &'static str = "CheckFreeExtrinsicUse";
3276	type Implicit = ();
3277	type Val = Val;
3278	type Pre = Pre;
3279
3280	fn weight(&self, call: &T::RuntimeCall) -> Weight {
3281		match call.is_sub_type() {
3282			Some(Call::revoke_delegation_by_provider { .. }) =>
3283				T::WeightInfo::check_free_extrinsic_use_revoke_delegation_by_provider(),
3284			Some(Call::revoke_delegation_by_delegator { .. }) =>
3285				T::WeightInfo::check_free_extrinsic_use_revoke_delegation_by_delegator(),
3286			Some(Call::delete_msa_public_key { .. }) =>
3287				T::WeightInfo::check_free_extrinsic_use_delete_msa_public_key(),
3288			Some(Call::retire_msa { .. }) => T::WeightInfo::check_free_extrinsic_use_retire_msa(),
3289			Some(Call::withdraw_tokens { .. }) =>
3290				T::WeightInfo::check_free_extrinsic_use_withdraw_tokens(),
3291			_ => Weight::zero(),
3292		}
3293	}
3294
3295	fn validate(
3296		&self,
3297		origin: <T as frame_system::Config>::RuntimeOrigin,
3298		call: &T::RuntimeCall,
3299		_info: &DispatchInfoOf<T::RuntimeCall>,
3300		_len: usize,
3301		_self_implicit: Self::Implicit,
3302		_inherited_implication: &impl Encode,
3303		_source: TransactionSource,
3304	) -> ValidateResult<Self::Val, T::RuntimeCall> {
3305		let weight = self.weight(call);
3306		let Some(who) = origin.as_system_origin_signer() else {
3307			return Ok((ValidTransaction::default(), Val::Refund(weight), origin));
3308		};
3309		let validity = match call.is_sub_type() {
3310			Some(Call::revoke_delegation_by_provider { delegator, .. }) =>
3311				Self::validate_delegation_by_provider(who, delegator),
3312			Some(Call::revoke_delegation_by_delegator { provider_msa_id, .. }) =>
3313				Self::validate_delegation_by_delegator(who, provider_msa_id),
3314			Some(Call::delete_msa_public_key { public_key_to_delete, .. }) =>
3315				Self::validate_key_delete(who, public_key_to_delete),
3316			Some(Call::retire_msa { .. }) => Self::ensure_msa_can_retire(who),
3317			Some(Call::withdraw_tokens {
3318				msa_owner_public_key,
3319				msa_owner_proof,
3320				authorization_payload,
3321			}) => Self::validate_msa_token_withdrawal(
3322				who,
3323				msa_owner_public_key,
3324				msa_owner_proof,
3325				authorization_payload,
3326			),
3327			_ => Ok(Default::default()),
3328		};
3329		validity.map(|v| (v, Val::Valid, origin))
3330	}
3331
3332	fn prepare(
3333		self,
3334		val: Self::Val,
3335		_origin: &<T as frame_system::Config>::RuntimeOrigin,
3336		_call: &T::RuntimeCall,
3337		_info: &DispatchInfoOf<T::RuntimeCall>,
3338		_len: usize,
3339	) -> Result<Self::Pre, TransactionValidityError> {
3340		match val {
3341			Val::Valid => Ok(Pre::Valid),
3342			Val::Refund(w) => Ok(Pre::Refund(w)),
3343		}
3344	}
3345
3346	fn post_dispatch_details(
3347		pre: Self::Pre,
3348		_info: &DispatchInfo,
3349		_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
3350		_len: usize,
3351		_result: &sp_runtime::DispatchResult,
3352	) -> Result<Weight, TransactionValidityError> {
3353		match pre {
3354			Pre::Valid => Ok(Weight::zero()),
3355			Pre::Refund(w) => Ok(w),
3356		}
3357	}
3358}