Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
611556b
feat(seedless-onboarding): add dataType support for secret data items
huggingbot Dec 2, 2025
85275f5
refactor: Removed default dataType parameter from createToprfKeyAndBa…
huggingbot Dec 2, 2025
fc73107
refactor: update documentation for secret data item methods
huggingbot Dec 2, 2025
3afa879
refactor: enhance primary secret data validation in SeedlessOnboardin…
huggingbot Dec 2, 2025
90d642b
fix: Update sorting mechanism to prioritize PrimarySrp dataType over …
huggingbot Dec 2, 2025
ac52a76
feat(seedless-onboarding-controller): add createdAt field and sort by…
huggingbot Dec 3, 2025
11c09f8
feat(seedless-onboarding-controller): add storage metadata to SecretM…
huggingbot Dec 3, 2025
2885314
fix(seedless-onboarding-controller): use timestamp extraction for TIM…
huggingbot Dec 5, 2025
3e2b477
chore: Update version
huggingbot Dec 16, 2025
f0b9638
Merge branch 'main' into feat/data-type
huggingbot Dec 16, 2025
ebcc4e0
chore: Update CHANGELOG and remove deprecated methods in SecretMetadata
huggingbot Dec 16, 2025
b279fa5
fix: improve sorting logic for secret data with mixed createdAt values
huggingbot Dec 18, 2025
074d8af
feat: add runMigrations for legacy dataType migration
huggingbot Jan 5, 2026
eb77007
feat: add setMigrationVersion method for direct migration version set…
huggingbot Jan 5, 2026
0bde969
Merge branch 'main' into feat/data-type
huggingbot Jan 5, 2026
7634383
feat: update SecretMetadata to use SecretDataItemOutput for storageVe…
huggingbot Jan 6, 2026
12d0ed0
fix: update SecretMetadata method signature to require storageMetadat…
huggingbot Jan 6, 2026
3aae970
refactor: remove SecretMetadataVersion from SecretMetadata and relate…
huggingbot Jan 6, 2026
e820ef5
fix: handle null dataType in SeedlessOnboardingController validation
huggingbot Jan 6, 2026
22b695e
refactor: replace type param with dataType in addNewSecretData
huggingbot Jan 6, 2026
c0dcad7
refactor: update SecretMetadata to streamline constructor options and…
huggingbot Jan 6, 2026
6e5a3ce
fix: ensure password synchronization before running migrations in See…
huggingbot Jan 6, 2026
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
17 changes: 17 additions & 0 deletions packages/seedless-onboarding-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add optional `dataType` parameter to `createToprfKeyAndBackupSeedPhrase` method (defaults to `PrimarySrp` for backward compatibility)
- Add optional `dataType` parameter to `addNewSecretData` method for categorizing secret data on insert
- Add `updateSecretDataItem` method to update fields for existing items by `itemId`
- Add `batchUpdateSecretDataItems` method to batch update fields for multiple items
- Add `SecretDataItemWithMetadata` type for storage-level metadata (`itemId`, `dataType`)
- Add `SecretMetadata.compareByTimestamp` static method for comparing metadata by timestamp
- Add `SecretMetadata.matchesType` static method for checking if metadata matches a given type
- Re-export `EncAccountDataType` from `@metamask/toprf-secure-backup`

### Changed

- **BREAKING:** Update `fetchAllSecretData` to return `SecretDataItemWithMetadata[]` instead of `SecretMetadata[]`
- Consumers must now access secret data via the `secret` property (e.g., `result[0].secret.data` instead of `result[0].data`)
- **BREAKING:** Remove `parseSecretsFromMetadataStore`, `fromBatch`, and `sort` methods from `SecretMetadata`
- Use `SecretMetadata.compareByTimestamp` for sorting
- Use `SecretMetadata.matchesType` for filtering
- Bump `@metamask/toprf-secure-backup` from `^0.10.0` to `^0.11.0`
- Move peer dependencies for controller and service packages to direct dependencies ([#7209](https://github.com/MetaMask/core/pull/7209))
- The dependencies moved are:
- `@metamask/keyring-controller` (^25.0.0)
Expand Down
2 changes: 1 addition & 1 deletion packages/seedless-onboarding-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@metamask/browser-passworder": "^6.0.0",
"@metamask/keyring-controller": "^25.0.0",
"@metamask/messenger": "^0.3.0",
"@metamask/toprf-secure-backup": "^0.10.0",
"@metamask/toprf-secure-backup": "^0.11.0",
"@metamask/utils": "^11.8.1",
"@noble/ciphers": "^1.3.0",
"@noble/curves": "^1.9.2",
Expand Down
99 changes: 26 additions & 73 deletions packages/seedless-onboarding-controller/src/SecretMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,37 +68,6 @@ export class SecretMetadata<DataType extends SecretDataType = Uint8Array>
this.#version = options?.version ?? SecretMetadataVersion.V1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should maintain the metadata type version with this.

The existing (old) items will have version number SecretMetadataVersion.V1 after decryption. We can do the increment on version number (V2) for new structure.

}

/**
* Create an Array of SecretMetadata instances from an array of secrets.
*
* To respect the order of the secrets, we add the index to the timestamp
* so that the first secret backup will have the oldest timestamp
* and the last secret backup will have the newest timestamp.
*
* @param data - The data to add metadata to.
* @param data.value - The SeedPhrase/PrivateKey to add metadata to.
* @param data.options - The options for the seed phrase metadata.
* @returns The SecretMetadata instances.
*/
static fromBatch<DataType extends SecretDataType = Uint8Array>(
data: {
value: DataType;
options?: Partial<SecretMetadataOptions>;
}[],
): SecretMetadata<DataType>[] {
const timestamp = Date.now();
return data.map((d, index) => {
// To respect the order of the seed phrases, we add the index to the timestamp
// so that the first seed phrase backup will have the oldest timestamp
// and the last seed phrase backup will have the newest timestamp
const backupCreatedAt = d.options?.timestamp ?? timestamp + index * 5;
return new SecretMetadata(d.value, {
timestamp: backupCreatedAt,
type: d.options?.type,
});
});
}

/**
* Assert that the provided value is a valid seed phrase metadata.
*
Expand All @@ -122,34 +91,6 @@ export class SecretMetadata<DataType extends SecretDataType = Uint8Array>
}
}

/**
* Parse the SecretMetadata from the metadata store and return the array of SecretMetadata instances.
*
* This method also sorts the secrets by timestamp in ascending order, i.e. the oldest secret will be the first element in the array.
*
* @param secretMetadataArr - The array of SecretMetadata from the metadata store.
* @param filterType - The type of the secret to filter.
* @returns The array of SecretMetadata instances.
*/
static parseSecretsFromMetadataStore<
DataType extends SecretDataType = Uint8Array,
>(
secretMetadataArr: Uint8Array[],
filterType?: SecretType,
): SecretMetadata<DataType>[] {
const parsedSecertMetadata = secretMetadataArr.map((metadata) =>
SecretMetadata.fromRawMetadata<DataType>(metadata),
);

const secrets = SecretMetadata.sort(parsedSecertMetadata);

if (filterType) {
return secrets.filter((secret) => secret.type === filterType);
}

return secrets;
}

/**
* Parse and create the SecretMetadata instance from the raw metadata bytes.
*
Expand Down Expand Up @@ -183,23 +124,35 @@ export class SecretMetadata<DataType extends SecretDataType = Uint8Array>
}

/**
* Sort the seed phrases by timestamp.
*
* @param data - The secret metadata array to sort.
* @param order - The order to sort the seed phrases. Default is `desc`.
* Compare two SecretMetadata instances by timestamp.
*
* @returns The sorted secret metadata array.
* @param a - The first SecretMetadata instance.
* @param b - The second SecretMetadata instance.
* @param order - The sort order. Default is 'asc'.
* @returns A negative number if a < b, positive if a > b, zero if equal.
*/
static sort<DataType extends SecretDataType = Uint8Array>(
data: SecretMetadata<DataType>[],
static compareByTimestamp<DataType extends SecretDataType = SecretDataType>(
a: SecretMetadata<DataType>,
b: SecretMetadata<DataType>,
order: 'asc' | 'desc' = 'asc',
): SecretMetadata<DataType>[] {
return data.sort((a, b) => {
if (order === 'asc') {
return a.timestamp - b.timestamp;
}
return b.timestamp - a.timestamp;
});
): number {
return order === 'asc'
? a.timestamp - b.timestamp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be compared using the server timestamp (createdAt?)

: b.timestamp - a.timestamp;
}

/**
* Check if a SecretMetadata instance matches the given type.
*
* @param secret - The SecretMetadata instance to check.
* @param type - The type to match against.
* @returns True if the secret matches the type.
*/
static matchesType<DataType extends SecretDataType = SecretDataType>(
secret: SecretMetadata<DataType>,
type: SecretType,
): boolean {
return secret.type === type;
}

get data(): DataType {
Expand Down
Loading
Loading