@@ -192,6 +192,11 @@ export type KeyringControllerWithKeyringAction = {
192
192
handler: KeyringController['withKeyring'];
193
193
};
194
194
195
+ export type KeyringControllerWithReadonlyKeyringAction = {
196
+ type: `${typeof name}:withReadonlyKeyring`;
197
+ handler: KeyringController['withReadonlyKeyring'];
198
+ };
199
+
195
200
export type KeyringControllerStateChangeEvent = {
196
201
type: `${typeof name}:stateChange`;
197
202
payload: [KeyringControllerState, Patch[]];
@@ -233,7 +238,8 @@ export type KeyringControllerActions =
233
238
| KeyringControllerPatchUserOperationAction
234
239
| KeyringControllerSignUserOperationAction
235
240
| KeyringControllerAddNewAccountAction
236
- | KeyringControllerWithKeyringAction;
241
+ | KeyringControllerWithKeyringAction
242
+ | KeyringControllerWithReadonlyKeyringAction;
237
243
238
244
export type KeyringControllerEvents =
239
245
| KeyringControllerStateChangeEvent
@@ -1465,6 +1471,74 @@ export class KeyringController extends BaseController<
1465
1471
);
1466
1472
}
1467
1473
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
+
1468
1542
/**
1469
1543
* Select a keyring and execute the given operation with
1470
1544
* the selected keyring, as a mutually exclusive atomic
@@ -1552,45 +1626,60 @@ export class KeyringController extends BaseController<
1552
1626
this.#assertIsUnlocked();
1553
1627
1554
1628
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
+ }
1579
1632
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>;
1584
1660
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();
1592
1680
1593
- return result;
1681
+ return this.#withControllerLock(async () => {
1682
+ return await this.#executeWithKeyring(selector, operation, options);
1594
1683
});
1595
1684
}
1596
1685
@@ -1913,6 +2002,11 @@ export class KeyringController extends BaseController<
1913
2002
`${name}:withKeyring`,
1914
2003
this.withKeyring.bind(this),
1915
2004
);
2005
+
2006
+ this.messagingSystem.registerActionHandler(
2007
+ `${name}:withReadonlyKeyring`,
2008
+ this.withReadonlyKeyring.bind(this),
2009
+ );
1916
2010
}
1917
2011
1918
2012
/**
0 commit comments