Skip to content

Commit 1ff4115

Browse files
committed
refactor: re-organize files
1 parent 64b83dd commit 1ff4115

File tree

7 files changed

+329
-340
lines changed

7 files changed

+329
-340
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import type { KeyringAccount } from '@metamask/keyring-api';
2+
import { isScopeEqualToAny, type AccountId } from '@metamask/keyring-utils';
3+
import type { CaipChainId } from '@metamask/utils';
4+
5+
import type { MultichainAccountProvider } from './provider';
6+
import type {
7+
MultichainAccountWallet,
8+
MultichainAccountWalletId,
9+
} from './wallet';
10+
import type { AccountGroup, AccountGroupId } from '../group';
11+
import { AccountWalletCategory } from '../wallet';
12+
13+
const MULTICHAIN_ACCOUNT_GROUP_INDEX_REGEX = new RegExp(
14+
`^${AccountWalletCategory.Entropy}:.*/(?<groupIndex>\\d+)$`,
15+
'u',
16+
);
17+
18+
export type AccountType = string;
19+
20+
export type AccountMethod = string;
21+
22+
export type MultichainAccountSelector = {
23+
id?: AccountId;
24+
address?: string;
25+
type?: AccountType;
26+
methods?: AccountMethod[];
27+
scopes?: CaipChainId[];
28+
};
29+
30+
export type MultichainAccountId = `${MultichainAccountWalletId}/${number}`; // Use number for the account group index.
31+
32+
export type MultichainAccount<Account extends KeyringAccount> =
33+
AccountGroup<Account> & {
34+
get id(): MultichainAccountId;
35+
36+
get wallet(): MultichainAccountWallet<Account>;
37+
38+
get index(): number;
39+
40+
/**
41+
* Gets the "blockchain" accounts for this multichain account.
42+
*
43+
* @param id - Account ID.
44+
* @returns The "blockchain" accounts.
45+
*/
46+
getAccounts(): Account[];
47+
48+
/**
49+
* Gets the "blockchain" account for a given account ID.
50+
*
51+
* @param id - Account ID.
52+
* @returns The "blockchain" account or undefined if not found.
53+
*/
54+
getAccount(id: AccountId): Account | undefined;
55+
56+
/**
57+
* Query a "blockchain" account matching the selector.
58+
*
59+
* @param selector - Query selector.
60+
* @returns The "blockchain" account matching the selector or undefined if not matching.
61+
* @throws If multiple accounts match the selector.
62+
*/
63+
get(selector: MultichainAccountSelector): Account | undefined;
64+
65+
/**
66+
* Query "blockchain" accounts matching the selector.
67+
*
68+
* @param selector - Query selector.
69+
* @returns The "blockchain" accounts matching the selector.
70+
*/
71+
select(selector: MultichainAccountSelector): Account[];
72+
};
73+
74+
export class MultichainAccountAdapter<Account extends KeyringAccount>
75+
implements MultichainAccount<Account>
76+
{
77+
readonly #id: MultichainAccountId;
78+
79+
readonly #wallet: MultichainAccountWallet<Account>;
80+
81+
readonly #index: number;
82+
83+
readonly #providers: MultichainAccountProvider<Account>[];
84+
85+
readonly #providersByAccountId: Map<
86+
AccountId,
87+
MultichainAccountProvider<Account>
88+
>;
89+
90+
readonly #accounts: Map<MultichainAccountProvider<Account>, AccountId[]>;
91+
92+
constructor({
93+
groupIndex,
94+
wallet,
95+
providers,
96+
}: {
97+
groupIndex: number;
98+
wallet: MultichainAccountWallet<Account>;
99+
providers: MultichainAccountProvider<Account>[];
100+
}) {
101+
this.#id = toMultichainAccountId(wallet.id, groupIndex);
102+
this.#index = groupIndex;
103+
this.#wallet = wallet;
104+
this.#providers = providers;
105+
this.#accounts = new Map();
106+
this.#providersByAccountId = new Map();
107+
108+
for (const provider of this.#providers) {
109+
const accounts = provider.getAccounts({
110+
entropySource: this.#wallet.entropySource,
111+
groupIndex: this.#index,
112+
});
113+
114+
this.#accounts.set(provider, accounts);
115+
for (const id of accounts) {
116+
this.#providersByAccountId.set(id, provider);
117+
}
118+
}
119+
}
120+
121+
get id(): MultichainAccountId {
122+
return this.#id;
123+
}
124+
125+
get wallet(): MultichainAccountWallet<Account> {
126+
return this.#wallet;
127+
}
128+
129+
get index(): number {
130+
return this.#index;
131+
}
132+
133+
hasAccounts(): boolean {
134+
// Use this map, cause if there's no accounts, then this map will also
135+
// be empty.
136+
return this.#providersByAccountId.size > 0;
137+
}
138+
139+
getAccounts(): Account[] {
140+
let allAccounts: Account[] = [];
141+
142+
for (const [provider, accounts] of this.#accounts.entries()) {
143+
allAccounts = allAccounts.concat(
144+
accounts.map((id) => provider.getAccount(id)),
145+
);
146+
}
147+
148+
return allAccounts;
149+
}
150+
151+
getAccount(id: AccountId): Account | undefined {
152+
const provider = this.#providersByAccountId.get(id);
153+
154+
return provider?.getAccount(id);
155+
}
156+
157+
get(selector: MultichainAccountSelector): Account | undefined {
158+
const accounts = this.select(selector);
159+
160+
if (accounts.length > 1) {
161+
throw new Error(
162+
`Too many account candidates, expected 1, got: ${accounts.length}`,
163+
);
164+
}
165+
166+
if (accounts.length === 0) {
167+
return undefined;
168+
}
169+
170+
return accounts[0]; // This is safe, see checks above.
171+
}
172+
173+
select(selector: MultichainAccountSelector): Account[] {
174+
return this.getAccounts().filter((account) => {
175+
let selected = true;
176+
177+
if (selector.id) {
178+
selected &&= account.id === selector.id;
179+
}
180+
if (selector.address) {
181+
selected &&= account.address === selector.address;
182+
}
183+
if (selector.type) {
184+
selected &&= account.type === selector.type;
185+
}
186+
if (selector.methods !== undefined) {
187+
selected &&= selector.methods.some((method) =>
188+
account.methods.includes(method),
189+
);
190+
}
191+
if (selector.scopes !== undefined) {
192+
selected &&= selector.scopes.some((scope) => {
193+
return (
194+
// This will cover specific EVM EOA scopes as well.
195+
isScopeEqualToAny(scope, account.scopes)
196+
);
197+
});
198+
}
199+
200+
return selected;
201+
});
202+
}
203+
}
204+
205+
/**
206+
* Gets the multichain account ID from its multichain account wallet ID and its index.
207+
*
208+
* @param walletId - Multichain account wallet ID.
209+
* @param groupIndex - Index of that multichain account.
210+
* @returns The multichain account ID.
211+
*/
212+
export function toMultichainAccountId(
213+
walletId: MultichainAccountWalletId,
214+
groupIndex: number,
215+
): MultichainAccountId {
216+
return `${walletId}/${groupIndex}`;
217+
}
218+
219+
/**
220+
* Gets the multichain account index from an account group ID.
221+
*
222+
* @param groupId - Account group ID.
223+
* @returns The multichain account index if extractable, undefined otherwise.
224+
*/
225+
export function getGroupIndexFromAccountGroupId(
226+
groupId: AccountGroupId,
227+
): number | undefined {
228+
const matched = groupId.match(MULTICHAIN_ACCOUNT_GROUP_INDEX_REGEX);
229+
if (matched) {
230+
if (matched.groups?.groupIndex !== undefined) {
231+
return Number(matched.groups.groupIndex);
232+
}
233+
}
234+
235+
// Unable to extract group index.
236+
return undefined;
237+
}

packages/account-api/src/api/multichain/id.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export * from './id';
2-
export type * from './types';
3-
export type * from './providers';
4-
export type * from './adapters';
1+
export * from './account';
2+
export * from './wallet';
3+
export type * from './provider';

packages/account-api/src/api/multichain/providers.ts renamed to packages/account-api/src/api/multichain/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api';
22
import type { AccountId } from '@metamask/keyring-utils';
33

4-
export type AccountProvider<Account extends KeyringAccount> = {
4+
export type MultichainAccountProvider<Account extends KeyringAccount> = {
55
getAccount: (id: AccountId) => Account; // Assuming getting an account from the provider can never fail.
66

77
getAccounts: (opts: {

0 commit comments

Comments
 (0)