Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/keyring-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add support for custom capabilities and entropy types in `KeyringV2` ([#415](https://github.com/MetaMask/accounts/pull/415))
- Add `custom` capability to `KeyringCapabilities` for keyrings with non-standard `createAccounts` method.
- Add `KeyringAccountEntropyTypeOption.Custom` for custom/opaque entropy sources.
- Add `AccountCreationType.Custom` and `CreateAccountCustomOptions` for custom account creation flows.
- Add `EthKeyringWrapper` abstract class for Ethereum-based `KeyringV2` implementations ([#404](https://github.com/MetaMask/accounts/pull/404))
- Provides common Ethereum signing method routing (`submitRequest`) for all Ethereum-based keyrings.
- Add `KeyringWrapper` base class to adapt legacy keyrings to `KeyringV2` ([#398](https://github.com/MetaMask/accounts/pull/398)), ([#410](https://github.com/MetaMask/accounts/pull/410))
Expand Down
28 changes: 22 additions & 6 deletions packages/keyring-api/src/api/account-options.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { assert } from '@metamask/superstruct';

import { KeyringAccountOptionsStruct } from './account-options';
import {
KeyringAccountEntropyTypeOption,
KeyringAccountOptionsStruct,
} from './account-options';

describe('api', () => {
describe('KeyringAccountOptionsStruct', () => {
const baseEntropyMnemonicOptions = {
type: 'mnemonic',
type: KeyringAccountEntropyTypeOption.Mnemonic,
id: '01K0BX6VDR5DPDPGGNA8PZVBVB',
derivationPath: "m/44'/60'/0'/0/0",
};
Expand All @@ -14,9 +17,22 @@ describe('api', () => {
{},
{ exportable: true },
{ exportable: false },
{ entropy: { type: 'private-key' } },
{ entropy: { type: 'private-key' }, exportable: true },
{ entropy: { type: 'private-key' }, exportable: false },
{ entropy: { type: KeyringAccountEntropyTypeOption.PrivateKey } },
{
entropy: { type: KeyringAccountEntropyTypeOption.PrivateKey },
exportable: true,
},
{
entropy: { type: KeyringAccountEntropyTypeOption.PrivateKey },
exportable: false,
},
{
entropy: { type: KeyringAccountEntropyTypeOption.Custom },
},
{
entropy: { type: KeyringAccountEntropyTypeOption.Custom },
exportable: true,
},
{
entropy: {
...baseEntropyMnemonicOptions,
Expand Down Expand Up @@ -55,7 +71,7 @@ describe('api', () => {
it('throws if legacy options partially matches options.entropy.type', () => {
const options = {
entropy: {
type: 'mnemonic',
type: KeyringAccountEntropyTypeOption.Mnemonic,
// Nothing else, like if it was legacy.
},
};
Expand Down
59 changes: 53 additions & 6 deletions packages/keyring-api/src/api/account-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export enum KeyringAccountEntropyTypeOption {
* Indicates that the account was imported from a private key.
*/
PrivateKey = 'private-key',

/**
* Indicates that the account was created with custom, keyring-specific entropy.
* This is an opaque type where the entropy source is managed internally by the keyring.
*/
Custom = 'custom',
}

/**
Expand Down Expand Up @@ -78,15 +84,46 @@ export type KeyringAccountEntropyPrivateKeyOptions = Infer<
typeof KeyringAccountEntropyPrivateKeyOptionsStruct
>;

/**
* Keyring account options struct for custom entropy.
*
* This is an opaque type where the entropy source is managed internally by the keyring.
* It behaves similarly to a private key import but allows keyrings to define their own
* entropy management strategy.
*/
export const KeyringAccountEntropyCustomOptionsStruct = object({
/**
* Indicates that the account was created with custom, keyring-specific entropy.
*/
type: literal(`${KeyringAccountEntropyTypeOption.Custom}`),
});

/**
* Keyring account options for custom entropy {@link KeyringAccountEntropyCustomOptionsStruct}.
*/
export type KeyringAccountEntropyCustomOptions = Infer<
typeof KeyringAccountEntropyCustomOptionsStruct
>;

/**
* Keyring account entropy options struct.
*/
export const KeyringAccountEntropyOptionsStruct = selectiveUnion(
(value: any) => {
return isPlainObject(value) &&
value.type === KeyringAccountEntropyTypeOption.PrivateKey
? KeyringAccountEntropyPrivateKeyOptionsStruct
: KeyringAccountEntropyMnemonicOptionsStruct;
if (!isPlainObject(value)) {
return KeyringAccountEntropyMnemonicOptionsStruct;
}

switch (value.type) {
case KeyringAccountEntropyTypeOption.PrivateKey:
return KeyringAccountEntropyPrivateKeyOptionsStruct;
case KeyringAccountEntropyTypeOption.Custom:
return KeyringAccountEntropyCustomOptionsStruct;
case KeyringAccountEntropyTypeOption.Mnemonic:
return KeyringAccountEntropyMnemonicOptionsStruct;
default:
return KeyringAccountEntropyMnemonicOptionsStruct;
}
},
);

Expand All @@ -100,8 +137,9 @@ export type KeyringAccountEntropyOptions = Infer<
/**
* Keyring options struct. This represents various options for a Keyring account object.
*
* See {@link KeyringAccountEntropyMnemonicOptionsStruct} and
* {@link KeyringAccountEntropyPrivateKeyOptionsStruct}.
* See {@link KeyringAccountEntropyMnemonicOptionsStruct},
* {@link KeyringAccountEntropyPrivateKeyOptionsStruct}, and
* {@link KeyringAccountEntropyCustomOptionsStruct}.
*
* @example
* ```ts
Expand All @@ -128,6 +166,15 @@ export type KeyringAccountEntropyOptions = Infer<
* @example
* ```ts
* {
* entropy: {
* type: 'custom',
* },
* }
* ```
*
* @example
* ```ts
* {
* some: {
* untyped: 'options',
* something: true,
Expand Down
26 changes: 26 additions & 0 deletions packages/keyring-api/src/api/v2/create-account/custom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { literal, type, type Infer } from '@metamask/superstruct';

/**
* Struct for {@link CreateAccountCustomOptions}.
*/
export const CreateAccountCustomOptionsStruct = type({
/**
* The type of the options.
*/
type: literal('custom'),
});

/**
* Options for creating an account using a custom, keyring-specific method.
*
* This is an opaque type that allows keyrings with non-standard account
* creation flows to define their own options. Keyrings using this type
* should declare `custom.createAccounts: true` in their capabilities.
*
* The actual options accepted by the keyring are implementation-specific
* and not validated by this struct beyond the `type` field. Adaptors should
* handle any additional options as needed and add type intersections as necessary.
*/
export type CreateAccountCustomOptions = Infer<
typeof CreateAccountCustomOptionsStruct
>;
12 changes: 12 additions & 0 deletions packages/keyring-api/src/api/v2/create-account/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
CreateAccountBip44DeriveIndexOptionsStruct,
CreateAccountBip44DerivePathOptionsStruct,
} from './bip44';
import { CreateAccountCustomOptionsStruct } from './custom';
import { CreateAccountPrivateKeyOptionsStruct } from './private-key';

export * from './bip44';
export * from './custom';
export * from './private-key';

/**
Expand Down Expand Up @@ -42,6 +44,14 @@ export enum AccountCreationType {
* Represents an account imported from a private key.
*/
PrivateKeyImport = 'private-key:import',

/**
* Represents an account created using a custom, keyring-specific method.
*
* This is used by keyrings that have non-standard account creation flows
* and declare `custom.createAccounts: true` in their capabilities.
*/
Custom = 'custom',
}

/**
Expand All @@ -58,6 +68,8 @@ export const CreateAccountOptionsStruct = selectiveUnion((value: any) => {
return CreateAccountBip44DiscoverOptionsStruct;
case AccountCreationType.PrivateKeyImport:
return CreateAccountPrivateKeyOptionsStruct;
case AccountCreationType.Custom:
return CreateAccountCustomOptionsStruct;
default:
// Return first struct as fallback - validation will fail with proper error indicating the type mismatch
return CreateAccountBip44DerivePathOptionsStruct;
Expand Down
15 changes: 15 additions & 0 deletions packages/keyring-api/src/api/v2/keyring-capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
exactOptional,
nonempty,
object,
partial,
type Infer,
} from '@metamask/superstruct';

Expand Down Expand Up @@ -55,6 +56,20 @@ export const KeyringCapabilitiesStruct = object({
exportFormats: exactOptional(array(ExportPrivateKeyFormatStruct)),
}),
),
/**
* Indicates which KeyringV2 methods accept non-standard options.
*
* When a method is set to `true`, it signals that the keyring implementation
* accepts custom options for that method, different from the standard API.
* This is a workaround for keyrings with very specific requirements.
*/
custom: exactOptional(
partial(
object({
createAccounts: boolean(),
}),
),
),
});

/**
Expand Down
26 changes: 26 additions & 0 deletions packages/keyring-api/src/api/v2/keyring.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type CreateAccountBip44DiscoverOptions,
type CreateAccountBip44DeriveIndexOptions,
type CreateAccountBip44DerivePathOptions,
type CreateAccountCustomOptions,
type CreateAccountOptions,
type CreateAccountPrivateKeyOptions,
} from './create-account';
Expand Down Expand Up @@ -33,6 +34,7 @@ expectAssignable<AccountCreationType>(AccountCreationType.Bip44DerivePath);
expectAssignable<AccountCreationType>(AccountCreationType.Bip44DeriveIndex);
expectAssignable<AccountCreationType>(AccountCreationType.Bip44Discover);
expectAssignable<AccountCreationType>(AccountCreationType.PrivateKeyImport);
expectAssignable<AccountCreationType>(AccountCreationType.Custom);

// Test AccountExportType enum
expectAssignable<AccountExportType>(AccountExportType.PrivateKey);
Expand Down Expand Up @@ -73,6 +75,21 @@ expectAssignable<KeyringCapabilities>({
},
});

expectAssignable<KeyringCapabilities>({
scopes: ['eip155:1'],
custom: {
createAccounts: true,
},
});

expectNotAssignable<KeyringCapabilities>({
scopes: ['eip155:1'],
custom: {
createAccounts: true,
exportAccount: true,
},
});

// Test CreateAccountBip44DerivePathOptions
expectAssignable<CreateAccountBip44DerivePathOptions>({
type: AccountCreationType.Bip44DerivePath,
Expand Down Expand Up @@ -114,6 +131,11 @@ expectAssignable<CreateAccountPrivateKeyOptions>({
accountType: 'bip122:p2wpkh',
});

// Test CreateAccountCustomOptions
expectAssignable<CreateAccountCustomOptions>({
type: AccountCreationType.Custom,
});

// Test CreateAccountOptions union
expectAssignable<CreateAccountOptions>({
type: AccountCreationType.Bip44DerivePath,
Expand All @@ -133,6 +155,10 @@ expectAssignable<CreateAccountOptions>({
encoding: 'hexadecimal',
});

expectAssignable<CreateAccountOptions>({
type: AccountCreationType.Custom,
});

// Test ExportAccountOptions
expectAssignable<ExportAccountOptions>({
type: AccountExportType.PrivateKey,
Expand Down
Loading