@vaultkeepr/smart-account 0.1.1 → 0.2.0

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.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @vaultkeepr/smart-account
2
+
3
+ ERC-4337 account abstraction for VaultKeepR: Kernel smart account with Pimlico paymaster. Universal module for Web, Extension, iOS, and Android.
4
+
5
+ Identity can be derived from the master password (on-device, no seed phrase exposure) or from an existing signer.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @vaultkeepr/smart-account
11
+ ```
12
+
13
+ Requires `@vaultkeepr/core` and `@vaultkeepr/logger`.
14
+
15
+ > This package ships as TypeScript source (`main`/`exports` point at `src/index.ts`) — consume it through your bundler.
16
+
17
+ ## Usage
18
+
19
+ ```ts
20
+ import {
21
+ getIdentityAddress,
22
+ initIdentityFromPassword,
23
+ isIdentityConnected,
24
+ } from "@vaultkeepr/smart-account";
25
+
26
+ await initIdentityFromPassword(password, salt);
27
+ if (isIdentityConnected()) {
28
+ const address = getIdentityAddress(); // smart account address
29
+ }
30
+ ```
31
+
32
+ Alternative: `initIdentityFromSigner(signer)` when a wallet is already connected; `clearIdentity()` wipes in-memory state.
33
+
34
+ Modules: `identity.ts` (identity lifecycle), `kernel.ts` (Kernel account), `owner.ts` + `secretKey.ts` (signer derivation).
35
+
36
+ ## Security notes
37
+
38
+ The chain stores location pointers only — never keys. Signatures for on-chain writes go through the user's smart account.
39
+
40
+ Repository: [VaultKeepR/vaultkeepr-public](https://github.com/VaultKeepR/vaultkeepr-public) · Site: [vaultkeepr.xyz](https://vaultkeepr.xyz)
41
+
42
+ MIT licensed.
package/dist/index.d.mts CHANGED
@@ -19,124 +19,59 @@ declare function getCachedSmartAccountAddress(): Address | null;
19
19
  declare function getCachedSmartAccountMode(): VaultSmartAccount["mode"] | null;
20
20
  declare function clearSmartAccountCache(): void;
21
21
 
22
- declare function getOwnerFromPassword(password: string, secretKey?: string): Promise<LocalAccount>;
22
+ type VaultIdentityMode = "password" | "passkey" | "biometric";
23
+ interface VaultIdentity {
24
+ ownerAddress: Address;
25
+ aaAddress: Address;
26
+ mode: VaultIdentityMode;
27
+ }
28
+ declare function deriveIdentity(ownerAccount: LocalAccount, mode: VaultIdentityMode): Promise<VaultIdentity>;
29
+ interface VerifyAAAuthParams {
30
+ signature: string;
31
+ message: string;
32
+ claimedOwner?: Address;
33
+ claimedAA?: Address | null;
34
+ timestamp: number;
35
+ }
36
+ interface VerifyAAAuthResult {
37
+ ok: boolean;
38
+ ownerAddress: Address;
39
+ aaAddress: Address;
40
+ }
41
+ declare function verifyAAAuth(params: VerifyAAAuthParams): Promise<VerifyAAAuthResult>;
23
42
 
24
- declare const CID_REGISTRY_ABI: readonly [{
25
- readonly name: "setCid";
26
- readonly type: "function";
27
- readonly stateMutability: "nonpayable";
28
- readonly inputs: readonly [{
29
- readonly name: "cid";
30
- readonly type: "string";
31
- }];
32
- readonly outputs: readonly [];
33
- }, {
34
- readonly name: "deleteCid";
35
- readonly type: "function";
36
- readonly stateMutability: "nonpayable";
37
- readonly inputs: readonly [];
38
- readonly outputs: readonly [];
39
- }, {
40
- readonly name: "getCid";
41
- readonly type: "function";
42
- readonly stateMutability: "view";
43
- readonly inputs: readonly [{
44
- readonly name: "account";
45
- readonly type: "address";
46
- }];
47
- readonly outputs: readonly [{
48
- readonly name: "";
49
- readonly type: "string";
50
- }];
51
- }, {
52
- readonly name: "hasCid";
53
- readonly type: "function";
54
- readonly stateMutability: "view";
55
- readonly inputs: readonly [{
56
- readonly name: "account";
57
- readonly type: "address";
58
- }];
59
- readonly outputs: readonly [{
60
- readonly name: "";
61
- readonly type: "bool";
62
- }];
63
- }, {
64
- readonly name: "getUpdatedAt";
65
- readonly type: "function";
66
- readonly stateMutability: "view";
67
- readonly inputs: readonly [{
68
- readonly name: "account";
69
- readonly type: "address";
70
- }];
71
- readonly outputs: readonly [{
72
- readonly name: "";
73
- readonly type: "uint256";
74
- }];
75
- }, {
76
- readonly name: "nextAllowedUpdate";
77
- readonly type: "function";
78
- readonly stateMutability: "view";
79
- readonly inputs: readonly [{
80
- readonly name: "account";
81
- readonly type: "address";
82
- }];
83
- readonly outputs: readonly [{
84
- readonly name: "";
85
- readonly type: "uint256";
86
- }];
87
- }, {
88
- readonly name: "cooldownSeconds";
89
- readonly type: "function";
90
- readonly stateMutability: "view";
91
- readonly inputs: readonly [];
92
- readonly outputs: readonly [{
93
- readonly name: "";
94
- readonly type: "uint256";
95
- }];
96
- }];
97
- declare function isCidRegistryConfigured(): boolean;
98
- declare function publishCidOnChain(smartAccount: VaultSmartAccount, cid: string): Promise<`0x${string}` | null>;
99
- declare function readCidOnChain(smartAccountAddress: Address): Promise<string | null>;
43
+ declare function getOwnerFromPassword(password: string, secretKey?: string): Promise<LocalAccount>;
100
44
 
101
45
  declare function getPimlicoApiKey(): string;
102
46
  declare function isSmartAccountConfigured(): boolean;
103
47
  declare function getTargetChain(): Chain;
104
48
 
105
- declare function scheduleCidOnChainSync(smartAccount: VaultSmartAccount | null, cid: string, debounceMs?: number): void;
106
- declare function flushCidOnChainSync(): Promise<boolean>;
107
- declare function getOnChainSyncStatus(): {
108
- hasPending: boolean;
109
- pendingCid: string | null;
110
- lastPublishedCid: string | null;
111
- lastPublishTime: number;
112
- isPublishing: boolean;
113
- };
114
- declare function resetOnChainSync(): void;
115
-
116
- interface IdentityState {
49
+ interface IdentityState$1 {
117
50
  owner: LocalAccount | null;
118
51
  smartAccount: VaultSmartAccount | null;
119
52
  mode: "password" | "passkey" | "biometric" | null;
120
53
  initialized: boolean;
121
54
  }
122
- type IdentityListener = (state: IdentityState) => void;
55
+ type IdentityListener = (state: IdentityState$1) => void;
123
56
  declare function initIdentityFromPassword(password: string, secretKey?: string): Promise<{
124
57
  address: Address;
125
58
  smartAccountAddress: Address | null;
59
+ aaAddress: Address | null;
126
60
  }>;
127
61
  declare function initIdentityFromSigner(signer: LocalAccount, mode?: "passkey" | "biometric"): Promise<{
128
62
  address: Address;
129
63
  smartAccountAddress: Address | null;
64
+ aaAddress: Address | null;
130
65
  }>;
131
66
  declare function clearIdentity(): void;
132
67
  declare function getIdentityAddress(): Address | null;
133
68
  declare function isIdentityConnected(): boolean;
134
69
  declare function getIdentityOwner(): LocalAccount | null;
135
70
  declare function getIdentitySmartAccount(): VaultSmartAccount | null;
136
- declare function getIdentityMode(): IdentityState["mode"];
71
+ declare function getIdentityMode(): IdentityState$1["mode"];
137
72
  declare function signMessageWithIdentity(message: string): Promise<`0x${string}`>;
138
73
  declare function subscribeIdentity(listener: IdentityListener): () => void;
139
- declare function getIdentitySnapshot(): IdentityState;
74
+ declare function getIdentitySnapshot(): IdentityState$1;
140
75
 
141
76
  interface UseIdentityReturn {
142
77
  address: Address | null;
@@ -145,9 +80,7 @@ interface UseIdentityReturn {
145
80
  smartAccountAddress: Address | null;
146
81
  signMessage: (message: string) => Promise<`0x${string}`>;
147
82
  disconnect: () => void;
148
- notifyOnChainSync: (cid: string) => void;
149
83
  cleanupOnLock: () => Promise<void>;
150
- flushOnChainSync: () => Promise<void>;
151
84
  }
152
85
  declare function useIdentity(): UseIdentityReturn;
153
86
 
@@ -156,4 +89,170 @@ declare function isValidSecretKey(key: string): boolean;
156
89
  declare function formatSecretKeyForDisplay(key: string): string;
157
90
  declare function parseSecretKeyInput(input: string): string;
158
91
 
159
- export { CID_REGISTRY_ABI, type CreateSmartAccountParams, type UseIdentityReturn, type VaultSmartAccount, clearIdentity, clearSmartAccountCache, computeSmartAccountAddress, createVaultSmartAccount, flushCidOnChainSync, formatSecretKeyForDisplay, generateSecretKey, getCachedSmartAccountAddress, getCachedSmartAccountMode, getIdentityAddress, getIdentityMode, getIdentityOwner, getIdentitySmartAccount, getIdentitySnapshot, getOnChainSyncStatus, getOwnerFromPassword, getPimlicoApiKey, getTargetChain, initIdentityFromPassword, initIdentityFromSigner, isCidRegistryConfigured, isIdentityConnected, isSmartAccountConfigured, isValidSecretKey, parseSecretKeyInput, publishCidOnChain, readCidOnChain, resetOnChainSync, scheduleCidOnChainSync, signMessageWithIdentity, subscribeIdentity, useIdentity };
92
+ /**
93
+ * Accounts without a secret key, and their one-time passage to a key.
94
+ *
95
+ * Rule: one secret key per account. A key-less account has the password identity as owner.
96
+ * Passing it to a key is a one-time move, in this order:
97
+ * 1. create the key as a pending key (this device keeps its identity);
98
+ * 2. copy the pointer under the key's Smart Account;
99
+ * 3. mark the legacy pointer as moved to that Smart Account;
100
+ * 4. activate the pending key on this device.
101
+ * Nothing is deleted. Devices that do not hold the key become read-only.
102
+ *
103
+ * A failure stops the sequence. A retry reuses the same pending key. A device never uses the
104
+ * new key before the legacy pointer has moved to its Smart Account.
105
+ *
106
+ * Pure logic. Network and storage are injected, so the order and the failure behaviour are
107
+ * tested without a device.
108
+ */
109
+ /** Server view of the legacy (password identity) pointer. */
110
+ type PointerState = "none" | "active" | "moved";
111
+ type IdentityState = {
112
+ kind: "ready";
113
+ } | {
114
+ kind: "pass_to_key";
115
+ } | {
116
+ kind: "pair_required";
117
+ } | {
118
+ kind: "join_or_create";
119
+ };
120
+ declare function identityState(input: {
121
+ hasSecretKey: boolean;
122
+ pointer: PointerState;
123
+ }): IdentityState;
124
+ /**
125
+ * Writes are allowed while the account has not moved: a key-less account keeps writing to its
126
+ * legacy pointer until its passage ("nothing changes"). A moved pointer, or no pointer and no
127
+ * key, cannot be written from this device.
128
+ */
129
+ declare function canWritePointer(state: IdentityState): boolean;
130
+ interface LegacyPointer {
131
+ cid: string | null;
132
+ moved: boolean;
133
+ /** Smart Account the pointer moved to, when moved. */
134
+ movedTo?: string;
135
+ }
136
+ interface PassageIO {
137
+ /** Reads the legacy (password identity) pointer. */
138
+ readLegacyPointer(): Promise<LegacyPointer>;
139
+ /** The pending key left by a passage on this device, or null. Never creates a key. */
140
+ peekPendingKey(): Promise<string | null>;
141
+ /** The pending key: created and kept in pending storage if absent. Same key on every retry. Does not change the identity. */
142
+ getOrCreatePendingKey(): Promise<string>;
143
+ /** Smart Account of the key's owner. */
144
+ smartAccountOf(secretKey: string): Promise<string>;
145
+ /** Writes the copy under the Smart Account, signed with the pending key. Idempotent. */
146
+ writeSmartAccountPointer(smartAccount: string, cid: string): Promise<void>;
147
+ /** Marks the legacy pointer as moved to the Smart Account, signed with the legacy owner. Idempotent for the same target. */
148
+ markLegacyMoved(smartAccount: string): Promise<void>;
149
+ /** Makes the pending key this device's account key. Called only once the legacy pointer has moved to its Smart Account. */
150
+ activatePendingKey(): Promise<void>;
151
+ /** Removes the pending key. Used when the account has moved to another key. */
152
+ discardPendingKey(): Promise<void>;
153
+ }
154
+ type PassageResult = {
155
+ status: "moved";
156
+ smartAccount: string;
157
+ } | {
158
+ status: "nothing_to_pass";
159
+ } | {
160
+ status: "already_moved";
161
+ } | {
162
+ status: "already_keyed";
163
+ };
164
+ declare function runPassage(io: PassageIO, opts: {
165
+ hasSecretKey: boolean;
166
+ }): Promise<PassageResult>;
167
+
168
+ /**
169
+ * Client side of the passage to a key: the requests to the server, with the signatures and the
170
+ * storage supplied by each app. The order and the failure rules are in keyPassage.ts.
171
+ *
172
+ * Off in production: PASSAGE_ENABLED stays false until the server move route and the Smart
173
+ * Account namespace are deployed.
174
+ */
175
+
176
+ /** Off until the server side (move route, Smart Account namespace) is deployed. */
177
+ declare const PASSAGE_ENABLED = false;
178
+ interface PassageEnv {
179
+ /** API origin, without trailing slash. */
180
+ apiBase: string;
181
+ fetch: typeof fetch;
182
+ /** EOA of the password identity (the legacy owner). */
183
+ legacyAddress: string;
184
+ /** Signs a message with the legacy owner. */
185
+ signLegacy(message: string): Promise<string>;
186
+ /** Signs a message with the owner of a secret key. */
187
+ signWithKey(secretKey: string, message: string): Promise<string>;
188
+ /** Smart Account of a secret key's owner. */
189
+ smartAccountOf(secretKey: string): Promise<string>;
190
+ /** The autosave delegation message the server verifies (wallet-messages). */
191
+ formatDelegation(sessionId: string, expiryTimestamp: number): string;
192
+ /** A new random secret key (client RNG). */
193
+ newSecretKey(): string;
194
+ /** A new random session id. */
195
+ newSessionId(): string;
196
+ /** Current time, in milliseconds. */
197
+ now(): number;
198
+ /** Pending key storage: encrypted at rest, never logged. */
199
+ pending: {
200
+ read(): Promise<string | null>;
201
+ write(secretKey: string): Promise<void>;
202
+ clear(): Promise<void>;
203
+ };
204
+ /** Stores the committed account key and updates this device's identity. */
205
+ activate(secretKey: string): Promise<void>;
206
+ }
207
+ declare function createPassageIO(env: PassageEnv): PassageIO;
208
+ /** The account's state on this device, for the UI. Changes nothing. */
209
+ declare function readPassageState(env: PassageEnv, hasSecretKey: boolean): Promise<IdentityState>;
210
+ /** Runs the one-time passage. On a failed step it throws, and the pending key is kept for a retry. */
211
+ declare function runPassageWith(env: PassageEnv, hasSecretKey: boolean): Promise<PassageResult>;
212
+
213
+ /**
214
+ * Migration of an account that already has a secret key: its pointer moves from the legacy
215
+ * namespace (the owner EOA of the key) to the Smart Account namespace (SA of that EOA).
216
+ *
217
+ * Steps, with the device key as the owner signer (same routes as the one-time passage):
218
+ * 1. read the legacy pointer of the owner EOA;
219
+ * 2. if the SA has no pointer, copy the legacy CID under the SA (the server infers the SA namespace
220
+ * from the owner signature);
221
+ * 3. mark the legacy pointer moved to the SA (the server refuses it without the SA copy);
222
+ * 4. after the move the legacy pointer is frozen: read it again and make the SA equal to it.
223
+ * This closes the race where an old client wrote the legacy pointer between steps 1 and 3.
224
+ *
225
+ * Nothing is deleted. A failed step throws; the next unlock runs the same steps again.
226
+ * A conflict (the SA already holds a different CID) changes nothing: the device keeps the legacy pointer.
227
+ *
228
+ * Off until the server side is deployed: KEYED_MIGRATION_ENABLED stays false.
229
+ */
230
+ /** Off until the Smart Account namespace and the move route are deployed. */
231
+ declare const KEYED_MIGRATION_ENABLED = false;
232
+ interface KeyedMigrationEnv {
233
+ apiBase: string;
234
+ fetch: typeof fetch;
235
+ /** Owner EOA derived from the device secret key. The legacy pointer lives under it. */
236
+ ownerAddress: string;
237
+ /** Smart Account of the owner EOA, computed on the device. Never taken from the server. */
238
+ smartAccount: string;
239
+ /** Signs with the device key (the owner EOA). */
240
+ signWithKey(message: string): Promise<string>;
241
+ formatDelegation(sessionId: string, expiryTimestamp: number): string;
242
+ newSessionId(): string;
243
+ now(): number;
244
+ }
245
+ /**
246
+ * - moved: the legacy pointer is now moved to the SA, and the SA holds its CID.
247
+ * - already_moved: a previous run (or another device) already moved it to this SA.
248
+ * - no_pointer: no legacy pointer. Nothing to move: the device keeps the legacy namespace.
249
+ * - conflict: the SA holds a different CID. Nothing changed; to be reported, not resolved here.
250
+ */
251
+ type KeyedMigrationStatus = "moved" | "already_moved" | "no_pointer" | "conflict";
252
+ /**
253
+ * Runs the migration for the device's account. Throws on any failed step: the caller keeps the legacy
254
+ * namespace and retries at the next unlock. Switches the device to the SA only on moved / already_moved.
255
+ */
256
+ declare function migrateKeyedPointer(env: KeyedMigrationEnv): Promise<KeyedMigrationStatus>;
257
+
258
+ export { type CreateSmartAccountParams, type IdentityState, KEYED_MIGRATION_ENABLED, type KeyedMigrationEnv, type KeyedMigrationStatus, type LegacyPointer, PASSAGE_ENABLED, type PassageEnv, type PassageIO, type PassageResult, type PointerState, type UseIdentityReturn, type VaultIdentity, type VaultIdentityMode, type VaultSmartAccount, type VerifyAAAuthParams, type VerifyAAAuthResult, canWritePointer, clearIdentity, clearSmartAccountCache, computeSmartAccountAddress, createPassageIO, createVaultSmartAccount, deriveIdentity, formatSecretKeyForDisplay, generateSecretKey, getCachedSmartAccountAddress, getCachedSmartAccountMode, getIdentityAddress, getIdentityMode, getIdentityOwner, getIdentitySmartAccount, getIdentitySnapshot, getOwnerFromPassword, getPimlicoApiKey, getTargetChain, identityState, initIdentityFromPassword, initIdentityFromSigner, isIdentityConnected, isSmartAccountConfigured, isValidSecretKey, migrateKeyedPointer, parseSecretKeyInput, readPassageState, runPassage, runPassageWith, signMessageWithIdentity, subscribeIdentity, useIdentity, verifyAAAuth };