pallet_msa/
lib.rs

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