-
-
Notifications
You must be signed in to change notification settings - Fork 249
feat(multichain-account-service): basic functionality management #6332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
91ae80e
2f89753
2235983
bc04a0a
6828cad
220116e
2ed1305
407fb56
76640ca
ee3e69e
56a7ba7
e73bc0c
81661f2
95561be
3cb95a8
c9c0a56
7dfc314
d79fc0b
fcfacab
c8bb2d2
4538dc6
e255ea1
372a6ca
3eefed3
6a91767
544906a
41f4564
68747e9
970969c
ae6e3e0
4de9cbb
173e760
24dc1eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,14 +5,18 @@ import { | |
import type { | ||
MultichainAccountWalletId, | ||
Bip44Account, | ||
AccountProvider, | ||
} from '@metamask/account-api'; | ||
import type { AccountProvider } from '@metamask/account-api'; | ||
import type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api'; | ||
import { KeyringTypes } from '@metamask/keyring-controller'; | ||
|
||
import type { MultichainAccountGroup } from './MultichainAccountGroup'; | ||
import { MultichainAccountWallet } from './MultichainAccountWallet'; | ||
import { EvmAccountProvider } from './providers/EvmAccountProvider'; | ||
import { | ||
ProviderWrapper, | ||
isProviderWrapper, | ||
} from './providers/ProviderWrapper'; | ||
import { SolAccountProvider } from './providers/SolAccountProvider'; | ||
import type { MultichainAccountServiceMessenger } from './types'; | ||
|
||
|
@@ -40,7 +44,10 @@ type AccountContext<Account extends Bip44Account<KeyringAccount>> = { | |
export class MultichainAccountService { | ||
readonly #messenger: MultichainAccountServiceMessenger; | ||
|
||
readonly #providers: AccountProvider<Bip44Account<KeyringAccount>>[]; | ||
readonly #providers: ( | ||
| AccountProvider<Bip44Account<KeyringAccount>> | ||
| ProviderWrapper | ||
)[]; | ||
|
||
readonly #wallets: Map< | ||
MultichainAccountWalletId, | ||
|
@@ -73,10 +80,11 @@ export class MultichainAccountService { | |
this.#messenger = messenger; | ||
this.#wallets = new Map(); | ||
this.#accountIdToContext = new Map(); | ||
|
||
// TODO: Rely on keyring capabilities once the keyring API is used by all keyrings. | ||
this.#providers = [ | ||
new EvmAccountProvider(this.#messenger), | ||
new SolAccountProvider(this.#messenger), | ||
new ProviderWrapper(new SolAccountProvider(this.#messenger)), | ||
// Custom account providers that can be provided by the MetaMask client. | ||
...providers, | ||
]; | ||
|
@@ -105,6 +113,10 @@ export class MultichainAccountService { | |
'MultichainAccountService:createMultichainAccountGroup', | ||
(...args) => this.createMultichainAccountGroup(...args), | ||
); | ||
this.#messenger.registerActionHandler( | ||
'MultichainAccountService:setBasicFunctionality', | ||
(...args) => this.setBasicFunctionality(...args), | ||
); | ||
this.#messenger.registerActionHandler( | ||
'MultichainAccountService:alignWallets', | ||
(...args) => this.alignWallets(...args), | ||
|
@@ -359,6 +371,34 @@ export class MultichainAccountService { | |
); | ||
} | ||
|
||
/** | ||
* Set basic functionality state and trigger alignment if enabled. | ||
* When basic functionality is disabled, snap-based providers are disabled. | ||
* When enabled, all snap providers are enabled and wallet alignment is triggered. | ||
* EVM providers are never disabled as they're required for basic wallet functionality. | ||
* | ||
* @param enabled - Whether basic functionality is enabled. | ||
*/ | ||
async setBasicFunctionality(enabled: boolean): Promise<void> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IDK if we should have this as part of the public API 🤔 My initial idea was to have the "account provider wrapper" to "react" to some events (or maybe a runtime callback) and adapt each of their methods to return all accounts or no accounts (based on the "basic functionality flag"). Though, I haven't checked of this was done on both clients, so maybe it's not that easy to do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is something to discuss @ccharly . It's an architectural decision, and I chose the simplest path (multichain account service was already available to both places where basicFunctionality is toggled in extension and mobile). Are you suggesting we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, let's be pragmatic here and keep your implementation for now. It's simple enough and does get the job done, so that's ok! 👍 We can re-visit that later once we have a bit more time. |
||
console.log( | ||
`MultichainAccountService: Setting basic functionality ${enabled ? 'enabled' : 'disabled'}`, | ||
); | ||
fabiobozzo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
// Loop through providers and disable only wrapped ones when basic functionality is disabled | ||
for (const provider of this.#providers) { | ||
if (isProviderWrapper(provider)) { | ||
provider.setDisabled(!enabled); | ||
} | ||
// Regular providers (like EVM) are never disabled for basic functionality | ||
} | ||
|
||
// Trigger alignment only when basic functionality is enabled | ||
if (enabled) { | ||
console.log('Triggered wallet alignment...'); | ||
await this.alignWallets(); | ||
} | ||
} | ||
|
||
/** | ||
* Align all multichain account wallets. | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import type { AccountProvider, Bip44Account } from '@metamask/account-api'; | ||
import type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api'; | ||
|
||
/** | ||
* A simple wrapper that adds disable functionality to any AccountProvider. | ||
* When disabled, the provider will not create new accounts and return empty results. | ||
*/ | ||
export class ProviderWrapper | ||
gantunesr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ccharly marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
implements AccountProvider<Bip44Account<KeyringAccount>> | ||
fabiobozzo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
{ | ||
private isDisabled: boolean = false; | ||
|
||
private readonly provider: AccountProvider<Bip44Account<KeyringAccount>>; | ||
|
||
constructor(provider: AccountProvider<Bip44Account<KeyringAccount>>) { | ||
this.provider = provider; | ||
} | ||
|
||
/** | ||
* Set the disabled state for this provider. | ||
* | ||
* @param disabled - Whether the provider should be disabled. | ||
*/ | ||
setDisabled(disabled: boolean): void { | ||
fabiobozzo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
this.isDisabled = disabled; | ||
console.log( | ||
`Provider ${this.provider.constructor.name} ${disabled ? 'disabled' : 'enabled'}`, | ||
); | ||
} | ||
|
||
/** | ||
* Get accounts, returns empty array when disabled. | ||
* | ||
* @returns Array of accounts, or empty array if disabled. | ||
*/ | ||
getAccounts(): Bip44Account<KeyringAccount>[] { | ||
if (this.isDisabled) { | ||
return []; | ||
fabiobozzo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
return this.provider.getAccounts(); | ||
} | ||
|
||
/** | ||
* Get account by ID, throws error when disabled. | ||
* | ||
* @param id - The account ID to retrieve. | ||
* @returns The account with the specified ID, or undefined if not found. | ||
*/ | ||
getAccount( | ||
id: Bip44Account<KeyringAccount>['id'], | ||
): Bip44Account<KeyringAccount> | undefined { | ||
if (this.isDisabled) { | ||
throw new Error(`Provider ${this.provider.constructor.name} is disabled`); | ||
} | ||
return this.provider.getAccount(id); | ||
} | ||
fabiobozzo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Create accounts, returns empty array when disabled. | ||
* | ||
* @param options - Account creation options. | ||
* @param options.entropySource - The entropy source to use. | ||
* @param options.groupIndex - The group index to use. | ||
* @returns Promise resolving to created accounts, or empty array if disabled. | ||
*/ | ||
async createAccounts(options: { | ||
entropySource: EntropySourceId; | ||
groupIndex: number; | ||
}): Promise<Bip44Account<KeyringAccount>[]> { | ||
if (this.isDisabled) { | ||
console.log( | ||
`Provider ${this.provider.constructor.name} is disabled - skipping account creation`, | ||
); | ||
return []; | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
fabiobozzo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
return this.provider.createAccounts(options); | ||
} | ||
|
||
/** | ||
* Discover and create accounts, returns empty array when disabled. | ||
* | ||
* @param options - Account discovery options. | ||
* @param options.entropySource - The entropy source to use. | ||
* @param options.groupIndex - The group index to use. | ||
* @returns Promise resolving to discovered accounts, or empty array if disabled. | ||
*/ | ||
async discoverAndCreateAccounts(options: { | ||
entropySource: EntropySourceId; | ||
groupIndex: number; | ||
}): Promise<Bip44Account<KeyringAccount>[]> { | ||
if (this.isDisabled) { | ||
console.log( | ||
`Provider ${this.provider.constructor.name} is disabled - skipping account discovery`, | ||
); | ||
return []; | ||
fabiobozzo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
return this.provider.discoverAndCreateAccounts(options); | ||
} | ||
|
||
/** | ||
* Check if account is compatible. | ||
* | ||
* @param account - The account to check. | ||
* @returns True if the account is compatible. | ||
*/ | ||
isAccountCompatible(account: Bip44Account<KeyringAccount>): boolean { | ||
// Check if the provider has the method (from BaseAccountProvider) | ||
if ( | ||
'isAccountCompatible' in this.provider && | ||
typeof this.provider.isAccountCompatible === 'function' | ||
) { | ||
ccharly marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
return this.provider.isAccountCompatible(account); | ||
} | ||
fabiobozzo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
// Fallback: return true if the method doesn't exist | ||
return true; | ||
} | ||
} | ||
|
||
/** | ||
* Simple type guard to check if a provider is wrapped. | ||
* | ||
* @param provider - The provider to check. | ||
* @returns True if the provider is a ProviderWrapper. | ||
*/ | ||
export function isProviderWrapper( | ||
provider: AccountProvider<Bip44Account<KeyringAccount>> | ProviderWrapper, | ||
): provider is ProviderWrapper { | ||
return provider instanceof ProviderWrapper; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.