Skip to content

feat(account-tree-controller): add payload/snapshot support - #9826

Open
ccharly wants to merge 11 commits into
mainfrom
cc/feat/account-tree-export-import-payload-and-snapshot
Open

feat(account-tree-controller): add payload/snapshot support#9826
ccharly wants to merge 11 commits into
mainfrom
cc/feat/account-tree-export-import-payload-and-snapshot

Conversation

@ccharly

@ccharly ccharly commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Explanation

First part of the new :{import,export}State actions for the account-tree.

This PR only exposes the new payload/snapshot types that will be used to import/export.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Touches wallet payload shapes and secret-bearing fields (mnemonics/private keys) with validation/redaction, but does not wire import/export into the controller yet; mistakes in schema or filtering could affect future cross-device account migration.

Overview
Introduces the v1 portable account-tree format and supporting types ahead of importState / exportState on the controller. New code lives under packages/account-tree-controller/src/state/.

AccountTreePayload defines a versioned flat snapshot (ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION = 1) with mnemonic HD wallets and a merged private-key wallet entry, stable wallet: / group ID helpers, and optional secret fields. Validation uses Superstruct (AccountTreePayloadStruct), rejects unknown versions and wallet types, enforces contiguous mnemonic groupIndex values, and wraps secrets with sensitive() plus formatValidationErrorMessages so validation errors do not leak mnemonics or keys.

AccountTreeSnapshot is an immutable value object: entries are deep-cloned and frozen, with filterWallets / filterGroups / filterAllGroups (dropping empty wallets), serialize() back to payload, and deserialize() as the untrusted entry point. An optional IdMap bridges local controller IDs to payload IDs through filtering; deserialized snapshots have no map unless supplied at construction.

utils adds JSON byte encoding, deepFreeze, and shared validation error formatting. Broad unit tests cover ID mapping, snapshot behavior, payload edge cases, and secret redaction.

Reviewed by Cursor Bugbot for commit ccc122e. Bugbot is set up for automated code reviews on this repo. Configure here.

@ccharly

ccharly commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Using no-changelog for this one, since it's purely internal for the moment. I'll make sure to update the main PR with this PR reference:

See: 9e0e993

@ccharly
ccharly marked this pull request as ready for review August 11, 2026 16:28
@ccharly
ccharly requested a review from a team as a code owner August 11, 2026 16:28
@ccharly
ccharly deployed to default-branch August 11, 2026 16:28 — with GitHub Actions Active
Comment thread packages/account-tree-controller/src/state/id-map.ts
Comment thread packages/account-tree-controller/src/state/snapshot.ts
@ccharly
ccharly enabled auto-merge August 11, 2026 17:11

@gantunesr gantunesr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good overall

  • Would be good to have an integration test for the flow export→filter→serialize→deserialize. This is probably covered in the next PR for the controller wiring

Comment thread packages/account-tree-controller/src/state/payload.ts
Comment on lines +212 to +215
export type ExportStateOptions = {
/** When `true`, secrets (mnemonic / private keys) are included. Requires the vault to be unlocked. */
includeSecrets?: boolean;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do you think there is any explicit cleanup/teardown to do when secrets are exported?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To like clearing them out of memory?

Like you'd like to have them being passed as options to control this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I investigated this a bit with claude.

The real problem is that we use raw string for the secrets, and it makes it hard to zero-ing memory completely with those (since we don't own the real memory buffer for those, only the view/value on it).

If we really wanted to do this, we would need to use real buffer types like ArrayBuffer or Uint8Array. But that means our payload will need special encoding functions (at least, to make it compatible with JSON-encoding for example).

Like we could have a .stringify() and .parse(json) for example.

But that would yet another steps before/after the snapshot's serialize and deserialize.

@ccharly ccharly Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After discussing a bit about this internally, I actually went for a different way of encoding those in our payload, so we "limit" the window of when those sensitive values are used.

They are just encoded differently, so it's more or less security-by-obscurity, but it's slightly better than having them in clear I think (and that's aligned with how we encode them elsewhere in the codebase)

ccc122e

* @throws If `raw` is not a valid payload or its version is unsupported.
*/
static async deserialize(raw: unknown): Promise<AccountTreeSnapshot> {
// TODO: Use migration framework here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Decided to omit the migration part for now. My plan was to use our new generic migration framework that we created for keyrings. Though, it's not needed here (we expect a v1 payload + the migration framework needs a slight rework around version handling + flattening the state/data being migrated).

Delaying this work for a bit to not block this PR.

Comment thread packages/account-tree-controller/src/state/payload.test.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ee63cf4. Configure here.

Comment thread packages/account-tree-controller/src/state/utils.test.ts
@ccharly

ccharly commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Looks good overall

  • Would be good to have an integration test for the flow export→filter→serialize→deserialize. This is probably covered in the next PR for the controller wiring

We indeed have some round-trips "integration" tests on the controller itself, to make sure the combo :exportState and :importState can be used together.

If needed, I'll add even more scenarios to also include filtering in this mix!

gantunesr
gantunesr previously approved these changes Aug 12, 2026
@ccharly
ccharly added this pull request to the merge queue Aug 12, 2026
@ccharly
ccharly removed this pull request from the merge queue due to a manual request Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants