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