@thru/indexed-db-stamper 0.0.4

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.
@@ -0,0 +1,182 @@
1
+ import { EncryptedData } from '@thru/crypto';
2
+ import { AddressType, AppMetadata, ConnectedApp } from '@thru/chain-interfaces';
3
+
4
+ /**
5
+ * IndexedDB storage for wallet data
6
+ */
7
+ declare class WalletStorage {
8
+ private static dbPromise;
9
+ /**
10
+ * Get or create database connection
11
+ */
12
+ private static getDB;
13
+ /**
14
+ * Save encrypted seed to storage
15
+ * @param encryptedSeed - Encrypted seed data
16
+ * @param walletId - Optional wallet ID (defaults to 'default')
17
+ */
18
+ static saveEncryptedSeed(encryptedSeed: EncryptedData, walletId?: string): Promise<void>;
19
+ /**
20
+ * Get encrypted seed from storage
21
+ * @param walletId - Wallet ID (defaults to 'default')
22
+ * @returns Encrypted seed data or null if not found
23
+ */
24
+ static getEncryptedSeed(walletId?: string): Promise<EncryptedData | null>;
25
+ /**
26
+ * Check if wallet exists
27
+ * @param walletId - Wallet ID (defaults to 'default')
28
+ */
29
+ static walletExists(walletId?: string): Promise<boolean>;
30
+ /**
31
+ * Delete wallet from storage
32
+ * @param walletId - Wallet ID (defaults to 'default')
33
+ */
34
+ static deleteWallet(walletId?: string): Promise<void>;
35
+ /**
36
+ * Clear all wallet data (use with caution!)
37
+ */
38
+ static clearAll(): Promise<void>;
39
+ /**
40
+ * Get storage quota information
41
+ */
42
+ static getStorageInfo(): Promise<{
43
+ usage: number;
44
+ quota: number;
45
+ available: number;
46
+ } | null>;
47
+ /**
48
+ * Request persistent storage (prevents data from being cleared)
49
+ */
50
+ static requestPersistentStorage(): Promise<boolean>;
51
+ /**
52
+ * Check if storage is persistent
53
+ */
54
+ static isPersistent(): Promise<boolean>;
55
+ }
56
+
57
+ /**
58
+ * Stored account representation in IndexedDB
59
+ */
60
+ interface StoredAccount {
61
+ index: number;
62
+ label: string;
63
+ publicKey: string;
64
+ path: string;
65
+ createdAt: Date;
66
+ addressType?: AddressType;
67
+ publicKeyRawBase64?: string;
68
+ }
69
+
70
+ /**
71
+ * Account storage management using IndexedDB
72
+ * Stores metadata for each derived account (but NOT private keys)
73
+ */
74
+ declare class AccountStorage {
75
+ private static dbPromise;
76
+ /**
77
+ * Initialize or get existing database connection
78
+ */
79
+ private static getDB;
80
+ /**
81
+ * Save a new account to storage
82
+ * @param account - Account metadata to save
83
+ */
84
+ static saveAccount(account: StoredAccount): Promise<void>;
85
+ /**
86
+ * Get all accounts, sorted by index (ascending)
87
+ * @returns Array of stored accounts
88
+ */
89
+ static getAccounts(): Promise<StoredAccount[]>;
90
+ /**
91
+ * Get a specific account by index
92
+ * @param index - Account index
93
+ * @returns Account or null if not found
94
+ */
95
+ static getAccount(index: number): Promise<StoredAccount | null>;
96
+ /**
97
+ * Update an account's label
98
+ * @param index - Account index
99
+ * @param label - New label
100
+ */
101
+ static updateAccountLabel(index: number, label: string): Promise<void>;
102
+ /**
103
+ * Get the next available account index
104
+ * @returns Next index (0 if no accounts exist)
105
+ */
106
+ static getNextAccountIndex(): Promise<number>;
107
+ /**
108
+ * Check if any accounts exist
109
+ * @returns true if at least one account exists
110
+ */
111
+ static hasAccounts(): Promise<boolean>;
112
+ /**
113
+ * Get total number of accounts
114
+ * @returns Account count
115
+ */
116
+ static getAccountCount(): Promise<number>;
117
+ /**
118
+ * Clear all accounts (use with caution!)
119
+ * Used primarily for testing or wallet reset
120
+ */
121
+ static clearAccounts(): Promise<void>;
122
+ }
123
+
124
+ interface ConnectedAppUpsert {
125
+ accountId: number;
126
+ appId: string;
127
+ origin: string;
128
+ metadata: AppMetadata;
129
+ }
130
+ /**
131
+ * Storage helper for connected dApps per account
132
+ */
133
+ declare class ConnectedAppsStorage {
134
+ private static dbPromise;
135
+ private static getDB;
136
+ static upsert(app: ConnectedAppUpsert): Promise<ConnectedApp>;
137
+ static listByAccount(accountId: number): Promise<ConnectedApp[]>;
138
+ static remove(accountId: number, appId: string): Promise<void>;
139
+ static clear(): Promise<void>;
140
+ static get(accountId: number, appId: string): Promise<ConnectedApp | null>;
141
+ }
142
+
143
+ /**
144
+ * IndexedDB schema for wallet storage
145
+ */
146
+ declare const DB_NAME = "thru-wallet";
147
+ declare const DB_VERSION = 2;
148
+ /**
149
+ * Object store names
150
+ */
151
+ declare enum StoreName {
152
+ WALLET = "wallet",
153
+ SETTINGS = "settings",
154
+ CONNECTED_APPS = "connectedApps"
155
+ }
156
+ /**
157
+ * Wallet data stored in IndexedDB
158
+ */
159
+ interface WalletData {
160
+ id: string;
161
+ encryptedSeed: string;
162
+ createdAt: number;
163
+ updatedAt: number;
164
+ }
165
+ /**
166
+ * Settings data
167
+ */
168
+ interface SettingsData {
169
+ key: string;
170
+ value: any;
171
+ }
172
+ interface ConnectedAppData {
173
+ key: string;
174
+ accountId: number;
175
+ appId: string;
176
+ origin: string;
177
+ metadata: AppMetadata;
178
+ connectedAt: number;
179
+ updatedAt: number;
180
+ }
181
+
182
+ export { AccountStorage, type ConnectedAppData, ConnectedAppsStorage, DB_NAME, DB_VERSION, type SettingsData, StoreName, type StoredAccount, type WalletData, WalletStorage };
package/dist/index.js ADDED
@@ -0,0 +1,299 @@
1
+ import { openDB } from 'idb';
2
+ import { EncryptionService } from '@thru/crypto';
3
+
4
+ // src/wallet-store.ts
5
+
6
+ // src/schema.ts
7
+ var DB_NAME = "thru-wallet";
8
+ var DB_VERSION = 2;
9
+ var StoreName = /* @__PURE__ */ ((StoreName2) => {
10
+ StoreName2["WALLET"] = "wallet";
11
+ StoreName2["SETTINGS"] = "settings";
12
+ StoreName2["CONNECTED_APPS"] = "connectedApps";
13
+ return StoreName2;
14
+ })(StoreName || {});
15
+ function initializeSchema(db) {
16
+ if (!db.objectStoreNames.contains("wallet" /* WALLET */)) {
17
+ const walletStore = db.createObjectStore("wallet" /* WALLET */, { keyPath: "id" });
18
+ walletStore.createIndex("createdAt", "createdAt", { unique: false });
19
+ }
20
+ if (!db.objectStoreNames.contains("settings" /* SETTINGS */)) {
21
+ db.createObjectStore("settings" /* SETTINGS */, { keyPath: "key" });
22
+ }
23
+ if (!db.objectStoreNames.contains("connectedApps" /* CONNECTED_APPS */)) {
24
+ const store = db.createObjectStore("connectedApps" /* CONNECTED_APPS */, { keyPath: "key" });
25
+ store.createIndex("by-account", "accountId", { unique: false });
26
+ store.createIndex("by-updated", "updatedAt", { unique: false });
27
+ }
28
+ }
29
+ var WalletStorage = class {
30
+ /**
31
+ * Get or create database connection
32
+ */
33
+ static async getDB() {
34
+ if (!this.dbPromise) {
35
+ this.dbPromise = openDB(DB_NAME, DB_VERSION, {
36
+ upgrade(db) {
37
+ initializeSchema(db);
38
+ }
39
+ });
40
+ }
41
+ return this.dbPromise;
42
+ }
43
+ /**
44
+ * Save encrypted seed to storage
45
+ * @param encryptedSeed - Encrypted seed data
46
+ * @param walletId - Optional wallet ID (defaults to 'default')
47
+ */
48
+ static async saveEncryptedSeed(encryptedSeed, walletId = "default") {
49
+ const db = await this.getDB();
50
+ const serialized = EncryptionService.serialize(encryptedSeed);
51
+ const now = Date.now();
52
+ const walletData = {
53
+ id: walletId,
54
+ encryptedSeed: serialized,
55
+ createdAt: now,
56
+ updatedAt: now
57
+ };
58
+ await db.put("wallet" /* WALLET */, walletData);
59
+ }
60
+ /**
61
+ * Get encrypted seed from storage
62
+ * @param walletId - Wallet ID (defaults to 'default')
63
+ * @returns Encrypted seed data or null if not found
64
+ */
65
+ static async getEncryptedSeed(walletId = "default") {
66
+ const db = await this.getDB();
67
+ const walletData = await db.get("wallet" /* WALLET */, walletId);
68
+ if (!walletData) {
69
+ return null;
70
+ }
71
+ return EncryptionService.deserialize(walletData.encryptedSeed);
72
+ }
73
+ /**
74
+ * Check if wallet exists
75
+ * @param walletId - Wallet ID (defaults to 'default')
76
+ */
77
+ static async walletExists(walletId = "default") {
78
+ const db = await this.getDB();
79
+ const walletData = await db.get("wallet" /* WALLET */, walletId);
80
+ return walletData !== void 0;
81
+ }
82
+ /**
83
+ * Delete wallet from storage
84
+ * @param walletId - Wallet ID (defaults to 'default')
85
+ */
86
+ static async deleteWallet(walletId = "default") {
87
+ const db = await this.getDB();
88
+ await db.delete("wallet" /* WALLET */, walletId);
89
+ }
90
+ /**
91
+ * Clear all wallet data (use with caution!)
92
+ */
93
+ static async clearAll() {
94
+ const db = await this.getDB();
95
+ await db.clear("wallet" /* WALLET */);
96
+ await db.clear("settings" /* SETTINGS */);
97
+ }
98
+ /**
99
+ * Get storage quota information
100
+ */
101
+ static async getStorageInfo() {
102
+ if (!navigator.storage || !navigator.storage.estimate) {
103
+ return null;
104
+ }
105
+ const estimate = await navigator.storage.estimate();
106
+ const usage = estimate.usage || 0;
107
+ const quota = estimate.quota || 0;
108
+ return {
109
+ usage,
110
+ quota,
111
+ available: quota - usage
112
+ };
113
+ }
114
+ /**
115
+ * Request persistent storage (prevents data from being cleared)
116
+ */
117
+ static async requestPersistentStorage() {
118
+ if (!navigator.storage || !navigator.storage.persist) {
119
+ return false;
120
+ }
121
+ return await navigator.storage.persist();
122
+ }
123
+ /**
124
+ * Check if storage is persistent
125
+ */
126
+ static async isPersistent() {
127
+ if (!navigator.storage || !navigator.storage.persisted) {
128
+ return false;
129
+ }
130
+ return await navigator.storage.persisted();
131
+ }
132
+ };
133
+ WalletStorage.dbPromise = null;
134
+ var DB_NAME2 = "thru-wallet-accounts";
135
+ var DB_VERSION2 = 1;
136
+ var STORE_NAME = "accounts";
137
+ var AccountStorage = class {
138
+ /**
139
+ * Initialize or get existing database connection
140
+ */
141
+ static async getDB() {
142
+ if (!this.dbPromise) {
143
+ this.dbPromise = openDB(DB_NAME2, DB_VERSION2, {
144
+ upgrade(db) {
145
+ const store = db.createObjectStore(STORE_NAME, {
146
+ keyPath: "index"
147
+ });
148
+ store.createIndex("by-created", "createdAt");
149
+ }
150
+ });
151
+ }
152
+ return this.dbPromise;
153
+ }
154
+ /**
155
+ * Save a new account to storage
156
+ * @param account - Account metadata to save
157
+ */
158
+ static async saveAccount(account) {
159
+ const db = await this.getDB();
160
+ await db.put(STORE_NAME, account);
161
+ }
162
+ /**
163
+ * Get all accounts, sorted by index (ascending)
164
+ * @returns Array of stored accounts
165
+ */
166
+ static async getAccounts() {
167
+ const db = await this.getDB();
168
+ const accounts = await db.getAll(STORE_NAME);
169
+ return accounts.sort((a, b) => a.index - b.index);
170
+ }
171
+ /**
172
+ * Get a specific account by index
173
+ * @param index - Account index
174
+ * @returns Account or null if not found
175
+ */
176
+ static async getAccount(index) {
177
+ const db = await this.getDB();
178
+ const account = await db.get(STORE_NAME, index);
179
+ return account || null;
180
+ }
181
+ /**
182
+ * Update an account's label
183
+ * @param index - Account index
184
+ * @param label - New label
185
+ */
186
+ static async updateAccountLabel(index, label) {
187
+ const db = await this.getDB();
188
+ const account = await db.get(STORE_NAME, index);
189
+ if (!account) {
190
+ throw new Error(`Account ${index} not found`);
191
+ }
192
+ account.label = label;
193
+ await db.put(STORE_NAME, account);
194
+ }
195
+ /**
196
+ * Get the next available account index
197
+ * @returns Next index (0 if no accounts exist)
198
+ */
199
+ static async getNextAccountIndex() {
200
+ const accounts = await this.getAccounts();
201
+ if (accounts.length === 0) {
202
+ return 0;
203
+ }
204
+ const maxIndex = Math.max(...accounts.map((a) => a.index));
205
+ return maxIndex + 1;
206
+ }
207
+ /**
208
+ * Check if any accounts exist
209
+ * @returns true if at least one account exists
210
+ */
211
+ static async hasAccounts() {
212
+ const db = await this.getDB();
213
+ const count = await db.count(STORE_NAME);
214
+ return count > 0;
215
+ }
216
+ /**
217
+ * Get total number of accounts
218
+ * @returns Account count
219
+ */
220
+ static async getAccountCount() {
221
+ const db = await this.getDB();
222
+ return await db.count(STORE_NAME);
223
+ }
224
+ /**
225
+ * Clear all accounts (use with caution!)
226
+ * Used primarily for testing or wallet reset
227
+ */
228
+ static async clearAccounts() {
229
+ const db = await this.getDB();
230
+ await db.clear(STORE_NAME);
231
+ }
232
+ };
233
+ AccountStorage.dbPromise = null;
234
+ var STORE_NAME2 = "connectedApps" /* CONNECTED_APPS */;
235
+ function createKey(accountId, appId) {
236
+ return `${accountId}:${appId}`;
237
+ }
238
+ function toDomain(record) {
239
+ return {
240
+ accountId: record.accountId,
241
+ appId: record.appId,
242
+ origin: record.origin,
243
+ metadata: record.metadata,
244
+ connectedAt: record.connectedAt,
245
+ updatedAt: record.updatedAt
246
+ };
247
+ }
248
+ var ConnectedAppsStorage = class {
249
+ static async getDB() {
250
+ if (!this.dbPromise) {
251
+ this.dbPromise = openDB(DB_NAME, DB_VERSION, {
252
+ upgrade(db) {
253
+ initializeSchema(db);
254
+ }
255
+ });
256
+ }
257
+ return this.dbPromise;
258
+ }
259
+ static async upsert(app) {
260
+ const db = await this.getDB();
261
+ const key = createKey(app.accountId, app.appId);
262
+ const existing = await db.get(STORE_NAME2, key);
263
+ const now = Date.now();
264
+ const record = {
265
+ key,
266
+ accountId: app.accountId,
267
+ appId: app.appId,
268
+ origin: app.origin,
269
+ metadata: app.metadata,
270
+ connectedAt: existing?.connectedAt ?? now,
271
+ updatedAt: now
272
+ };
273
+ await db.put(STORE_NAME2, record);
274
+ return toDomain(record);
275
+ }
276
+ static async listByAccount(accountId) {
277
+ const db = await this.getDB();
278
+ const records = await db.getAllFromIndex(STORE_NAME2, "by-account", IDBKeyRange.only(accountId));
279
+ return records.sort((a, b) => b.updatedAt - a.updatedAt).map(toDomain);
280
+ }
281
+ static async remove(accountId, appId) {
282
+ const db = await this.getDB();
283
+ await db.delete(STORE_NAME2, createKey(accountId, appId));
284
+ }
285
+ static async clear() {
286
+ const db = await this.getDB();
287
+ await db.clear(STORE_NAME2);
288
+ }
289
+ static async get(accountId, appId) {
290
+ const db = await this.getDB();
291
+ const record = await db.get(STORE_NAME2, createKey(accountId, appId));
292
+ return record ? toDomain(record) : null;
293
+ }
294
+ };
295
+ ConnectedAppsStorage.dbPromise = null;
296
+
297
+ export { AccountStorage, ConnectedAppsStorage, DB_NAME, DB_VERSION, StoreName, WalletStorage };
298
+ //# sourceMappingURL=index.js.map
299
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema.ts","../src/wallet-store.ts","../src/account-store.ts","../src/connected-apps-store.ts"],"names":["StoreName","DB_NAME","DB_VERSION","openDB","STORE_NAME"],"mappings":"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","file":"index.js","sourcesContent":["import type { AppMetadata } from '@thru/chain-interfaces';\n\n/**\n * IndexedDB schema for wallet storage\n */\n\nexport const DB_NAME = 'thru-wallet';\nexport const DB_VERSION = 2;\n\n/**\n * Object store names\n */\nexport enum StoreName {\n WALLET = 'wallet',\n SETTINGS = 'settings',\n CONNECTED_APPS = 'connectedApps',\n}\n\n/**\n * Wallet data stored in IndexedDB\n */\nexport interface WalletData {\n id: string;\n encryptedSeed: string; // Serialized EncryptedData\n createdAt: number; // Timestamp\n updatedAt: number; // Timestamp\n}\n\n/**\n * Settings data\n */\nexport interface SettingsData {\n key: string;\n value: any;\n}\n\nexport interface ConnectedAppData {\n key: string; // `${accountId}:${appId}`\n accountId: number;\n appId: string;\n origin: string;\n metadata: AppMetadata;\n connectedAt: number;\n updatedAt: number;\n}\n\n/**\n * Initialize database schema\n */\nexport function initializeSchema(db: IDBDatabase): void {\n // Wallet store\n if (!db.objectStoreNames.contains(StoreName.WALLET)) {\n const walletStore = db.createObjectStore(StoreName.WALLET, { keyPath: 'id' });\n walletStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n\n // Settings store\n if (!db.objectStoreNames.contains(StoreName.SETTINGS)) {\n db.createObjectStore(StoreName.SETTINGS, { keyPath: 'key' });\n }\n\n // Connected apps store\n if (!db.objectStoreNames.contains(StoreName.CONNECTED_APPS)) {\n const store = db.createObjectStore(StoreName.CONNECTED_APPS, { keyPath: 'key' });\n store.createIndex('by-account', 'accountId', { unique: false });\n store.createIndex('by-updated', 'updatedAt', { unique: false });\n }\n}\n","import { openDB, IDBPDatabase } from 'idb';\nimport { DB_NAME, DB_VERSION, StoreName, WalletData, initializeSchema } from './schema';\nimport { EncryptedData, EncryptionService } from '@thru/crypto';\n\n/**\n * IndexedDB storage for wallet data\n */\nexport class WalletStorage {\n private static dbPromise: Promise<IDBPDatabase> | null = null;\n\n /**\n * Get or create database connection\n */\n private static async getDB(): Promise<IDBPDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = openDB(DB_NAME, DB_VERSION, {\n upgrade(db) {\n initializeSchema(db as any);\n },\n });\n }\n return this.dbPromise;\n }\n\n /**\n * Save encrypted seed to storage\n * @param encryptedSeed - Encrypted seed data\n * @param walletId - Optional wallet ID (defaults to 'default')\n */\n static async saveEncryptedSeed(\n encryptedSeed: EncryptedData,\n walletId: string = 'default'\n ): Promise<void> {\n const db = await this.getDB();\n\n // Serialize encrypted data\n const serialized = EncryptionService.serialize(encryptedSeed);\n\n const now = Date.now();\n const walletData: WalletData = {\n id: walletId,\n encryptedSeed: serialized,\n createdAt: now,\n updatedAt: now,\n };\n\n await db.put(StoreName.WALLET, walletData);\n }\n\n /**\n * Get encrypted seed from storage\n * @param walletId - Wallet ID (defaults to 'default')\n * @returns Encrypted seed data or null if not found\n */\n static async getEncryptedSeed(walletId: string = 'default'): Promise<EncryptedData | null> {\n const db = await this.getDB();\n const walletData = await db.get(StoreName.WALLET, walletId);\n\n if (!walletData) {\n return null;\n }\n\n // Deserialize encrypted data\n return EncryptionService.deserialize(walletData.encryptedSeed);\n }\n\n /**\n * Check if wallet exists\n * @param walletId - Wallet ID (defaults to 'default')\n */\n static async walletExists(walletId: string = 'default'): Promise<boolean> {\n const db = await this.getDB();\n const walletData = await db.get(StoreName.WALLET, walletId);\n return walletData !== undefined;\n }\n\n /**\n * Delete wallet from storage\n * @param walletId - Wallet ID (defaults to 'default')\n */\n static async deleteWallet(walletId: string = 'default'): Promise<void> {\n const db = await this.getDB();\n await db.delete(StoreName.WALLET, walletId);\n }\n\n /**\n * Clear all wallet data (use with caution!)\n */\n static async clearAll(): Promise<void> {\n const db = await this.getDB();\n await db.clear(StoreName.WALLET);\n await db.clear(StoreName.SETTINGS);\n }\n\n /**\n * Get storage quota information\n */\n static async getStorageInfo(): Promise<{\n usage: number;\n quota: number;\n available: number;\n } | null> {\n if (!navigator.storage || !navigator.storage.estimate) {\n return null;\n }\n\n const estimate = await navigator.storage.estimate();\n const usage = estimate.usage || 0;\n const quota = estimate.quota || 0;\n\n return {\n usage,\n quota,\n available: quota - usage,\n };\n }\n\n /**\n * Request persistent storage (prevents data from being cleared)\n */\n static async requestPersistentStorage(): Promise<boolean> {\n if (!navigator.storage || !navigator.storage.persist) {\n return false;\n }\n\n return await navigator.storage.persist();\n }\n\n /**\n * Check if storage is persistent\n */\n static async isPersistent(): Promise<boolean> {\n if (!navigator.storage || !navigator.storage.persisted) {\n return false;\n }\n\n return await navigator.storage.persisted();\n }\n}\n","import { openDB, DBSchema, IDBPDatabase } from 'idb';\nimport { StoredAccount } from './types';\n\n/**\n * IndexedDB schema for account storage\n */\ninterface AccountDB extends DBSchema {\n accounts: {\n key: number; // account index\n value: StoredAccount;\n indexes: {\n 'by-created': Date;\n };\n };\n}\n\nconst DB_NAME = 'thru-wallet-accounts';\nconst DB_VERSION = 1;\nconst STORE_NAME = 'accounts';\n\n/**\n * Account storage management using IndexedDB\n * Stores metadata for each derived account (but NOT private keys)\n */\nexport class AccountStorage {\n private static dbPromise: Promise<IDBPDatabase<AccountDB>> | null = null;\n\n /**\n * Initialize or get existing database connection\n */\n private static async getDB(): Promise<IDBPDatabase<AccountDB>> {\n if (!this.dbPromise) {\n this.dbPromise = openDB<AccountDB>(DB_NAME, DB_VERSION, {\n upgrade(db) {\n // Create accounts object store\n const store = db.createObjectStore(STORE_NAME, {\n keyPath: 'index',\n });\n\n // Create index for sorting by creation date\n store.createIndex('by-created', 'createdAt');\n },\n });\n }\n return this.dbPromise;\n }\n\n /**\n * Save a new account to storage\n * @param account - Account metadata to save\n */\n static async saveAccount(account: StoredAccount): Promise<void> {\n const db = await this.getDB();\n await db.put(STORE_NAME, account);\n }\n\n /**\n * Get all accounts, sorted by index (ascending)\n * @returns Array of stored accounts\n */\n static async getAccounts(): Promise<StoredAccount[]> {\n const db = await this.getDB();\n const accounts = await db.getAll(STORE_NAME);\n\n // Sort by index (0, 1, 2, ...)\n return accounts.sort((a, b) => a.index - b.index);\n }\n\n /**\n * Get a specific account by index\n * @param index - Account index\n * @returns Account or null if not found\n */\n static async getAccount(index: number): Promise<StoredAccount | null> {\n const db = await this.getDB();\n const account = await db.get(STORE_NAME, index);\n return account || null;\n }\n\n /**\n * Update an account's label\n * @param index - Account index\n * @param label - New label\n */\n static async updateAccountLabel(index: number, label: string): Promise<void> {\n const db = await this.getDB();\n const account = await db.get(STORE_NAME, index);\n\n if (!account) {\n throw new Error(`Account ${index} not found`);\n }\n\n account.label = label;\n await db.put(STORE_NAME, account);\n }\n\n /**\n * Get the next available account index\n * @returns Next index (0 if no accounts exist)\n */\n static async getNextAccountIndex(): Promise<number> {\n const accounts = await this.getAccounts();\n\n if (accounts.length === 0) {\n return 0;\n }\n\n // Return highest index + 1\n const maxIndex = Math.max(...accounts.map(a => a.index));\n return maxIndex + 1;\n }\n\n /**\n * Check if any accounts exist\n * @returns true if at least one account exists\n */\n static async hasAccounts(): Promise<boolean> {\n const db = await this.getDB();\n const count = await db.count(STORE_NAME);\n return count > 0;\n }\n\n /**\n * Get total number of accounts\n * @returns Account count\n */\n static async getAccountCount(): Promise<number> {\n const db = await this.getDB();\n return await db.count(STORE_NAME);\n }\n\n /**\n * Clear all accounts (use with caution!)\n * Used primarily for testing or wallet reset\n */\n static async clearAccounts(): Promise<void> {\n const db = await this.getDB();\n await db.clear(STORE_NAME);\n }\n}\n","import { openDB, IDBPDatabase } from 'idb';\nimport {\n DB_NAME,\n DB_VERSION,\n StoreName,\n initializeSchema,\n type ConnectedAppData,\n} from './schema';\nimport type { ConnectedApp, AppMetadata } from '@thru/chain-interfaces';\n\nconst STORE_NAME = StoreName.CONNECTED_APPS;\n\nfunction createKey(accountId: number, appId: string): string {\n return `${accountId}:${appId}`;\n}\n\nfunction toDomain(record: ConnectedAppData): ConnectedApp {\n return {\n accountId: record.accountId,\n appId: record.appId,\n origin: record.origin,\n metadata: record.metadata,\n connectedAt: record.connectedAt,\n updatedAt: record.updatedAt,\n };\n}\n\nexport interface ConnectedAppUpsert {\n accountId: number;\n appId: string;\n origin: string;\n metadata: AppMetadata;\n}\n\n/**\n * Storage helper for connected dApps per account\n */\nexport class ConnectedAppsStorage {\n private static dbPromise: Promise<IDBPDatabase> | null = null;\n\n private static async getDB(): Promise<IDBPDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = openDB(DB_NAME, DB_VERSION, {\n upgrade(db) {\n initializeSchema(db as any);\n },\n });\n }\n return this.dbPromise;\n }\n\n static async upsert(app: ConnectedAppUpsert): Promise<ConnectedApp> {\n const db = await this.getDB();\n const key = createKey(app.accountId, app.appId);\n const existing = (await db.get(STORE_NAME, key)) as ConnectedAppData | undefined;\n const now = Date.now();\n\n const record: ConnectedAppData = {\n key,\n accountId: app.accountId,\n appId: app.appId,\n origin: app.origin,\n metadata: app.metadata,\n connectedAt: existing?.connectedAt ?? now,\n updatedAt: now,\n };\n\n await db.put(STORE_NAME, record);\n return toDomain(record);\n }\n\n static async listByAccount(accountId: number): Promise<ConnectedApp[]> {\n const db = await this.getDB();\n const records = (await db.getAllFromIndex(STORE_NAME, 'by-account', IDBKeyRange.only(accountId))) as ConnectedAppData[];\n return records\n .sort((a, b) => b.updatedAt - a.updatedAt)\n .map(toDomain);\n }\n\n static async remove(accountId: number, appId: string): Promise<void> {\n const db = await this.getDB();\n await db.delete(STORE_NAME, createKey(accountId, appId));\n }\n\n static async clear(): Promise<void> {\n const db = await this.getDB();\n await db.clear(STORE_NAME);\n }\n\n static async get(accountId: number, appId: string): Promise<ConnectedApp | null> {\n const db = await this.getDB();\n const record = (await db.get(STORE_NAME, createKey(accountId, appId))) as ConnectedAppData | undefined;\n return record ? toDomain(record) : null;\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@thru/indexed-db-stamper",
3
+ "version": "0.0.4",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "dependencies": {
14
+ "idb": "^8.0.3",
15
+ "@thru/chain-interfaces": "0.0.4",
16
+ "@thru/crypto": "0.0.4"
17
+ },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "lint": "eslint src/",
22
+ "clean": "rm -rf dist"
23
+ }
24
+ }
@@ -0,0 +1,140 @@
1
+ import { openDB, DBSchema, IDBPDatabase } from 'idb';
2
+ import { StoredAccount } from './types';
3
+
4
+ /**
5
+ * IndexedDB schema for account storage
6
+ */
7
+ interface AccountDB extends DBSchema {
8
+ accounts: {
9
+ key: number; // account index
10
+ value: StoredAccount;
11
+ indexes: {
12
+ 'by-created': Date;
13
+ };
14
+ };
15
+ }
16
+
17
+ const DB_NAME = 'thru-wallet-accounts';
18
+ const DB_VERSION = 1;
19
+ const STORE_NAME = 'accounts';
20
+
21
+ /**
22
+ * Account storage management using IndexedDB
23
+ * Stores metadata for each derived account (but NOT private keys)
24
+ */
25
+ export class AccountStorage {
26
+ private static dbPromise: Promise<IDBPDatabase<AccountDB>> | null = null;
27
+
28
+ /**
29
+ * Initialize or get existing database connection
30
+ */
31
+ private static async getDB(): Promise<IDBPDatabase<AccountDB>> {
32
+ if (!this.dbPromise) {
33
+ this.dbPromise = openDB<AccountDB>(DB_NAME, DB_VERSION, {
34
+ upgrade(db) {
35
+ // Create accounts object store
36
+ const store = db.createObjectStore(STORE_NAME, {
37
+ keyPath: 'index',
38
+ });
39
+
40
+ // Create index for sorting by creation date
41
+ store.createIndex('by-created', 'createdAt');
42
+ },
43
+ });
44
+ }
45
+ return this.dbPromise;
46
+ }
47
+
48
+ /**
49
+ * Save a new account to storage
50
+ * @param account - Account metadata to save
51
+ */
52
+ static async saveAccount(account: StoredAccount): Promise<void> {
53
+ const db = await this.getDB();
54
+ await db.put(STORE_NAME, account);
55
+ }
56
+
57
+ /**
58
+ * Get all accounts, sorted by index (ascending)
59
+ * @returns Array of stored accounts
60
+ */
61
+ static async getAccounts(): Promise<StoredAccount[]> {
62
+ const db = await this.getDB();
63
+ const accounts = await db.getAll(STORE_NAME);
64
+
65
+ // Sort by index (0, 1, 2, ...)
66
+ return accounts.sort((a, b) => a.index - b.index);
67
+ }
68
+
69
+ /**
70
+ * Get a specific account by index
71
+ * @param index - Account index
72
+ * @returns Account or null if not found
73
+ */
74
+ static async getAccount(index: number): Promise<StoredAccount | null> {
75
+ const db = await this.getDB();
76
+ const account = await db.get(STORE_NAME, index);
77
+ return account || null;
78
+ }
79
+
80
+ /**
81
+ * Update an account's label
82
+ * @param index - Account index
83
+ * @param label - New label
84
+ */
85
+ static async updateAccountLabel(index: number, label: string): Promise<void> {
86
+ const db = await this.getDB();
87
+ const account = await db.get(STORE_NAME, index);
88
+
89
+ if (!account) {
90
+ throw new Error(`Account ${index} not found`);
91
+ }
92
+
93
+ account.label = label;
94
+ await db.put(STORE_NAME, account);
95
+ }
96
+
97
+ /**
98
+ * Get the next available account index
99
+ * @returns Next index (0 if no accounts exist)
100
+ */
101
+ static async getNextAccountIndex(): Promise<number> {
102
+ const accounts = await this.getAccounts();
103
+
104
+ if (accounts.length === 0) {
105
+ return 0;
106
+ }
107
+
108
+ // Return highest index + 1
109
+ const maxIndex = Math.max(...accounts.map(a => a.index));
110
+ return maxIndex + 1;
111
+ }
112
+
113
+ /**
114
+ * Check if any accounts exist
115
+ * @returns true if at least one account exists
116
+ */
117
+ static async hasAccounts(): Promise<boolean> {
118
+ const db = await this.getDB();
119
+ const count = await db.count(STORE_NAME);
120
+ return count > 0;
121
+ }
122
+
123
+ /**
124
+ * Get total number of accounts
125
+ * @returns Account count
126
+ */
127
+ static async getAccountCount(): Promise<number> {
128
+ const db = await this.getDB();
129
+ return await db.count(STORE_NAME);
130
+ }
131
+
132
+ /**
133
+ * Clear all accounts (use with caution!)
134
+ * Used primarily for testing or wallet reset
135
+ */
136
+ static async clearAccounts(): Promise<void> {
137
+ const db = await this.getDB();
138
+ await db.clear(STORE_NAME);
139
+ }
140
+ }
@@ -0,0 +1,95 @@
1
+ import { openDB, IDBPDatabase } from 'idb';
2
+ import {
3
+ DB_NAME,
4
+ DB_VERSION,
5
+ StoreName,
6
+ initializeSchema,
7
+ type ConnectedAppData,
8
+ } from './schema';
9
+ import type { ConnectedApp, AppMetadata } from '@thru/chain-interfaces';
10
+
11
+ const STORE_NAME = StoreName.CONNECTED_APPS;
12
+
13
+ function createKey(accountId: number, appId: string): string {
14
+ return `${accountId}:${appId}`;
15
+ }
16
+
17
+ function toDomain(record: ConnectedAppData): ConnectedApp {
18
+ return {
19
+ accountId: record.accountId,
20
+ appId: record.appId,
21
+ origin: record.origin,
22
+ metadata: record.metadata,
23
+ connectedAt: record.connectedAt,
24
+ updatedAt: record.updatedAt,
25
+ };
26
+ }
27
+
28
+ export interface ConnectedAppUpsert {
29
+ accountId: number;
30
+ appId: string;
31
+ origin: string;
32
+ metadata: AppMetadata;
33
+ }
34
+
35
+ /**
36
+ * Storage helper for connected dApps per account
37
+ */
38
+ export class ConnectedAppsStorage {
39
+ private static dbPromise: Promise<IDBPDatabase> | null = null;
40
+
41
+ private static async getDB(): Promise<IDBPDatabase> {
42
+ if (!this.dbPromise) {
43
+ this.dbPromise = openDB(DB_NAME, DB_VERSION, {
44
+ upgrade(db) {
45
+ initializeSchema(db as any);
46
+ },
47
+ });
48
+ }
49
+ return this.dbPromise;
50
+ }
51
+
52
+ static async upsert(app: ConnectedAppUpsert): Promise<ConnectedApp> {
53
+ const db = await this.getDB();
54
+ const key = createKey(app.accountId, app.appId);
55
+ const existing = (await db.get(STORE_NAME, key)) as ConnectedAppData | undefined;
56
+ const now = Date.now();
57
+
58
+ const record: ConnectedAppData = {
59
+ key,
60
+ accountId: app.accountId,
61
+ appId: app.appId,
62
+ origin: app.origin,
63
+ metadata: app.metadata,
64
+ connectedAt: existing?.connectedAt ?? now,
65
+ updatedAt: now,
66
+ };
67
+
68
+ await db.put(STORE_NAME, record);
69
+ return toDomain(record);
70
+ }
71
+
72
+ static async listByAccount(accountId: number): Promise<ConnectedApp[]> {
73
+ const db = await this.getDB();
74
+ const records = (await db.getAllFromIndex(STORE_NAME, 'by-account', IDBKeyRange.only(accountId))) as ConnectedAppData[];
75
+ return records
76
+ .sort((a, b) => b.updatedAt - a.updatedAt)
77
+ .map(toDomain);
78
+ }
79
+
80
+ static async remove(accountId: number, appId: string): Promise<void> {
81
+ const db = await this.getDB();
82
+ await db.delete(STORE_NAME, createKey(accountId, appId));
83
+ }
84
+
85
+ static async clear(): Promise<void> {
86
+ const db = await this.getDB();
87
+ await db.clear(STORE_NAME);
88
+ }
89
+
90
+ static async get(accountId: number, appId: string): Promise<ConnectedApp | null> {
91
+ const db = await this.getDB();
92
+ const record = (await db.get(STORE_NAME, createKey(accountId, appId))) as ConnectedAppData | undefined;
93
+ return record ? toDomain(record) : null;
94
+ }
95
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export { WalletStorage } from './wallet-store';
2
+ export { AccountStorage } from './account-store';
3
+ export { ConnectedAppsStorage } from './connected-apps-store';
4
+ export {
5
+ type WalletData,
6
+ type SettingsData,
7
+ type ConnectedAppData,
8
+ StoreName,
9
+ DB_NAME,
10
+ DB_VERSION,
11
+ } from './schema';
12
+ export { type StoredAccount } from './types';
package/src/schema.ts ADDED
@@ -0,0 +1,68 @@
1
+ import type { AppMetadata } from '@thru/chain-interfaces';
2
+
3
+ /**
4
+ * IndexedDB schema for wallet storage
5
+ */
6
+
7
+ export const DB_NAME = 'thru-wallet';
8
+ export const DB_VERSION = 2;
9
+
10
+ /**
11
+ * Object store names
12
+ */
13
+ export enum StoreName {
14
+ WALLET = 'wallet',
15
+ SETTINGS = 'settings',
16
+ CONNECTED_APPS = 'connectedApps',
17
+ }
18
+
19
+ /**
20
+ * Wallet data stored in IndexedDB
21
+ */
22
+ export interface WalletData {
23
+ id: string;
24
+ encryptedSeed: string; // Serialized EncryptedData
25
+ createdAt: number; // Timestamp
26
+ updatedAt: number; // Timestamp
27
+ }
28
+
29
+ /**
30
+ * Settings data
31
+ */
32
+ export interface SettingsData {
33
+ key: string;
34
+ value: any;
35
+ }
36
+
37
+ export interface ConnectedAppData {
38
+ key: string; // `${accountId}:${appId}`
39
+ accountId: number;
40
+ appId: string;
41
+ origin: string;
42
+ metadata: AppMetadata;
43
+ connectedAt: number;
44
+ updatedAt: number;
45
+ }
46
+
47
+ /**
48
+ * Initialize database schema
49
+ */
50
+ export function initializeSchema(db: IDBDatabase): void {
51
+ // Wallet store
52
+ if (!db.objectStoreNames.contains(StoreName.WALLET)) {
53
+ const walletStore = db.createObjectStore(StoreName.WALLET, { keyPath: 'id' });
54
+ walletStore.createIndex('createdAt', 'createdAt', { unique: false });
55
+ }
56
+
57
+ // Settings store
58
+ if (!db.objectStoreNames.contains(StoreName.SETTINGS)) {
59
+ db.createObjectStore(StoreName.SETTINGS, { keyPath: 'key' });
60
+ }
61
+
62
+ // Connected apps store
63
+ if (!db.objectStoreNames.contains(StoreName.CONNECTED_APPS)) {
64
+ const store = db.createObjectStore(StoreName.CONNECTED_APPS, { keyPath: 'key' });
65
+ store.createIndex('by-account', 'accountId', { unique: false });
66
+ store.createIndex('by-updated', 'updatedAt', { unique: false });
67
+ }
68
+ }
package/src/types.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { AddressType } from '@thru/chain-interfaces';
2
+
3
+ /**
4
+ * Stored account representation in IndexedDB
5
+ */
6
+ export interface StoredAccount {
7
+ index: number; // BIP44 account index (0, 1, 2, ...)
8
+ label: string; // User-defined name (e.g., "Trading", "NFTs")
9
+ publicKey: string; // Encoded address string (currently base58 SOL; will migrate to Thru)
10
+ path: string; // Full derivation path (m/44'/<coin>'/index'/0')
11
+ createdAt: Date; // Timestamp of account creation
12
+ addressType?: AddressType; // Chain identifier (e.g., 'thru')
13
+ publicKeyRawBase64?: string; // Optional raw 32-byte public key as base64 (for Thru migration)
14
+ }
@@ -0,0 +1,139 @@
1
+ import { openDB, IDBPDatabase } from 'idb';
2
+ import { DB_NAME, DB_VERSION, StoreName, WalletData, initializeSchema } from './schema';
3
+ import { EncryptedData, EncryptionService } from '@thru/crypto';
4
+
5
+ /**
6
+ * IndexedDB storage for wallet data
7
+ */
8
+ export class WalletStorage {
9
+ private static dbPromise: Promise<IDBPDatabase> | null = null;
10
+
11
+ /**
12
+ * Get or create database connection
13
+ */
14
+ private static async getDB(): Promise<IDBPDatabase> {
15
+ if (!this.dbPromise) {
16
+ this.dbPromise = openDB(DB_NAME, DB_VERSION, {
17
+ upgrade(db) {
18
+ initializeSchema(db as any);
19
+ },
20
+ });
21
+ }
22
+ return this.dbPromise;
23
+ }
24
+
25
+ /**
26
+ * Save encrypted seed to storage
27
+ * @param encryptedSeed - Encrypted seed data
28
+ * @param walletId - Optional wallet ID (defaults to 'default')
29
+ */
30
+ static async saveEncryptedSeed(
31
+ encryptedSeed: EncryptedData,
32
+ walletId: string = 'default'
33
+ ): Promise<void> {
34
+ const db = await this.getDB();
35
+
36
+ // Serialize encrypted data
37
+ const serialized = EncryptionService.serialize(encryptedSeed);
38
+
39
+ const now = Date.now();
40
+ const walletData: WalletData = {
41
+ id: walletId,
42
+ encryptedSeed: serialized,
43
+ createdAt: now,
44
+ updatedAt: now,
45
+ };
46
+
47
+ await db.put(StoreName.WALLET, walletData);
48
+ }
49
+
50
+ /**
51
+ * Get encrypted seed from storage
52
+ * @param walletId - Wallet ID (defaults to 'default')
53
+ * @returns Encrypted seed data or null if not found
54
+ */
55
+ static async getEncryptedSeed(walletId: string = 'default'): Promise<EncryptedData | null> {
56
+ const db = await this.getDB();
57
+ const walletData = await db.get(StoreName.WALLET, walletId);
58
+
59
+ if (!walletData) {
60
+ return null;
61
+ }
62
+
63
+ // Deserialize encrypted data
64
+ return EncryptionService.deserialize(walletData.encryptedSeed);
65
+ }
66
+
67
+ /**
68
+ * Check if wallet exists
69
+ * @param walletId - Wallet ID (defaults to 'default')
70
+ */
71
+ static async walletExists(walletId: string = 'default'): Promise<boolean> {
72
+ const db = await this.getDB();
73
+ const walletData = await db.get(StoreName.WALLET, walletId);
74
+ return walletData !== undefined;
75
+ }
76
+
77
+ /**
78
+ * Delete wallet from storage
79
+ * @param walletId - Wallet ID (defaults to 'default')
80
+ */
81
+ static async deleteWallet(walletId: string = 'default'): Promise<void> {
82
+ const db = await this.getDB();
83
+ await db.delete(StoreName.WALLET, walletId);
84
+ }
85
+
86
+ /**
87
+ * Clear all wallet data (use with caution!)
88
+ */
89
+ static async clearAll(): Promise<void> {
90
+ const db = await this.getDB();
91
+ await db.clear(StoreName.WALLET);
92
+ await db.clear(StoreName.SETTINGS);
93
+ }
94
+
95
+ /**
96
+ * Get storage quota information
97
+ */
98
+ static async getStorageInfo(): Promise<{
99
+ usage: number;
100
+ quota: number;
101
+ available: number;
102
+ } | null> {
103
+ if (!navigator.storage || !navigator.storage.estimate) {
104
+ return null;
105
+ }
106
+
107
+ const estimate = await navigator.storage.estimate();
108
+ const usage = estimate.usage || 0;
109
+ const quota = estimate.quota || 0;
110
+
111
+ return {
112
+ usage,
113
+ quota,
114
+ available: quota - usage,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Request persistent storage (prevents data from being cleared)
120
+ */
121
+ static async requestPersistentStorage(): Promise<boolean> {
122
+ if (!navigator.storage || !navigator.storage.persist) {
123
+ return false;
124
+ }
125
+
126
+ return await navigator.storage.persist();
127
+ }
128
+
129
+ /**
130
+ * Check if storage is persistent
131
+ */
132
+ static async isPersistent(): Promise<boolean> {
133
+ if (!navigator.storage || !navigator.storage.persisted) {
134
+ return false;
135
+ }
136
+
137
+ return await navigator.storage.persisted();
138
+ }
139
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ sourcemap: true,
8
+ clean: true,
9
+ treeshake: true,
10
+ });