From 1835234e9923709270522464f10c4be61442667a Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Fri, 11 Sep 2026 17:12:44 +0200 Subject: [PATCH 1/2] feat: add shareable keyring account --- packages/snap-networks-utils/CHANGELOG.md | 1 + packages/snap-networks-utils/package.json | 1 + packages/snap-networks-utils/src/index.ts | 5 +++ .../keyringAccount/keyringAccount.test.ts | 42 +++++++++++++++++++ .../utils/keyringAccount/keyringAccount.ts | 39 +++++++++++++++++ yarn.lock | 1 + 6 files changed, 89 insertions(+) create mode 100644 packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.test.ts create mode 100644 packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.ts diff --git a/packages/snap-networks-utils/CHANGELOG.md b/packages/snap-networks-utils/CHANGELOG.md index 1ad15de4..d14dec9a 100644 --- a/packages/snap-networks-utils/CHANGELOG.md +++ b/packages/snap-networks-utils/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `ExtendedKeyringAccount`, `KeyringAccountExtension`, and `asStrictKeyringAccount` for snap keyring accounts that persist `entropySource`, `derivationPath`, and `index`. - Add shared snap state helpers `IStateManager`, `State`, and `InMemoryState` (Tron-style write mutex plus blob/path locking). ([#288](https://github.com/MetaMask/internal-snaps/pull/288)) - Add shared caching utilities for network snaps ([#287](https://github.com/MetaMask/internal-snaps/pull/287)) - `ICache`, `CacheEntry`, and `TimestampMilliseconds` for describing a generic cache diff --git a/packages/snap-networks-utils/package.json b/packages/snap-networks-utils/package.json index 7a70b479..cf57573d 100644 --- a/packages/snap-networks-utils/package.json +++ b/packages/snap-networks-utils/package.json @@ -72,6 +72,7 @@ }, "dependencies": { "@metamask/assets-controller": "^13.0.0", + "@metamask/keyring-api": "^24.1.0", "@metamask/remote-feature-flag-controller": "^5.0.0", "@metamask/snaps-sdk": "^12.0.1", "@metamask/superstruct": "^3.4.1", diff --git a/packages/snap-networks-utils/src/index.ts b/packages/snap-networks-utils/src/index.ts index e90003ea..574b8f11 100644 --- a/packages/snap-networks-utils/src/index.ts +++ b/packages/snap-networks-utils/src/index.ts @@ -17,6 +17,11 @@ export { } from './utils/sanitize/sanitize'; export { UrlStruct } from './utils/urlStruct/urlStruct'; export { UuidStruct } from './utils/uuidStruct/uuidStruct'; +export { asStrictKeyringAccount } from './utils/keyringAccount/keyringAccount'; +export type { + ExtendedKeyringAccount, + KeyringAccountExtension, +} from './utils/keyringAccount/keyringAccount'; export { batchesAll, batchesAllSettled, diff --git a/packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.test.ts b/packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.test.ts new file mode 100644 index 00000000..53828132 --- /dev/null +++ b/packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.test.ts @@ -0,0 +1,42 @@ +import { asStrictKeyringAccount } from './keyringAccount'; +import type { ExtendedKeyringAccount } from './keyringAccount'; + +describe('asStrictKeyringAccount', () => { + const account: ExtendedKeyringAccount = { + type: 'eip155:eoa', + id: '4b445722-6766-4f99-ade5-c2c9295f21d0', + address: 'TAddress0', + options: { + entropy: { + type: 'mnemonic', + id: 'entropy-source', + derivationPath: "m/44'/195'/0'/0/0", + groupIndex: 0, + }, + }, + methods: ['personal_sign'], + scopes: ['tron:mainnet'], + entropySource: 'entropy-source', + derivationPath: "m/44'/195'/0'/0/0", + index: 0, + }; + + it('returns only KeyringAccount fields', () => { + expect(asStrictKeyringAccount(account)).toStrictEqual({ + type: account.type, + id: account.id, + address: account.address, + options: account.options, + methods: account.methods, + scopes: account.scopes, + }); + }); + + it('does not include entropySource, derivationPath, or index', () => { + expect(asStrictKeyringAccount(account)).not.toHaveProperty('entropySource'); + expect(asStrictKeyringAccount(account)).not.toHaveProperty( + 'derivationPath', + ); + expect(asStrictKeyringAccount(account)).not.toHaveProperty('index'); + }); +}); diff --git a/packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.ts b/packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.ts new file mode 100644 index 00000000..b8e72a20 --- /dev/null +++ b/packages/snap-networks-utils/src/utils/keyringAccount/keyringAccount.ts @@ -0,0 +1,39 @@ +import type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api'; + +/** + * Keyring account fields snaps persist in addition to `@metamask/keyring-api`'s + * `KeyringAccount`. `index` is stored so an account can be restored at a + * previously used derivation index. + */ +export type KeyringAccountExtension = { + entropySource: EntropySourceId; + derivationPath: `m/${string}`; + index: number; +}; + +/** + * A `KeyringAccount` plus the derivation fields used by Solana, Tron, and Stellar. + * + * A snap may narrow `derivationPath` (for example Stellar's BIP-44 coin type). + */ +export type ExtendedKeyringAccount = KeyringAccount & KeyringAccountExtension; + +/** + * Converts an extended keyring account to the Keyring API shape (no extra fields). + * + * @param account - A keyring account, possibly with snap-specific fields. + * @returns A strict keyring account. + */ +export function asStrictKeyringAccount( + account: KeyringAccount, +): KeyringAccount { + const { id, address, type, options, methods, scopes } = account; + return { + id, + address, + type, + options, + methods, + scopes, + }; +} diff --git a/yarn.lock b/yarn.lock index 77cf460e..e5ea2548 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3327,6 +3327,7 @@ __metadata: dependencies: "@metamask/assets-controller": "npm:^13.0.0" "@metamask/auto-changelog": "npm:^6.1.1" + "@metamask/keyring-api": "npm:^24.1.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/remote-feature-flag-controller": "npm:^5.0.0" "@metamask/snaps-sdk": "npm:^12.0.1" From 4ce3c90c8d7ad21994ccb4e9c877256ea55e023e Mon Sep 17 00:00:00 2001 From: Julien Fontanel Date: Fri, 11 Sep 2026 17:18:36 +0200 Subject: [PATCH 2/2] chore: update changelog --- packages/snap-networks-utils/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snap-networks-utils/CHANGELOG.md b/packages/snap-networks-utils/CHANGELOG.md index d14dec9a..d5c96340 100644 --- a/packages/snap-networks-utils/CHANGELOG.md +++ b/packages/snap-networks-utils/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `ExtendedKeyringAccount`, `KeyringAccountExtension`, and `asStrictKeyringAccount` for snap keyring accounts that persist `entropySource`, `derivationPath`, and `index`. +- Add `ExtendedKeyringAccount`, `KeyringAccountExtension`, and `asStrictKeyringAccount` for snap keyring accounts that persist `entropySource`, `derivationPath`, and `index`. ([#300](https://github.com/MetaMask/internal-snaps/pull/300)) - Add shared snap state helpers `IStateManager`, `State`, and `InMemoryState` (Tron-style write mutex plus blob/path locking). ([#288](https://github.com/MetaMask/internal-snaps/pull/288)) - Add shared caching utilities for network snaps ([#287](https://github.com/MetaMask/internal-snaps/pull/287)) - `ICache`, `CacheEntry`, and `TimestampMilliseconds` for describing a generic cache