From 1516c175d51ff60eabd80a90d67c8f9e43cd6474 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 22 Jul 2026 14:34:19 +0200 Subject: [PATCH 1/7] feat(keyring-api): add optional balance.metadata --- .../keyring-api/src/api/balance.test-d.ts | 9 +++++++++ packages/keyring-api/src/api/balance.test.ts | 19 +++++++++++++++++++ packages/keyring-api/src/api/balance.ts | 4 +++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/keyring-api/src/api/balance.test-d.ts b/packages/keyring-api/src/api/balance.test-d.ts index ccb0cc662..01b746f17 100644 --- a/packages/keyring-api/src/api/balance.test-d.ts +++ b/packages/keyring-api/src/api/balance.test-d.ts @@ -17,3 +17,12 @@ expectNotAssignable({ amount: '1.0', unit: 1 }); expectNotAssignable({ amount: '1.0', unit: true }); expectNotAssignable({ amount: '1.0', unit: undefined }); expectNotAssignable({ amount: '1.0', unit: null }); + +// metadata is optional and accepts arbitrary JSON-compatible key/value pairs +expectAssignable({ amount: '1.0', unit: 'ETH', metadata: { foo: 'bar' } }); +expectAssignable({ amount: '1.0', unit: 'ETH', metadata: { count: 42, flag: true } }); +expectAssignable({ amount: '1.0', unit: 'ETH', metadata: { nested: { x: 1 } } }); +expectAssignable({ amount: '1.0', unit: 'ETH' }); + +expectNotAssignable({ amount: '1.0', unit: 'ETH', metadata: 'string' }); +expectNotAssignable({ amount: '1.0', unit: 'ETH', metadata: 42 }); diff --git a/packages/keyring-api/src/api/balance.test.ts b/packages/keyring-api/src/api/balance.test.ts index 27ce466fb..c291ea60a 100644 --- a/packages/keyring-api/src/api/balance.test.ts +++ b/packages/keyring-api/src/api/balance.test.ts @@ -19,6 +19,25 @@ describe('BalanceStruct', () => { { balance: { amount: '1.0', unit: 1 }, expected: false }, { balance: { amount: '1.0', unit: true }, expected: false }, { balance: { amount: '1.0', unit: null }, expected: false }, + // With metadata + { + balance: { amount: '1.0', unit: 'ETH', metadata: { foo: 'bar' } }, + expected: true, + }, + { + balance: { + amount: '1.0', + unit: 'ETH', + metadata: { count: 42, flag: true, nested: { x: 1 } }, + }, + expected: true, + }, + // Without metadata (optional — key must be absent, not explicitly undefined) + { balance: { amount: '1.0', unit: 'ETH', metadata: undefined }, expected: false }, + // Invalid metadata values + { balance: { amount: '1.0', unit: 'ETH', metadata: 'string' }, expected: false }, + { balance: { amount: '1.0', unit: 'ETH', metadata: 42 }, expected: false }, + { balance: { amount: '1.0', unit: 'ETH', metadata: { key: undefined } }, expected: false }, ])( 'returns $expected for is($balance, BalanceStruct)', ({ balance, expected }) => { diff --git a/packages/keyring-api/src/api/balance.ts b/packages/keyring-api/src/api/balance.ts index 2ab2a2989..5c57282ae 100644 --- a/packages/keyring-api/src/api/balance.ts +++ b/packages/keyring-api/src/api/balance.ts @@ -1,10 +1,12 @@ import { StringNumberStruct } from '@metamask/keyring-utils'; import type { Infer } from '@metamask/superstruct'; -import { object, string } from '@metamask/superstruct'; +import { exactOptional, object, record, string } from '@metamask/superstruct'; +import { JsonStruct } from '@metamask/utils'; export const BalanceStruct = object({ amount: StringNumberStruct, unit: string(), + metadata: exactOptional(record(string(), JsonStruct)), }); export type Balance = Infer; From 80bbf0dc3d4946a2054c5075b249633c70b69d96 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 22 Jul 2026 14:47:55 +0200 Subject: [PATCH 2/7] chore: lint --- .../keyring-api/src/api/balance.test-d.ts | 24 +++++++++++++++---- packages/keyring-api/src/api/balance.test.ts | 15 +++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/keyring-api/src/api/balance.test-d.ts b/packages/keyring-api/src/api/balance.test-d.ts index 01b746f17..47f59e689 100644 --- a/packages/keyring-api/src/api/balance.test-d.ts +++ b/packages/keyring-api/src/api/balance.test-d.ts @@ -19,10 +19,26 @@ expectNotAssignable({ amount: '1.0', unit: undefined }); expectNotAssignable({ amount: '1.0', unit: null }); // metadata is optional and accepts arbitrary JSON-compatible key/value pairs -expectAssignable({ amount: '1.0', unit: 'ETH', metadata: { foo: 'bar' } }); -expectAssignable({ amount: '1.0', unit: 'ETH', metadata: { count: 42, flag: true } }); -expectAssignable({ amount: '1.0', unit: 'ETH', metadata: { nested: { x: 1 } } }); +expectAssignable({ + amount: '1.0', + unit: 'ETH', + metadata: { foo: 'bar' }, +}); +expectAssignable({ + amount: '1.0', + unit: 'ETH', + metadata: { count: 42, flag: true }, +}); +expectAssignable({ + amount: '1.0', + unit: 'ETH', + metadata: { nested: { x: 1 } }, +}); expectAssignable({ amount: '1.0', unit: 'ETH' }); -expectNotAssignable({ amount: '1.0', unit: 'ETH', metadata: 'string' }); +expectNotAssignable({ + amount: '1.0', + unit: 'ETH', + metadata: 'string', +}); expectNotAssignable({ amount: '1.0', unit: 'ETH', metadata: 42 }); diff --git a/packages/keyring-api/src/api/balance.test.ts b/packages/keyring-api/src/api/balance.test.ts index c291ea60a..63b7f2f69 100644 --- a/packages/keyring-api/src/api/balance.test.ts +++ b/packages/keyring-api/src/api/balance.test.ts @@ -33,11 +33,20 @@ describe('BalanceStruct', () => { expected: true, }, // Without metadata (optional — key must be absent, not explicitly undefined) - { balance: { amount: '1.0', unit: 'ETH', metadata: undefined }, expected: false }, + { + balance: { amount: '1.0', unit: 'ETH', metadata: undefined }, + expected: false, + }, // Invalid metadata values - { balance: { amount: '1.0', unit: 'ETH', metadata: 'string' }, expected: false }, + { + balance: { amount: '1.0', unit: 'ETH', metadata: 'string' }, + expected: false, + }, { balance: { amount: '1.0', unit: 'ETH', metadata: 42 }, expected: false }, - { balance: { amount: '1.0', unit: 'ETH', metadata: { key: undefined } }, expected: false }, + { + balance: { amount: '1.0', unit: 'ETH', metadata: { key: undefined } }, + expected: false, + }, ])( 'returns $expected for is($balance, BalanceStruct)', ({ balance, expected }) => { From c31e9cf9bc0bf6a2b4a40b45729413ff798e12e6 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 22 Jul 2026 14:49:06 +0200 Subject: [PATCH 3/7] chore: changelog --- packages/keyring-api/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/keyring-api/CHANGELOG.md b/packages/keyring-api/CHANGELOG.md index fbb94573c..822420909 100644 --- a/packages/keyring-api/CHANGELOG.md +++ b/packages/keyring-api/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add support for `base32` private key encoding in `exportAccount` ([#589](https://github.com/MetaMask/accounts/pull/589)) +- Add `Balance.metadata` ([#601](https://github.com/MetaMask/accounts/pull/601)) + - This can be used to pass extra-metadata alongside assets balances. ### Changed From abb5113f2c124d21648e6a72bc2dcc100585b4f5 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 22 Jul 2026 16:00:57 +0200 Subject: [PATCH 4/7] refactor: re-use BalanceStruct in FungibleAssetAmountStruct --- packages/keyring-api/src/api/asset.test-d.ts | 24 ++++++++++++++++++++ packages/keyring-api/src/api/asset.test.ts | 22 ++++++++++++++++++ packages/keyring-api/src/api/asset.ts | 16 ++++--------- packages/keyring-api/src/api/balance.ts | 11 +++++++++ 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/packages/keyring-api/src/api/asset.test-d.ts b/packages/keyring-api/src/api/asset.test-d.ts index f150b7210..bd162d502 100644 --- a/packages/keyring-api/src/api/asset.test-d.ts +++ b/packages/keyring-api/src/api/asset.test-d.ts @@ -9,6 +9,30 @@ expectAssignable({ amount: '0.01', }); +expectAssignable({ + fungible: true, + type: 'eip155:1/slip44:60', + unit: 'ETH', + amount: '0.01', + metadata: { foo: 'bar', count: 42, flag: true }, +}); + +expectNotAssignable({ + fungible: true, + type: 'eip155:1/slip44:60', + unit: 'ETH', + amount: '0.01', + metadata: 'string', +}); + +expectNotAssignable({ + fungible: true, + type: 'eip155:1/slip44:60', + unit: 'ETH', + amount: '0.01', + metadata: 42, +}); + expectAssignable({ fungible: false, id: 'hedera:mainnet/nft:0.0.55492/12', diff --git a/packages/keyring-api/src/api/asset.test.ts b/packages/keyring-api/src/api/asset.test.ts index 572086554..861c6ead4 100644 --- a/packages/keyring-api/src/api/asset.test.ts +++ b/packages/keyring-api/src/api/asset.test.ts @@ -36,6 +36,28 @@ describe('AssetStruct', () => { }, expected: true, }, + // Valid with metadata + { + asset: { + fungible: true, + type: 'eip155:1/slip44:60', + unit: 'ETH', + amount: '0.01', + metadata: { foo: 'bar', count: 42 }, + }, + expected: true, + }, + // Invalid metadata type + { + asset: { + fungible: true, + type: 'eip155:1/slip44:60', + unit: 'ETH', + amount: '0.01', + metadata: 'string', + }, + expected: false, + }, ])('returns $expected for is($asset, AssetStruct)', ({ asset, expected }) => { expect(is(asset, AssetStruct)).toBe(expected); }); diff --git a/packages/keyring-api/src/api/asset.ts b/packages/keyring-api/src/api/asset.ts index 04be00d22..98314f55f 100644 --- a/packages/keyring-api/src/api/asset.ts +++ b/packages/keyring-api/src/api/asset.ts @@ -1,25 +1,19 @@ -import { selectiveUnion, StringNumberStruct } from '@metamask/keyring-utils'; +import { selectiveUnion } from '@metamask/keyring-utils'; import type { Infer } from '@metamask/superstruct'; -import { literal, object, string } from '@metamask/superstruct'; +import { literal, object } from '@metamask/superstruct'; import { CaipAssetIdStruct, CaipAssetTypeStruct, isPlainObject, } from '@metamask/utils'; +import { BalanceStruct } from './balance'; + /** * Fungible asset amount struct. */ export const FungibleAssetAmountStruct = object({ - /** - * Asset unit. - */ - unit: string(), - - /** - * Asset amount. - */ - amount: StringNumberStruct, + ...BalanceStruct.schema, }); /** diff --git a/packages/keyring-api/src/api/balance.ts b/packages/keyring-api/src/api/balance.ts index 5c57282ae..02231a928 100644 --- a/packages/keyring-api/src/api/balance.ts +++ b/packages/keyring-api/src/api/balance.ts @@ -4,8 +4,19 @@ import { exactOptional, object, record, string } from '@metamask/superstruct'; import { JsonStruct } from '@metamask/utils'; export const BalanceStruct = object({ + /** + * Asset amount. + */ amount: StringNumberStruct, + + /** + * Asset unit. + */ unit: string(), + + /** + * Optional arbitrary metadata associated with this balance. + */ metadata: exactOptional(record(string(), JsonStruct)), }); From 7a494ea378416b97f829921d4eb2aea542d7a6c8 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 22 Jul 2026 16:41:44 +0200 Subject: [PATCH 5/7] chore: changelog --- packages/keyring-api/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/keyring-api/CHANGELOG.md b/packages/keyring-api/CHANGELOG.md index 822420909..7bdb7cadd 100644 --- a/packages/keyring-api/CHANGELOG.md +++ b/packages/keyring-api/CHANGELOG.md @@ -10,8 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add support for `base32` private key encoding in `exportAccount` ([#589](https://github.com/MetaMask/accounts/pull/589)) -- Add `Balance.metadata` ([#601](https://github.com/MetaMask/accounts/pull/601)) +- Add `{Balance,FunibleAssetAmountStruct}.metadata` ([#601](https://github.com/MetaMask/accounts/pull/601)) - This can be used to pass extra-metadata alongside assets balances. + - `FungibleAssetAmountStruct` now composes `BalanceStruct` + - `notify:accountBalancesUpdated` also accepts `metadata` now. ### Changed From c51e7579103ed2a9293416a8928e326f214ee9c7 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Wed, 22 Jul 2026 16:52:02 +0200 Subject: [PATCH 6/7] fix: changelog --- packages/keyring-api/CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/keyring-api/CHANGELOG.md b/packages/keyring-api/CHANGELOG.md index 445c8d2c3..9abfa9aa5 100644 --- a/packages/keyring-api/CHANGELOG.md +++ b/packages/keyring-api/CHANGELOG.md @@ -7,16 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [23.6.0] - ### Added -- Add support for `base32` private key encoding in `exportAccount` ([#589](https://github.com/MetaMask/accounts/pull/589)) - Add `{Balance,FunibleAssetAmountStruct}.metadata` ([#601](https://github.com/MetaMask/accounts/pull/601)) - This can be used to pass extra-metadata alongside assets balances. - `FungibleAssetAmountStruct` now composes `BalanceStruct` - `notify:accountBalancesUpdated` also accepts `metadata` now. +## [23.6.0] + +### Added + +- Add support for `base32` private key encoding in `exportAccount` ([#589](https://github.com/MetaMask/accounts/pull/589)) + ### Changed - Use `sensitive` struct for `privateKey` in `PrivateKeyExportedAccountStruct` ([#577](https://github.com/MetaMask/accounts/pull/577)) From bd8292c58f04fe580283c1069073cb8c36fa7cd6 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Thu, 23 Jul 2026 10:57:38 +0200 Subject: [PATCH 7/7] chore: typo --- packages/keyring-api/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/keyring-api/CHANGELOG.md b/packages/keyring-api/CHANGELOG.md index 9abfa9aa5..16b9aa20d 100644 --- a/packages/keyring-api/CHANGELOG.md +++ b/packages/keyring-api/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `{Balance,FunibleAssetAmountStruct}.metadata` ([#601](https://github.com/MetaMask/accounts/pull/601)) +- Add `{Balance,FungibleAssetAmountStruct}.metadata` ([#601](https://github.com/MetaMask/accounts/pull/601)) - This can be used to pass extra-metadata alongside assets balances. - `FungibleAssetAmountStruct` now composes `BalanceStruct` - `notify:accountBalancesUpdated` also accepts `metadata` now.