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