Skip to content

Commit 87b68d7

Browse files
committed
perf(keyring-controller): add withReadonlyKeyring action
1 parent 533502d commit 87b68d7

File tree

2 files changed

+135
-37
lines changed

2 files changed

+135
-37
lines changed

packages/keyring-controller/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `withReadonlyKeyring` action ([#5727](https://github.com/MetaMask/core/pull/5727))
13+
1014
## [21.0.3]
1115

1216
### Changed

packages/keyring-controller/src/KeyringController.ts

+131-37
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ export type KeyringControllerWithKeyringAction = {
192192
handler: KeyringController['withKeyring'];
193193
};
194194

195+
export type KeyringControllerWithReadonlyKeyringAction = {
196+
type: `${typeof name}:withReadonlyKeyring`;
197+
handler: KeyringController['withReadonlyKeyring'];
198+
};
199+
195200
export type KeyringControllerStateChangeEvent = {
196201
type: `${typeof name}:stateChange`;
197202
payload: [KeyringControllerState, Patch[]];
@@ -233,7 +238,8 @@ export type KeyringControllerActions =
233238
| KeyringControllerPatchUserOperationAction
234239
| KeyringControllerSignUserOperationAction
235240
| KeyringControllerAddNewAccountAction
236-
| KeyringControllerWithKeyringAction;
241+
| KeyringControllerWithKeyringAction
242+
| KeyringControllerWithReadonlyKeyringAction;
237243

238244
export type KeyringControllerEvents =
239245
| KeyringControllerStateChangeEvent
@@ -1465,6 +1471,74 @@ export class KeyringController extends BaseController<
14651471
);
14661472
}
14671473

1474+
/**
1475+
* Execute an operation on the selected keyring.
1476+
*
1477+
* @param selector - Keyring selector object.
1478+
* @param operation - Function to execute with the selected keyring.
1479+
* @param options - Additional options.
1480+
* @returns Promise resolving to the result of the function execution.
1481+
* @template SelectedKeyring - The type of the selected keyring.
1482+
* @template CallbackResult - The type of the value resolved by the callback function.
1483+
*/
1484+
async #executeWithKeyring<
1485+
SelectedKeyring extends EthKeyring = EthKeyring,
1486+
CallbackResult = void,
1487+
>(
1488+
selector: KeyringSelector,
1489+
operation: ({
1490+
keyring,
1491+
metadata,
1492+
}: {
1493+
keyring: SelectedKeyring;
1494+
metadata: KeyringMetadata;
1495+
}) => Promise<CallbackResult>,
1496+
1497+
options:
1498+
| { createIfMissing?: false }
1499+
| { createIfMissing: true; createWithData?: unknown },
1500+
): Promise<CallbackResult> {
1501+
let keyring: SelectedKeyring | undefined;
1502+
1503+
if ('address' in selector) {
1504+
keyring = (await this.getKeyringForAccount(selector.address)) as
1505+
| SelectedKeyring
1506+
| undefined;
1507+
} else if ('type' in selector) {
1508+
keyring = this.getKeyringsByType(selector.type)[selector.index || 0] as
1509+
| SelectedKeyring
1510+
| undefined;
1511+
1512+
if (!keyring && options.createIfMissing) {
1513+
keyring = (await this.#newKeyring(
1514+
selector.type,
1515+
options.createWithData,
1516+
)) as SelectedKeyring;
1517+
}
1518+
} else if ('id' in selector) {
1519+
keyring = this.#getKeyringById(selector.id) as SelectedKeyring;
1520+
}
1521+
1522+
if (!keyring) {
1523+
throw new Error(KeyringControllerError.KeyringNotFound);
1524+
}
1525+
1526+
const result = await operation({
1527+
keyring,
1528+
metadata: this.#getKeyringMetadata(keyring),
1529+
});
1530+
1531+
if (Object.is(result, keyring)) {
1532+
// Access to a keyring instance outside of controller safeguards
1533+
// should be discouraged, as it can lead to unexpected behavior.
1534+
// This error is thrown to prevent consumers using `withKeyring`
1535+
// as a way to get a reference to a keyring instance.
1536+
throw new Error(KeyringControllerError.UnsafeDirectKeyringAccess);
1537+
}
1538+
1539+
return result;
1540+
}
1541+
14681542
/**
14691543
* Select a keyring and execute the given operation with
14701544
* the selected keyring, as a mutually exclusive atomic
@@ -1552,45 +1626,60 @@ export class KeyringController extends BaseController<
15521626
this.#assertIsUnlocked();
15531627

15541628
return this.#persistOrRollback(async () => {
1555-
let keyring: SelectedKeyring | undefined;
1556-
1557-
if ('address' in selector) {
1558-
keyring = (await this.getKeyringForAccount(selector.address)) as
1559-
| SelectedKeyring
1560-
| undefined;
1561-
} else if ('type' in selector) {
1562-
keyring = this.getKeyringsByType(selector.type)[selector.index || 0] as
1563-
| SelectedKeyring
1564-
| undefined;
1565-
1566-
if (!keyring && options.createIfMissing) {
1567-
keyring = (await this.#newKeyring(
1568-
selector.type,
1569-
options.createWithData,
1570-
)) as SelectedKeyring;
1571-
}
1572-
} else if ('id' in selector) {
1573-
keyring = this.#getKeyringById(selector.id) as SelectedKeyring;
1574-
}
1575-
1576-
if (!keyring) {
1577-
throw new Error(KeyringControllerError.KeyringNotFound);
1578-
}
1629+
return await this.#executeWithKeyring(selector, operation, options);
1630+
});
1631+
}
15791632

1580-
const result = await operation({
1581-
keyring,
1582-
metadata: this.#getKeyringMetadata(keyring),
1583-
});
1633+
/**
1634+
* Select a keyring and execute the given operation with
1635+
* the selected keyring, as a mutually exclusive atomic
1636+
* operation.
1637+
*
1638+
* The method won't persists changes at the end of the
1639+
* function execution.
1640+
*
1641+
* @param selector - Keyring selector object.
1642+
* @param operation - Function to execute with the selected keyring.
1643+
* @returns Promise resolving to the result of the function execution.
1644+
* @template SelectedKeyring - The type of the selected keyring.
1645+
* @template CallbackResult - The type of the value resolved by the callback function.
1646+
*/
1647+
async withReadonlyKeyring<
1648+
SelectedKeyring extends EthKeyring = EthKeyring,
1649+
CallbackResult = void,
1650+
>(
1651+
selector: KeyringSelector,
1652+
operation: ({
1653+
keyring,
1654+
metadata,
1655+
}: {
1656+
keyring: Readonly<SelectedKeyring>;
1657+
metadata: KeyringMetadata;
1658+
}) => Promise<CallbackResult>,
1659+
): Promise<CallbackResult>;
15841660

