@metamask-previews/account-tree-controller 7.6.1-preview-e57e5c3dc → 7.6.1-preview-c449b8da8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/AccountTreeController-method-action-types.cjs.map +1 -1
  3. package/dist/AccountTreeController-method-action-types.d.cts +34 -1
  4. package/dist/AccountTreeController-method-action-types.d.cts.map +1 -1
  5. package/dist/AccountTreeController-method-action-types.d.mts +34 -1
  6. package/dist/AccountTreeController-method-action-types.d.mts.map +1 -1
  7. package/dist/AccountTreeController-method-action-types.mjs.map +1 -1
  8. package/dist/AccountTreeController.cjs +42 -0
  9. package/dist/AccountTreeController.cjs.map +1 -1
  10. package/dist/AccountTreeController.d.cts +29 -0
  11. package/dist/AccountTreeController.d.cts.map +1 -1
  12. package/dist/AccountTreeController.d.mts +29 -0
  13. package/dist/AccountTreeController.d.mts.map +1 -1
  14. package/dist/AccountTreeController.mjs +42 -0
  15. package/dist/AccountTreeController.mjs.map +1 -1
  16. package/dist/index.cjs +10 -1
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +5 -1
  19. package/dist/index.d.cts.map +1 -1
  20. package/dist/index.d.mts +5 -1
  21. package/dist/index.d.mts.map +1 -1
  22. package/dist/index.mjs +3 -0
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/state/export.cjs +214 -0
  25. package/dist/state/export.cjs.map +1 -0
  26. package/dist/state/export.d.cts +40 -0
  27. package/dist/state/export.d.cts.map +1 -0
  28. package/dist/state/export.d.mts +40 -0
  29. package/dist/state/export.d.mts.map +1 -0
  30. package/dist/state/export.mjs +208 -0
  31. package/dist/state/export.mjs.map +1 -0
  32. package/dist/state/id-map.cjs +65 -0
  33. package/dist/state/id-map.cjs.map +1 -0
  34. package/dist/state/id-map.d.cts +41 -0
  35. package/dist/state/id-map.d.cts.map +1 -0
  36. package/dist/state/id-map.d.mts +41 -0
  37. package/dist/state/id-map.d.mts.map +1 -0
  38. package/dist/state/id-map.mjs +61 -0
  39. package/dist/state/id-map.mjs.map +1 -0
  40. package/dist/state/import.cjs +268 -0
  41. package/dist/state/import.cjs.map +1 -0
  42. package/dist/state/import.d.cts +29 -0
  43. package/dist/state/import.d.cts.map +1 -0
  44. package/dist/state/import.d.mts +29 -0
  45. package/dist/state/import.d.mts.map +1 -0
  46. package/dist/state/import.mjs +264 -0
  47. package/dist/state/import.mjs.map +1 -0
  48. package/dist/state/payload.cjs +169 -0
  49. package/dist/state/payload.cjs.map +1 -0
  50. package/dist/state/payload.d.cts +288 -0
  51. package/dist/state/payload.d.cts.map +1 -0
  52. package/dist/state/payload.d.mts +288 -0
  53. package/dist/state/payload.d.mts.map +1 -0
  54. package/dist/state/payload.mjs +162 -0
  55. package/dist/state/payload.mjs.map +1 -0
  56. package/dist/state/snapshot.cjs +202 -0
  57. package/dist/state/snapshot.cjs.map +1 -0
  58. package/dist/state/snapshot.d.cts +120 -0
  59. package/dist/state/snapshot.d.cts.map +1 -0
  60. package/dist/state/snapshot.d.mts +120 -0
  61. package/dist/state/snapshot.d.mts.map +1 -0
  62. package/dist/state/snapshot.mjs +198 -0
  63. package/dist/state/snapshot.mjs.map +1 -0
  64. package/dist/state/utils.cjs +68 -0
  65. package/dist/state/utils.cjs.map +1 -0
  66. package/dist/state/utils.d.cts +49 -0
  67. package/dist/state/utils.d.cts.map +1 -0
  68. package/dist/state/utils.d.mts +49 -0
  69. package/dist/state/utils.d.mts.map +1 -0
  70. package/dist/state/utils.mjs +61 -0
  71. package/dist/state/utils.mjs.map +1 -0
  72. package/dist/types.cjs.map +1 -1
  73. package/dist/types.d.cts +3 -3
  74. package/dist/types.d.cts.map +1 -1
  75. package/dist/types.d.mts +3 -3
  76. package/dist/types.d.mts.map +1 -1
  77. package/dist/types.mjs.map +1 -1
  78. package/package.json +3 -2
@@ -0,0 +1,162 @@
1
+ import { KeyringAccountTypeStruct } from "@metamask/keyring-api";
2
+ import { array, assert, boolean, define, enums, integer, literal, object, exactOptional, refine, sensitive, string, StructError, union } from "@metamask/superstruct";
3
+ import { BytesStruct, formatValidationErrorMessages } from "./utils.mjs";
4
+ const PAYLOAD_GROUP_ID_REGEX = /^(?<walletId>wallet:[^/]+)\/(?<subId>.+)$/u;
5
+ /**
6
+ * Parses a payload group ID into its wallet ID and group sub-ID components.
7
+ *
8
+ * @param groupId - The payload group ID to parse.
9
+ * @returns The parsed wallet ID and group sub-ID.
10
+ * @throws If the group ID format is invalid.
11
+ */
12
+ export function parsePayloadGroupId(groupId) {
13
+ const match = PAYLOAD_GROUP_ID_REGEX.exec(groupId);
14
+ if (!match?.groups) {
15
+ throw new Error(`Invalid payload group ID: "${groupId}"`);
16
+ }
17
+ return {
18
+ walletId: match.groups.walletId,
19
+ subId: match.groups.subId,
20
+ };
21
+ }
22
+ /**
23
+ * Wallet type discriminants used in serialized {@link AccountTreePayload} entries.
24
+ *
25
+ * Use these constants instead of raw string literals so callers get autocomplete
26
+ * and a single source of truth for the discriminant values.
27
+ */
28
+ export const AccountWalletPayloadType = {
29
+ Mnemonic: 'mnemonic',
30
+ PrivateKey: 'private-key',
31
+ };
32
+ /**
33
+ * Encoding formats for exported private key material.
34
+ */
35
+ export const AccountWalletPrivateKeyEncoding = {
36
+ Hexadecimal: 'hexadecimal',
37
+ Base58: 'base58',
38
+ Base32: 'base32',
39
+ };
40
+ /**
41
+ * Constructs an {@link AccountWalletPayloadId} from an entropy source ID.
42
+ *
43
+ * @param entropySourceId - Stable entropy source ID returned by `HdKeyring.toEntropySourceId()`.
44
+ * @returns The portable wallet payload ID.
45
+ */
46
+ export function toWalletPayloadId(entropySourceId) {
47
+ return `wallet:${entropySourceId}`;
48
+ }
49
+ /**
50
+ * Constructs an {@link AccountGroupPayloadId} from a wallet payload ID and a sub-ID.
51
+ *
52
+ * @param walletId - The wallet payload ID this group belongs to.
53
+ * @param subId - The group-specific sub-ID (e.g. group index for mnemonic wallets, address for private-key wallets).
54
+ * @returns The portable group payload ID.
55
+ */
56
+ export function toGroupPayloadId(walletId, subId) {
57
+ return `${walletId}/${subId}`;
58
+ }
59
+ const AccountWalletPayloadIdStruct = define('AccountWalletPayloadId', (value) => typeof value === 'string' && value.startsWith('wallet:')
60
+ ? true
61
+ : 'Expected a wallet payload ID starting with "wallet:"');
62
+ const AccountGroupPayloadIdStruct = define('AccountGroupPayloadId', (value) => typeof value === 'string' && PAYLOAD_GROUP_ID_REGEX.test(value)
63
+ ? true
64
+ : 'Expected a group payload ID in the form "wallet:<id>/<subId>"');
65
+ const AccountWalletPayloadMetadataStruct = object({
66
+ name: string(),
67
+ });
68
+ const AccountWalletGroupPayloadMetadataStruct = object({
69
+ name: string(),
70
+ pinned: boolean(),
71
+ hidden: boolean(),
72
+ });
73
+ const AccountWalletPrivateKeyValueStruct = object({
74
+ privateKey: sensitive(BytesStruct),
75
+ encoding: enums(Object.values(AccountWalletPrivateKeyEncoding)),
76
+ type: exactOptional(KeyringAccountTypeStruct),
77
+ });
78
+ const AccountWalletMnemonicGroupEntryStruct = object({
79
+ id: AccountGroupPayloadIdStruct,
80
+ groupIndex: integer(),
81
+ metadata: AccountWalletGroupPayloadMetadataStruct,
82
+ });
83
+ const AccountWalletPrivateKeyGroupEntryStruct = object({
84
+ id: AccountGroupPayloadIdStruct,
85
+ value: exactOptional(AccountWalletPrivateKeyValueStruct),
86
+ metadata: AccountWalletGroupPayloadMetadataStruct,
87
+ });
88
+ // The `groups` array in a mnemonic wallet payload must have contiguous group indices starting at 0.
89
+ const AccountWalletMnemonicGroupsStruct = refine(array(AccountWalletMnemonicGroupEntryStruct), 'contiguous-group-indices', (groups) => {
90
+ let prev;
91
+ for (const curr of groups) {
92
+ if (prev === undefined && curr.groupIndex !== 0) {
93
+ return `group indices must start at 0; got ${curr.groupIndex}`;
94
+ }
95
+ if (prev !== undefined && curr.groupIndex !== prev.groupIndex + 1) {
96
+ return `group indices must be contiguous and sorted; found gap between index ${prev.groupIndex} and ${curr.groupIndex}`;
97
+ }
98
+ prev = curr;
99
+ }
100
+ return true;
101
+ });
102
+ const AccountWalletMnemonicPayloadStruct = object({
103
+ id: AccountWalletPayloadIdStruct,
104
+ type: literal(AccountWalletPayloadType.Mnemonic),
105
+ value: exactOptional(sensitive(BytesStruct)),
106
+ metadata: AccountWalletPayloadMetadataStruct,
107
+ groups: AccountWalletMnemonicGroupsStruct,
108
+ });
109
+ const AccountWalletPrivateKeyPayloadStruct = object({
110
+ id: AccountWalletPayloadIdStruct,
111
+ type: literal(AccountWalletPayloadType.PrivateKey),
112
+ metadata: AccountWalletPayloadMetadataStruct,
113
+ groups: array(AccountWalletPrivateKeyGroupEntryStruct),
114
+ });
115
+ const AccountTreeWalletEntryStruct = union([
116
+ AccountWalletMnemonicPayloadStruct,
117
+ AccountWalletPrivateKeyPayloadStruct,
118
+ ]);
119
+ /** Current version of the {@link AccountTreePayload} format. */
120
+ export const ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION = 1;
121
+ /**
122
+ * Superstruct schema for a fully versioned {@link AccountTreePayload}.
123
+ *
124
+ * Pins `version` to exactly {@link ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION} so
125
+ * payloads from newer clients (v2+) are rejected rather than silently
126
+ * mis-handled as v1. Once a migration framework is wired up in
127
+ * {@link AccountTreeSnapshot.deserialize}, older versions will be up-migrated
128
+ * before this struct is checked, and newer versions will require a new struct.
129
+ *
130
+ * Secret fields (`value`, `privateKey`) use the Superstruct `sensitive()`
131
+ * wrapper so validation failures redact secrets from error output.
132
+ */
133
+ export const AccountTreePayloadStruct = object({
134
+ version: literal(ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION),
135
+ wallets: array(AccountTreeWalletEntryStruct),
136
+ });
137
+ /**
138
+ * Asserts that `value` conforms to the v1 {@link AccountTreePayload} schema.
139
+ *
140
+ * Prefer {@link AccountTreeSnapshot.deserialize} at transport boundaries so
141
+ * validation stays paired with snapshot construction. Use this helper when you
142
+ * already hold a parsed object and need to assert its shape before further
143
+ * processing.
144
+ *
145
+ * @param value - Value to validate.
146
+ * @throws If `value` is not a valid v1 payload, including unsupported wallet types.
147
+ */
148
+ export function assertAccountTreePayload(value) {
149
+ try {
150
+ // AccountTreePayloadStruct pins version to ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,
151
+ // so unknown future versions are rejected here rather than silently mis-handled.
152
+ assert(value, AccountTreePayloadStruct);
153
+ }
154
+ catch (error) {
155
+ if (error instanceof StructError) {
156
+ throw new Error(`Invalid AccountTreePayload: ${formatValidationErrorMessages(error)}`);
157
+ }
158
+ /* istanbul ignore next */
159
+ throw error;
160
+ }
161
+ }
162
+ //# sourceMappingURL=payload.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.mjs","sourceRoot":"","sources":["../../src/state/payload.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyringAccountTypeStruct } from '@metamask/keyring-api';\nimport type { KeyringAccount } from '@metamask/keyring-api';\nimport {\n array,\n assert,\n boolean,\n define,\n enums,\n integer,\n literal,\n object,\n exactOptional,\n refine,\n sensitive,\n string,\n StructError,\n union,\n} from '@metamask/superstruct';\nimport type { Infer } from '@metamask/superstruct';\n\nimport type { DeepReadonly, EncodedBytes } from './utils.js';\nimport { BytesStruct, formatValidationErrorMessages } from './utils.js';\n\n/** Stable cross-device wallet identifier. Format: `wallet:<entropySourceId>`. */\nexport type AccountWalletPayloadId = `wallet:${string}`;\n\n/** Stable cross-device group identifier. Format: `wallet:<entropySourceId>/<groupSubId>`. */\nexport type AccountGroupPayloadId = `${AccountWalletPayloadId}/${string}`;\n\n/**\n * Parsed representation of an {@link AccountGroupPayloadId}.\n */\nexport type ParsedPayloadGroupId = {\n /** The wallet portion of the group ID. */\n walletId: AccountWalletPayloadId;\n /** The group-specific sub-ID (e.g. group index for mnemonic wallets, address for private-key wallets). */\n subId: string;\n};\n\nconst PAYLOAD_GROUP_ID_REGEX = /^(?<walletId>wallet:[^/]+)\\/(?<subId>.+)$/u;\n\n/**\n * Parses a payload group ID into its wallet ID and group sub-ID components.\n *\n * @param groupId - The payload group ID to parse.\n * @returns The parsed wallet ID and group sub-ID.\n * @throws If the group ID format is invalid.\n */\nexport function parsePayloadGroupId(\n groupId: AccountGroupPayloadId,\n): ParsedPayloadGroupId {\n const match = PAYLOAD_GROUP_ID_REGEX.exec(groupId);\n if (!match?.groups) {\n throw new Error(`Invalid payload group ID: \"${groupId}\"`);\n }\n return {\n walletId: match.groups.walletId as AccountWalletPayloadId,\n subId: match.groups.subId,\n };\n}\n\n/**\n * Wallet type discriminants used in serialized {@link AccountTreePayload} entries.\n *\n * Use these constants instead of raw string literals so callers get autocomplete\n * and a single source of truth for the discriminant values.\n */\nexport const AccountWalletPayloadType = {\n Mnemonic: 'mnemonic',\n PrivateKey: 'private-key',\n} as const;\n\nexport type AccountWalletPayloadType =\n (typeof AccountWalletPayloadType)[keyof typeof AccountWalletPayloadType];\n\n/** Wallet-level metadata carried in every payload wallet entry. */\nexport type AccountWalletPayloadMetadata = { name: string };\n\n/** Group-level metadata carried in every payload group entry. */\nexport type AccountWalletGroupPayloadMetadata = {\n name: string;\n pinned: boolean;\n hidden: boolean;\n};\n\n/** A single group entry inside an {@link AccountWalletMnemonicPayload}. */\nexport type AccountWalletMnemonicGroupEntry = {\n /** Stable group payload ID. Format: `<walletPayloadId>/<groupIndex>`. */\n id: AccountGroupPayloadId;\n /** BIP-44 account index this group was derived at. */\n groupIndex: number;\n metadata: AccountWalletGroupPayloadMetadata;\n};\n\n/**\n * Encoding formats for exported private key material.\n */\nexport const AccountWalletPrivateKeyEncoding = {\n Hexadecimal: 'hexadecimal',\n Base58: 'base58',\n Base32: 'base32',\n} as const;\n\nexport type AccountWalletPrivateKeyEncoding =\n (typeof AccountWalletPrivateKeyEncoding)[keyof typeof AccountWalletPrivateKeyEncoding];\n\n/** A single group entry inside an {@link AccountWalletPrivateKeyPayload}. */\nexport type AccountWalletPrivateKeyGroupEntry = {\n /** Stable group payload ID. Format: `wallet:private-key/<address>`. */\n id: AccountGroupPayloadId;\n /**\n * Private key material. Shape matches `ExportedAccount` from `@metamask/keyring-api/v2`\n * so the importer knows how to decode the key without additional out-of-band information.\n * Absent in metadata-only exports.\n */\n value?: {\n privateKey: EncodedBytes;\n encoding: AccountWalletPrivateKeyEncoding;\n /**\n * Account type from `KeyringAccountType` (e.g. `'eip155:eoa'`, `'bip122:p2wpkh'`).\n * Absent for EVM accounts -- import via `SimpleKeyring`.\n * Present for non-EVM accounts -- routing to the BIP-44 Snap handling this type is not yet implemented.\n */\n type?: KeyringAccount['type'];\n };\n metadata: AccountWalletGroupPayloadMetadata;\n};\n\n/** Payload entry for an HD (entropy) wallet and its derived account groups. */\nexport type AccountWalletMnemonicPayload = {\n id: AccountWalletPayloadId;\n type: typeof AccountWalletPayloadType.Mnemonic;\n /** BIP-39 mnemonic phrase encoded as bytes. Absent in metadata-only exports. */\n value?: EncodedBytes;\n metadata: AccountWalletPayloadMetadata;\n groups: AccountWalletMnemonicGroupEntry[];\n};\n\n/**\n * Payload entry for all imported private-key accounts.\n *\n * All local simple-keyring wallets are merged into this single entry;\n * each account is represented as a separate group entry keyed by address.\n */\nexport type AccountWalletPrivateKeyPayload = {\n id: AccountWalletPayloadId;\n type: typeof AccountWalletPayloadType.PrivateKey;\n metadata: AccountWalletPayloadMetadata;\n groups: AccountWalletPrivateKeyGroupEntry[];\n};\n\n/** Union of all wallet entry types that can appear in an {@link AccountTreePayload}. */\nexport type AccountTreeWalletEntry =\n | AccountWalletMnemonicPayload\n | AccountWalletPrivateKeyPayload;\n\n/** Portable snapshot of the full account tree state (flat versioned format). */\nexport type AccountTreePayload = {\n version: number;\n wallets: AccountTreeWalletEntry[];\n};\n\n/**\n * Deeply read-only wallet view passed to {@link AccountTreeSnapshot.filterWallets}\n * and {@link AccountTreeSnapshot.filterAllGroups} predicates.\n *\n * Entries are deep-cloned and deep-frozen when a snapshot is constructed, so\n * callers cannot mutate wallet IDs, types, secrets, metadata, or groups.\n */\nexport type AccountTreeSnapshotWallet = DeepReadonly<\n AccountWalletMnemonicPayload | AccountWalletPrivateKeyPayload\n>;\n\n/**\n * Deeply read-only group view passed to {@link AccountTreeSnapshot.filterGroups}\n * and {@link AccountTreeSnapshot.filterAllGroups} predicates.\n *\n * Entries are deep-cloned and deep-frozen when a snapshot is constructed, so\n * callers cannot mutate group IDs, secrets, metadata, or parent wallet references.\n */\nexport type AccountTreeSnapshotGroup = DeepReadonly<\n AccountWalletMnemonicGroupEntry | AccountWalletPrivateKeyGroupEntry\n>;\n\n/**\n * Constructs an {@link AccountWalletPayloadId} from an entropy source ID.\n *\n * @param entropySourceId - Stable entropy source ID returned by `HdKeyring.toEntropySourceId()`.\n * @returns The portable wallet payload ID.\n */\nexport function toWalletPayloadId(\n entropySourceId: string,\n): AccountWalletPayloadId {\n return `wallet:${entropySourceId}`;\n}\n\n/**\n * Constructs an {@link AccountGroupPayloadId} from a wallet payload ID and a sub-ID.\n *\n * @param walletId - The wallet payload ID this group belongs to.\n * @param subId - The group-specific sub-ID (e.g. group index for mnemonic wallets, address for private-key wallets).\n * @returns The portable group payload ID.\n */\nexport function toGroupPayloadId(\n walletId: AccountWalletPayloadId,\n subId: string | number,\n): AccountGroupPayloadId {\n return `${walletId}/${subId}`;\n}\n\n/** Options accepted by {@link AccountTreeController.exportState}. */\nexport type ExportStateOptions = {\n /** When `true`, secrets (mnemonic / private keys) are included. Requires the vault to be unlocked. */\n includeSecrets?: boolean;\n};\n\nconst AccountWalletPayloadIdStruct = define<AccountWalletPayloadId>(\n 'AccountWalletPayloadId',\n (value) =>\n typeof value === 'string' && value.startsWith('wallet:')\n ? true\n : 'Expected a wallet payload ID starting with \"wallet:\"',\n);\n\nconst AccountGroupPayloadIdStruct = define<AccountGroupPayloadId>(\n 'AccountGroupPayloadId',\n (value) =>\n typeof value === 'string' && PAYLOAD_GROUP_ID_REGEX.test(value)\n ? true\n : 'Expected a group payload ID in the form \"wallet:<id>/<subId>\"',\n);\n\nconst AccountWalletPayloadMetadataStruct = object({\n name: string(),\n});\n\nconst AccountWalletGroupPayloadMetadataStruct = object({\n name: string(),\n pinned: boolean(),\n hidden: boolean(),\n});\n\nconst AccountWalletPrivateKeyValueStruct = object({\n privateKey: sensitive(BytesStruct),\n encoding: enums(Object.values(AccountWalletPrivateKeyEncoding)),\n type: exactOptional(KeyringAccountTypeStruct),\n});\n\nconst AccountWalletMnemonicGroupEntryStruct = object({\n id: AccountGroupPayloadIdStruct,\n groupIndex: integer(),\n metadata: AccountWalletGroupPayloadMetadataStruct,\n});\n\nconst AccountWalletPrivateKeyGroupEntryStruct = object({\n id: AccountGroupPayloadIdStruct,\n value: exactOptional(AccountWalletPrivateKeyValueStruct),\n metadata: AccountWalletGroupPayloadMetadataStruct,\n});\n\n// The `groups` array in a mnemonic wallet payload must have contiguous group indices starting at 0.\nconst AccountWalletMnemonicGroupsStruct = refine(\n array(AccountWalletMnemonicGroupEntryStruct),\n 'contiguous-group-indices',\n (groups) => {\n let prev: AccountWalletMnemonicGroupEntry | undefined;\n for (const curr of groups) {\n if (prev === undefined && curr.groupIndex !== 0) {\n return `group indices must start at 0; got ${curr.groupIndex}`;\n }\n if (prev !== undefined && curr.groupIndex !== prev.groupIndex + 1) {\n return `group indices must be contiguous and sorted; found gap between index ${prev.groupIndex} and ${curr.groupIndex}`;\n }\n prev = curr;\n }\n return true;\n },\n);\n\nconst AccountWalletMnemonicPayloadStruct = object({\n id: AccountWalletPayloadIdStruct,\n type: literal(AccountWalletPayloadType.Mnemonic),\n value: exactOptional(sensitive(BytesStruct)),\n metadata: AccountWalletPayloadMetadataStruct,\n groups: AccountWalletMnemonicGroupsStruct,\n});\n\nconst AccountWalletPrivateKeyPayloadStruct = object({\n id: AccountWalletPayloadIdStruct,\n type: literal(AccountWalletPayloadType.PrivateKey),\n metadata: AccountWalletPayloadMetadataStruct,\n groups: array(AccountWalletPrivateKeyGroupEntryStruct),\n});\n\nconst AccountTreeWalletEntryStruct = union([\n AccountWalletMnemonicPayloadStruct,\n AccountWalletPrivateKeyPayloadStruct,\n]);\n\n/** Current version of the {@link AccountTreePayload} format. */\nexport const ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION = 1;\n\n/**\n * Superstruct schema for a fully versioned {@link AccountTreePayload}.\n *\n * Pins `version` to exactly {@link ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION} so\n * payloads from newer clients (v2+) are rejected rather than silently\n * mis-handled as v1. Once a migration framework is wired up in\n * {@link AccountTreeSnapshot.deserialize}, older versions will be up-migrated\n * before this struct is checked, and newer versions will require a new struct.\n *\n * Secret fields (`value`, `privateKey`) use the Superstruct `sensitive()`\n * wrapper so validation failures redact secrets from error output.\n */\nexport const AccountTreePayloadStruct = object({\n version: literal(ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION),\n wallets: array(AccountTreeWalletEntryStruct),\n});\n\n/** Inferred TypeScript type for a value matching {@link AccountTreePayloadStruct}. */\nexport type AccountTreePayloadStructType = Infer<\n typeof AccountTreePayloadStruct\n>;\n\n/**\n * Asserts that `value` conforms to the v1 {@link AccountTreePayload} schema.\n *\n * Prefer {@link AccountTreeSnapshot.deserialize} at transport boundaries so\n * validation stays paired with snapshot construction. Use this helper when you\n * already hold a parsed object and need to assert its shape before further\n * processing.\n *\n * @param value - Value to validate.\n * @throws If `value` is not a valid v1 payload, including unsupported wallet types.\n */\nexport function assertAccountTreePayload(\n value: unknown,\n): asserts value is AccountTreePayload {\n try {\n // AccountTreePayloadStruct pins version to ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n // so unknown future versions are rejected here rather than silently mis-handled.\n assert(value, AccountTreePayloadStruct);\n } catch (error) {\n if (error instanceof StructError) {\n throw new Error(\n `Invalid AccountTreePayload: ${formatValidationErrorMessages(error)}`,\n );\n }\n /* istanbul ignore next */\n throw error;\n }\n}\n"]}
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _AccountTreeSnapshot_entries, _AccountTreeSnapshot_idMap;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AccountTreeSnapshot = void 0;
16
+ const payload_js_1 = require("./payload.cjs");
17
+ const utils_js_1 = require("./utils.cjs");
18
+ /**
19
+ * Immutable value object returned by {@link AccountTreeController.exportState}.
20
+ *
21
+ * Construct with {@link AccountTreeController.exportState},
22
+ * {@link AccountTreeSnapshot.deserialize}, or `new AccountTreeSnapshot(...)`
23
+ * for tests and advanced use.
24
+ *
25
+ * Wallet and group entries are deep-cloned and deep-frozen once in the
26
+ * constructor. Filtering predicates receive those read-only views directly;
27
+ * each filter method returns a new snapshot that repeats the process for its
28
+ * result.
29
+ *
30
+ * An optional ID map (local ↔ payload) may be supplied when bridging between
31
+ * internal controller IDs and the stable cross-device IDs in the serialized
32
+ * payload. The map covers the original export and is preserved unchanged
33
+ * through filtering until {@link serialize}. Omit it when deterministic IDs
34
+ * make {@link toLocalId} / {@link toPayloadId} unnecessary.
35
+ */
36
+ class AccountTreeSnapshot {
37
+ /**
38
+ * @param entries - Wallet entries in the snapshot.
39
+ * @param idMap - Optional local ↔ payload ID map from export.
40
+ */
41
+ constructor(entries, idMap) {
42
+ _AccountTreeSnapshot_entries.set(this, void 0);
43
+ _AccountTreeSnapshot_idMap.set(this, void 0);
44
+ __classPrivateFieldSet(this, _AccountTreeSnapshot_entries, (0, utils_js_1.deepFreeze)(structuredClone(entries)), "f");
45
+ __classPrivateFieldSet(this, _AccountTreeSnapshot_idMap, idMap, "f");
46
+ }
47
+ /**
48
+ * Returns a new snapshot containing only the wallets for which
49
+ * `predicate` returns `true`.
50
+ *
51
+ * When filtering by wallet ID, compare against stable payload IDs from
52
+ * {@link serialize} or convert local IDs with {@link toPayloadId} first.
53
+ *
54
+ * @param predicate - Function called with each deeply read-only wallet entry.
55
+ * @returns A filtered snapshot.
56
+ */
57
+ filterWallets(predicate) {
58
+ const filteredEntries = __classPrivateFieldGet(this, _AccountTreeSnapshot_entries, "f").filter((entry) => predicate(entry));
59
+ return new AccountTreeSnapshot(filteredEntries, __classPrivateFieldGet(this, _AccountTreeSnapshot_idMap, "f"));
60
+ }
61
+ /**
62
+ * Filters groups within one wallet. Other wallets are left unchanged.
63
+ *
64
+ * Throws if `walletId` does not identify a wallet in the snapshot.
65
+ * Removes the wallet if no groups remain after filtering — this prevents a
66
+ * mnemonic wallet with zero selected groups from still transferring its secret.
67
+ *
68
+ * **Mnemonic wallets:** group indices must remain contiguous starting at 0
69
+ * after filtering, because the payload schema enforces this invariant.
70
+ * Predicates that produce gaps (e.g. keeping only index 1, or 0 and 2) will
71
+ * cause {@link AccountTreeSnapshot.deserialize} to reject the payload on the
72
+ * receiving end.
73
+ *
74
+ * @param walletId - Stable payload wallet ID to filter groups within.
75
+ * @param predicate - Function called with each deeply read-only group entry.
76
+ * @returns A filtered snapshot.
77
+ * @throws If `walletId` is not present in the snapshot.
78
+ */
79
+ filterGroups(walletId, predicate) {
80
+ const walletIndex = __classPrivateFieldGet(this, _AccountTreeSnapshot_entries, "f").findIndex((entry) => entry.id === walletId);
81
+ if (walletIndex === -1) {
82
+ throw new Error(`Cannot filter groups: wallet "${walletId}" not found in snapshot`);
83
+ }
84
+ const wallet = __classPrivateFieldGet(this, _AccountTreeSnapshot_entries, "f")[walletIndex];
85
+ const filteredGroups = wallet.groups.filter((group) => predicate(group));
86
+ const filteredEntries = [...__classPrivateFieldGet(this, _AccountTreeSnapshot_entries, "f")];
87
+ if (filteredGroups.length === 0) {
88
+ filteredEntries.splice(walletIndex, 1);
89
+ }
90
+ else if (wallet.type === payload_js_1.AccountWalletPayloadType.Mnemonic) {
91
+ filteredEntries[walletIndex] = {
92
+ ...wallet,
93
+ groups: filteredGroups,
94
+ };
95
+ }
96
+ else {
97
+ filteredEntries[walletIndex] = {
98
+ ...wallet,
99
+ groups: filteredGroups,
100
+ };
101
+ }
102
+ return new AccountTreeSnapshot(filteredEntries, __classPrivateFieldGet(this, _AccountTreeSnapshot_idMap, "f"));
103
+ }
104
+ /**
105
+ * Filters groups across every wallet.
106
+ *
107
+ * The parent wallet is provided as context to the predicate. Removes any
108
+ * wallet with no remaining groups after filtering.
109
+ *
110
+ * **Mnemonic wallets:** see {@link filterGroups} for the contiguous-index
111
+ * constraint that applies here as well.
112
+ *
113
+ * @param predicate - Function called with each group and its parent wallet.
114
+ * @returns A filtered snapshot.
115
+ */
116
+ filterAllGroups(predicate) {
117
+ const filteredEntries = [];
118
+ for (const wallet of __classPrivateFieldGet(this, _AccountTreeSnapshot_entries, "f")) {
119
+ const filteredGroups = wallet.groups.filter((group) => predicate(group, wallet));
120
+ if (filteredGroups.length === 0) {
121
+ continue;
122
+ }
123
+ if (wallet.type === payload_js_1.AccountWalletPayloadType.Mnemonic) {
124
+ filteredEntries.push({
125
+ ...wallet,
126
+ groups: filteredGroups,
127
+ });
128
+ }
129
+ else {
130
+ filteredEntries.push({
131
+ ...wallet,
132
+ groups: filteredGroups,
133
+ });
134
+ }
135
+ }
136
+ return new AccountTreeSnapshot(filteredEntries, __classPrivateFieldGet(this, _AccountTreeSnapshot_idMap, "f"));
137
+ }
138
+ /**
139
+ * Converts a payload ID (wallet or group) to the corresponding local
140
+ * `AccountTreeController` ID.
141
+ *
142
+ * The map reflects the original export, not the wallets/groups currently
143
+ * retained in this snapshot after filtering.
144
+ *
145
+ * @param payloadId - Stable cross-device wallet or group payload ID.
146
+ * @returns The local controller ID, or `undefined` if not found or no ID map is present.
147
+ */
148
+ toLocalId(payloadId) {
149
+ return __classPrivateFieldGet(this, _AccountTreeSnapshot_idMap, "f")?.getLocalId(payloadId);
150
+ }
151
+ /**
152
+ * Converts a local `AccountTreeController` ID (wallet or group) to its
153
+ * stable cross-device payload ID.
154
+ *
155
+ * The map reflects the original export, not the wallets/groups currently
156
+ * retained in this snapshot after filtering.
157
+ *
158
+ * @param localId - Local controller wallet or group ID.
159
+ * @returns The payload ID, or `undefined` if not found or no ID map is present.
160
+ */
161
+ toPayloadId(localId) {
162
+ return __classPrivateFieldGet(this, _AccountTreeSnapshot_idMap, "f")?.getPayloadId(localId);
163
+ }
164
+ /**
165
+ * Serializes the snapshot to a flat {@link AccountTreePayload} with `version` inlined
166
+ * alongside the wallet entries.
167
+ *
168
+ * Returns the constructor-frozen wallet tree without copying it again.
169
+ *
170
+ * @returns The versioned flat payload.
171
+ */
172
+ serialize() {
173
+ return {
174
+ version: payload_js_1.ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,
175
+ wallets: __classPrivateFieldGet(this, _AccountTreeSnapshot_entries, "f"),
176
+ };
177
+ }
178
+ /**
179
+ * Validates a raw value as an {@link AccountTreePayload}, running any
180
+ * necessary version migrations, and returns an immutable snapshot.
181
+ *
182
+ * This is the entry point for untrusted serialized data. Unsupported schema
183
+ * versions and wallet types fail closed with an error instead of returning a
184
+ * partial snapshot.
185
+ *
186
+ * The returned snapshot has no ID map — {@link toLocalId} / {@link toPayloadId}
187
+ * return `undefined`. Pass an {@link IdMap} to the constructor when you need
188
+ * the map.
189
+ *
190
+ * @param raw - Unknown value to parse.
191
+ * @returns A validated snapshot.
192
+ * @throws If `raw` is not a valid payload or its version is unsupported.
193
+ */
194
+ static async deserialize(raw) {
195
+ // TODO: Use migration framework here.
196
+ (0, payload_js_1.assertAccountTreePayload)(raw);
197
+ return new AccountTreeSnapshot(raw.wallets);
198
+ }
199
+ }
200
+ exports.AccountTreeSnapshot = AccountTreeSnapshot;
201
+ _AccountTreeSnapshot_entries = new WeakMap(), _AccountTreeSnapshot_idMap = new WeakMap();
202
+ //# sourceMappingURL=snapshot.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.cjs","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IdMap } from './id-map.js';\nimport type {\n AccountGroupPayloadId,\n AccountTreePayload,\n AccountTreeSnapshotGroup,\n AccountTreeSnapshotWallet,\n AccountTreeWalletEntry,\n AccountWalletMnemonicGroupEntry,\n AccountWalletPayloadId,\n AccountWalletPrivateKeyGroupEntry,\n} from './payload.js';\nimport {\n AccountWalletPayloadType,\n assertAccountTreePayload,\n ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n} from './payload.js';\nimport { deepFreeze } from './utils.js';\n\n/**\n * Immutable value object returned by {@link AccountTreeController.exportState}.\n *\n * Construct with {@link AccountTreeController.exportState},\n * {@link AccountTreeSnapshot.deserialize}, or `new AccountTreeSnapshot(...)`\n * for tests and advanced use.\n *\n * Wallet and group entries are deep-cloned and deep-frozen once in the\n * constructor. Filtering predicates receive those read-only views directly;\n * each filter method returns a new snapshot that repeats the process for its\n * result.\n *\n * An optional ID map (local ↔ payload) may be supplied when bridging between\n * internal controller IDs and the stable cross-device IDs in the serialized\n * payload. The map covers the original export and is preserved unchanged\n * through filtering until {@link serialize}. Omit it when deterministic IDs\n * make {@link toLocalId} / {@link toPayloadId} unnecessary.\n */\nexport class AccountTreeSnapshot {\n readonly #entries: AccountTreeWalletEntry[];\n\n readonly #idMap: IdMap | undefined;\n\n /**\n * @param entries - Wallet entries in the snapshot.\n * @param idMap - Optional local ↔ payload ID map from export.\n */\n constructor(entries: AccountTreeWalletEntry[], idMap?: IdMap) {\n this.#entries = deepFreeze(structuredClone(entries));\n this.#idMap = idMap;\n }\n\n /**\n * Returns a new snapshot containing only the wallets for which\n * `predicate` returns `true`.\n *\n * When filtering by wallet ID, compare against stable payload IDs from\n * {@link serialize} or convert local IDs with {@link toPayloadId} first.\n *\n * @param predicate - Function called with each deeply read-only wallet entry.\n * @returns A filtered snapshot.\n */\n filterWallets(\n predicate: (wallet: AccountTreeSnapshotWallet) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries = this.#entries.filter((entry) =>\n predicate(entry as AccountTreeSnapshotWallet),\n );\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups within one wallet. Other wallets are left unchanged.\n *\n * Throws if `walletId` does not identify a wallet in the snapshot.\n * Removes the wallet if no groups remain after filtering — this prevents a\n * mnemonic wallet with zero selected groups from still transferring its secret.\n *\n * **Mnemonic wallets:** group indices must remain contiguous starting at 0\n * after filtering, because the payload schema enforces this invariant.\n * Predicates that produce gaps (e.g. keeping only index 1, or 0 and 2) will\n * cause {@link AccountTreeSnapshot.deserialize} to reject the payload on the\n * receiving end.\n *\n * @param walletId - Stable payload wallet ID to filter groups within.\n * @param predicate - Function called with each deeply read-only group entry.\n * @returns A filtered snapshot.\n * @throws If `walletId` is not present in the snapshot.\n */\n filterGroups(\n walletId: AccountWalletPayloadId,\n predicate: (group: AccountTreeSnapshotGroup) => boolean,\n ): AccountTreeSnapshot {\n const walletIndex = this.#entries.findIndex(\n (entry) => entry.id === walletId,\n );\n if (walletIndex === -1) {\n throw new Error(\n `Cannot filter groups: wallet \"${walletId}\" not found in snapshot`,\n );\n }\n\n const wallet = this.#entries[walletIndex];\n\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(group as AccountTreeSnapshotGroup),\n );\n\n const filteredEntries = [...this.#entries];\n if (filteredGroups.length === 0) {\n filteredEntries.splice(walletIndex, 1);\n } else if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n };\n } else {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n };\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups across every wallet.\n *\n * The parent wallet is provided as context to the predicate. Removes any\n * wallet with no remaining groups after filtering.\n *\n * **Mnemonic wallets:** see {@link filterGroups} for the contiguous-index\n * constraint that applies here as well.\n *\n * @param predicate - Function called with each group and its parent wallet.\n * @returns A filtered snapshot.\n */\n filterAllGroups(\n predicate: (\n group: AccountTreeSnapshotGroup,\n wallet: AccountTreeSnapshotWallet,\n ) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries: AccountTreeWalletEntry[] = [];\n\n for (const wallet of this.#entries) {\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(\n group as AccountTreeSnapshotGroup,\n wallet as AccountTreeSnapshotWallet,\n ),\n );\n\n if (filteredGroups.length === 0) {\n continue;\n }\n\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n });\n } else {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n });\n }\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Converts a payload ID (wallet or group) to the corresponding local\n * `AccountTreeController` ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param payloadId - Stable cross-device wallet or group payload ID.\n * @returns The local controller ID, or `undefined` if not found or no ID map is present.\n */\n toLocalId(\n payloadId: AccountWalletPayloadId | AccountGroupPayloadId,\n ): ReturnType<IdMap['getLocalId']> {\n return this.#idMap?.getLocalId(payloadId);\n }\n\n /**\n * Converts a local `AccountTreeController` ID (wallet or group) to its\n * stable cross-device payload ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param localId - Local controller wallet or group ID.\n * @returns The payload ID, or `undefined` if not found or no ID map is present.\n */\n toPayloadId(\n localId: Parameters<IdMap['add']>[0],\n ): ReturnType<IdMap['getPayloadId']> {\n return this.#idMap?.getPayloadId(localId);\n }\n\n /**\n * Serializes the snapshot to a flat {@link AccountTreePayload} with `version` inlined\n * alongside the wallet entries.\n *\n * Returns the constructor-frozen wallet tree without copying it again.\n *\n * @returns The versioned flat payload.\n */\n serialize(): AccountTreePayload {\n return {\n version: ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n wallets: this.#entries,\n };\n }\n\n /**\n * Validates a raw value as an {@link AccountTreePayload}, running any\n * necessary version migrations, and returns an immutable snapshot.\n *\n * This is the entry point for untrusted serialized data. Unsupported schema\n * versions and wallet types fail closed with an error instead of returning a\n * partial snapshot.\n *\n * The returned snapshot has no ID map — {@link toLocalId} / {@link toPayloadId}\n * return `undefined`. Pass an {@link IdMap} to the constructor when you need\n * the map.\n *\n * @param raw - Unknown value to parse.\n * @returns A validated snapshot.\n * @throws If `raw` is not a valid payload or its version is unsupported.\n */\n static async deserialize(raw: unknown): Promise<AccountTreeSnapshot> {\n // TODO: Use migration framework here.\n assertAccountTreePayload(raw);\n return new AccountTreeSnapshot(raw.wallets);\n }\n}\n"]}
@@ -0,0 +1,120 @@
1
+ import type { IdMap } from "./id-map.cjs";
2
+ import type { AccountGroupPayloadId, AccountTreePayload, AccountTreeSnapshotGroup, AccountTreeSnapshotWallet, AccountTreeWalletEntry, AccountWalletPayloadId } from "./payload.cjs";
3
+ /**
4
+ * Immutable value object returned by {@link AccountTreeController.exportState}.
5
+ *
6
+ * Construct with {@link AccountTreeController.exportState},
7
+ * {@link AccountTreeSnapshot.deserialize}, or `new AccountTreeSnapshot(...)`
8
+ * for tests and advanced use.
9
+ *
10
+ * Wallet and group entries are deep-cloned and deep-frozen once in the
11
+ * constructor. Filtering predicates receive those read-only views directly;
12
+ * each filter method returns a new snapshot that repeats the process for its
13
+ * result.
14
+ *
15
+ * An optional ID map (local ↔ payload) may be supplied when bridging between
16
+ * internal controller IDs and the stable cross-device IDs in the serialized
17
+ * payload. The map covers the original export and is preserved unchanged
18
+ * through filtering until {@link serialize}. Omit it when deterministic IDs
19
+ * make {@link toLocalId} / {@link toPayloadId} unnecessary.
20
+ */
21
+ export declare class AccountTreeSnapshot {
22
+ #private;
23
+ /**
24
+ * @param entries - Wallet entries in the snapshot.
25
+ * @param idMap - Optional local ↔ payload ID map from export.
26
+ */
27
+ constructor(entries: AccountTreeWalletEntry[], idMap?: IdMap);
28
+ /**
29
+ * Returns a new snapshot containing only the wallets for which
30
+ * `predicate` returns `true`.
31
+ *
32
+ * When filtering by wallet ID, compare against stable payload IDs from
33
+ * {@link serialize} or convert local IDs with {@link toPayloadId} first.
34
+ *
35
+ * @param predicate - Function called with each deeply read-only wallet entry.
36
+ * @returns A filtered snapshot.
37
+ */
38
+ filterWallets(predicate: (wallet: AccountTreeSnapshotWallet) => boolean): AccountTreeSnapshot;
39
+ /**
40
+ * Filters groups within one wallet. Other wallets are left unchanged.
41
+ *
42
+ * Throws if `walletId` does not identify a wallet in the snapshot.
43
+ * Removes the wallet if no groups remain after filtering — this prevents a
44
+ * mnemonic wallet with zero selected groups from still transferring its secret.
45
+ *
46
+ * **Mnemonic wallets:** group indices must remain contiguous starting at 0
47
+ * after filtering, because the payload schema enforces this invariant.
48
+ * Predicates that produce gaps (e.g. keeping only index 1, or 0 and 2) will
49
+ * cause {@link AccountTreeSnapshot.deserialize} to reject the payload on the
50
+ * receiving end.
51
+ *
52
+ * @param walletId - Stable payload wallet ID to filter groups within.
53
+ * @param predicate - Function called with each deeply read-only group entry.
54
+ * @returns A filtered snapshot.
55
+ * @throws If `walletId` is not present in the snapshot.
56
+ */
57
+ filterGroups(walletId: AccountWalletPayloadId, predicate: (group: AccountTreeSnapshotGroup) => boolean): AccountTreeSnapshot;
58
+ /**
59
+ * Filters groups across every wallet.
60
+ *
61
+ * The parent wallet is provided as context to the predicate. Removes any
62
+ * wallet with no remaining groups after filtering.
63
+ *
64
+ * **Mnemonic wallets:** see {@link filterGroups} for the contiguous-index
65
+ * constraint that applies here as well.
66
+ *
67
+ * @param predicate - Function called with each group and its parent wallet.
68
+ * @returns A filtered snapshot.
69
+ */
70
+ filterAllGroups(predicate: (group: AccountTreeSnapshotGroup, wallet: AccountTreeSnapshotWallet) => boolean): AccountTreeSnapshot;
71
+ /**
72
+ * Converts a payload ID (wallet or group) to the corresponding local
73
+ * `AccountTreeController` ID.
74
+ *
75
+ * The map reflects the original export, not the wallets/groups currently
76
+ * retained in this snapshot after filtering.
77
+ *
78
+ * @param payloadId - Stable cross-device wallet or group payload ID.
79
+ * @returns The local controller ID, or `undefined` if not found or no ID map is present.
80
+ */
81
+ toLocalId(payloadId: AccountWalletPayloadId | AccountGroupPayloadId): ReturnType<IdMap['getLocalId']>;
82
+ /**
83
+ * Converts a local `AccountTreeController` ID (wallet or group) to its
84
+ * stable cross-device payload ID.
85
+ *
86
+ * The map reflects the original export, not the wallets/groups currently
87
+ * retained in this snapshot after filtering.
88
+ *
89
+ * @param localId - Local controller wallet or group ID.
90
+ * @returns The payload ID, or `undefined` if not found or no ID map is present.
91
+ */
92
+ toPayloadId(localId: Parameters<IdMap['add']>[0]): ReturnType<IdMap['getPayloadId']>;
93
+ /**
94
+ * Serializes the snapshot to a flat {@link AccountTreePayload} with `version` inlined
95
+ * alongside the wallet entries.
96
+ *
97
+ * Returns the constructor-frozen wallet tree without copying it again.
98
+ *
99
+ * @returns The versioned flat payload.
100
+ */
101
+ serialize(): AccountTreePayload;
102
+ /**
103
+ * Validates a raw value as an {@link AccountTreePayload}, running any
104
+ * necessary version migrations, and returns an immutable snapshot.
105
+ *
106
+ * This is the entry point for untrusted serialized data. Unsupported schema
107
+ * versions and wallet types fail closed with an error instead of returning a
108
+ * partial snapshot.
109
+ *
110
+ * The returned snapshot has no ID map — {@link toLocalId} / {@link toPayloadId}
111
+ * return `undefined`. Pass an {@link IdMap} to the constructor when you need
112
+ * the map.
113
+ *
114
+ * @param raw - Unknown value to parse.
115
+ * @returns A validated snapshot.
116
+ * @throws If `raw` is not a valid payload or its version is unsupported.
117
+ */
118
+ static deserialize(raw: unknown): Promise<AccountTreeSnapshot>;
119
+ }
120
+ //# sourceMappingURL=snapshot.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.d.cts","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAoB;AACzC,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EAEtB,sBAAsB,EAEvB,sBAAqB;AAQtB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,mBAAmB;;IAK9B;;;OAGG;gBACS,OAAO,EAAE,sBAAsB,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK;IAK5D;;;;;;;;;OASG;IACH,aAAa,CACX,SAAS,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,OAAO,GACxD,mBAAmB;IAQtB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CACV,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,OAAO,GACtD,mBAAmB;IAkCtB;;;;;;;;;;;OAWG;IACH,eAAe,CACb,SAAS,EAAE,CACT,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,yBAAyB,KAC9B,OAAO,GACX,mBAAmB;IA+BtB;;;;;;;;;OASG;IACH,SAAS,CACP,SAAS,EAAE,sBAAsB,GAAG,qBAAqB,GACxD,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAIlC;;;;;;;;;OASG;IACH,WAAW,CACT,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GACnC,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAIpC;;;;;;;OAOG;IACH,SAAS,IAAI,kBAAkB;IAO/B;;;;;;;;;;;;;;;OAeG;WACU,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAKrE"}