1585-
if (Object.is(result, keyring)) {
1586-
// Access to a keyring instance outside of controller safeguards
1587-
// should be discouraged, as it can lead to unexpected behavior.
1588-
// This error is thrown to prevent consumers using `withKeyring`
1589-
// as a way to get a reference to a keyring instance.
1590-
throw new Error(KeyringControllerError.UnsafeDirectKeyringAccess);
1591-
}
1661+
async withReadonlyKeyring<
1662+
SelectedKeyring extends EthKeyring = EthKeyring,
1663+
CallbackResult = void,
1664+
>(
1665+
selector: KeyringSelector,
1666+
operation: ({
1667+
keyring,
1668+
metadata,
1669+
}: {
1670+
keyring: Readonly<SelectedKeyring>;
1671+
metadata: KeyringMetadata;
1672+
}) => Promise<CallbackResult>,
1673+
options:
1674+
| { createIfMissing?: false }
1675+
| { createIfMissing: true; createWithData?: unknown } = {
1676+
createIfMissing: false,
1677+
},
1678+
): Promise<CallbackResult> {
1679+
this.#assertIsUnlocked();
15921680

1593-
return result;
1681+
return this.#withControllerLock(async () => {
1682+
return await this.#executeWithKeyring(selector, operation, options);
15941683
});
15951684
}
15961685

@@ -1913,6 +2002,11 @@ export class KeyringController extends BaseController<
19132002
`${name}:withKeyring`,
19142003
this.withKeyring.bind(this),
19152004
);
2005+
2006+
this.messagingSystem.registerActionHandler(
2007+
`${name}:withReadonlyKeyring`,
2008+
this.withReadonlyKeyring.bind(this),
2009+
);
19162010
}
19172011

19182012
/**

0 commit comments

Comments
 (0)