@0xsequence/wallet-wdk 0.0.0-20250520201059
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/.env.test +3 -0
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +202 -0
- package/dist/dbs/auth-commitments.d.ts +17 -0
- package/dist/dbs/auth-commitments.d.ts.map +1 -0
- package/dist/dbs/auth-commitments.js +13 -0
- package/dist/dbs/auth-keys.d.ts +19 -0
- package/dist/dbs/auth-keys.d.ts.map +1 -0
- package/dist/dbs/auth-keys.js +67 -0
- package/dist/dbs/generic.d.ts +33 -0
- package/dist/dbs/generic.d.ts.map +1 -0
- package/dist/dbs/generic.js +170 -0
- package/dist/dbs/index.d.ts +12 -0
- package/dist/dbs/index.d.ts.map +1 -0
- package/dist/dbs/index.js +8 -0
- package/dist/dbs/messages.d.ts +6 -0
- package/dist/dbs/messages.d.ts.map +1 -0
- package/dist/dbs/messages.js +13 -0
- package/dist/dbs/recovery.d.ts +6 -0
- package/dist/dbs/recovery.d.ts.map +1 -0
- package/dist/dbs/recovery.js +13 -0
- package/dist/dbs/signatures.d.ts +6 -0
- package/dist/dbs/signatures.d.ts.map +1 -0
- package/dist/dbs/signatures.js +13 -0
- package/dist/dbs/transactions.d.ts +6 -0
- package/dist/dbs/transactions.d.ts.map +1 -0
- package/dist/dbs/transactions.js +13 -0
- package/dist/dbs/wallets.d.ts +6 -0
- package/dist/dbs/wallets.d.ts.map +1 -0
- package/dist/dbs/wallets.js +13 -0
- package/dist/identity/signer.d.ts +17 -0
- package/dist/identity/signer.d.ts.map +1 -0
- package/dist/identity/signer.js +58 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/sequence/cron.d.ts +19 -0
- package/dist/sequence/cron.d.ts.map +1 -0
- package/dist/sequence/cron.js +118 -0
- package/dist/sequence/devices.d.ts +14 -0
- package/dist/sequence/devices.d.ts.map +1 -0
- package/dist/sequence/devices.js +43 -0
- package/dist/sequence/handlers/authcode-pkce.d.ts +14 -0
- package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -0
- package/dist/sequence/handlers/authcode-pkce.js +48 -0
- package/dist/sequence/handlers/authcode.d.ts +25 -0
- package/dist/sequence/handlers/authcode.d.ts.map +1 -0
- package/dist/sequence/handlers/authcode.js +91 -0
- package/dist/sequence/handlers/devices.d.ts +14 -0
- package/dist/sequence/handlers/devices.d.ts.map +1 -0
- package/dist/sequence/handlers/devices.js +39 -0
- package/dist/sequence/handlers/handler.d.ts +8 -0
- package/dist/sequence/handlers/handler.d.ts.map +1 -0
- package/dist/sequence/handlers/handler.js +1 -0
- package/dist/sequence/handlers/identity.d.ts +21 -0
- package/dist/sequence/handlers/identity.d.ts.map +1 -0
- package/dist/sequence/handlers/identity.js +86 -0
- package/dist/sequence/handlers/index.d.ts +7 -0
- package/dist/sequence/handlers/index.d.ts.map +1 -0
- package/dist/sequence/handlers/index.js +5 -0
- package/dist/sequence/handlers/mnemonic.d.ts +19 -0
- package/dist/sequence/handlers/mnemonic.d.ts.map +1 -0
- package/dist/sequence/handlers/mnemonic.js +67 -0
- package/dist/sequence/handlers/otp.d.ts +20 -0
- package/dist/sequence/handlers/otp.d.ts.map +1 -0
- package/dist/sequence/handlers/otp.js +83 -0
- package/dist/sequence/handlers/passkeys.d.ts +17 -0
- package/dist/sequence/handlers/passkeys.d.ts.map +1 -0
- package/dist/sequence/handlers/passkeys.js +63 -0
- package/dist/sequence/handlers/recovery.d.ts +15 -0
- package/dist/sequence/handlers/recovery.d.ts.map +1 -0
- package/dist/sequence/handlers/recovery.js +72 -0
- package/dist/sequence/index.d.ts +12 -0
- package/dist/sequence/index.d.ts.map +1 -0
- package/dist/sequence/index.js +9 -0
- package/dist/sequence/logger.d.ts +7 -0
- package/dist/sequence/logger.d.ts.map +1 -0
- package/dist/sequence/logger.js +11 -0
- package/dist/sequence/manager.d.ts +287 -0
- package/dist/sequence/manager.d.ts.map +1 -0
- package/dist/sequence/manager.js +356 -0
- package/dist/sequence/messages.d.ts +18 -0
- package/dist/sequence/messages.d.ts.map +1 -0
- package/dist/sequence/messages.js +115 -0
- package/dist/sequence/recovery.d.ts +30 -0
- package/dist/sequence/recovery.d.ts.map +1 -0
- package/dist/sequence/recovery.js +314 -0
- package/dist/sequence/sessions.d.ts +26 -0
- package/dist/sequence/sessions.d.ts.map +1 -0
- package/dist/sequence/sessions.js +169 -0
- package/dist/sequence/signatures.d.ts +21 -0
- package/dist/sequence/signatures.d.ts.map +1 -0
- package/dist/sequence/signatures.js +192 -0
- package/dist/sequence/signers.d.ts +14 -0
- package/dist/sequence/signers.d.ts.map +1 -0
- package/dist/sequence/signers.js +74 -0
- package/dist/sequence/transactions.d.ts +26 -0
- package/dist/sequence/transactions.d.ts.map +1 -0
- package/dist/sequence/transactions.js +201 -0
- package/dist/sequence/types/index.d.ts +9 -0
- package/dist/sequence/types/index.d.ts.map +1 -0
- package/dist/sequence/types/index.js +2 -0
- package/dist/sequence/types/message-request.d.ts +23 -0
- package/dist/sequence/types/message-request.d.ts.map +1 -0
- package/dist/sequence/types/message-request.js +1 -0
- package/dist/sequence/types/recovery.d.ts +15 -0
- package/dist/sequence/types/recovery.d.ts.map +1 -0
- package/dist/sequence/types/recovery.js +1 -0
- package/dist/sequence/types/signature-request.d.ts +76 -0
- package/dist/sequence/types/signature-request.d.ts.map +1 -0
- package/dist/sequence/types/signature-request.js +11 -0
- package/dist/sequence/types/signer.d.ts +28 -0
- package/dist/sequence/types/signer.d.ts.map +1 -0
- package/dist/sequence/types/signer.js +10 -0
- package/dist/sequence/types/transaction-request.d.ts +41 -0
- package/dist/sequence/types/transaction-request.d.ts.map +1 -0
- package/dist/sequence/types/transaction-request.js +1 -0
- package/dist/sequence/types/wallet.d.ts +21 -0
- package/dist/sequence/types/wallet.d.ts.map +1 -0
- package/dist/sequence/types/wallet.js +1 -0
- package/dist/sequence/wallets.d.ts +121 -0
- package/dist/sequence/wallets.d.ts.map +1 -0
- package/dist/sequence/wallets.js +632 -0
- package/package.json +40 -0
- package/src/dbs/auth-commitments.ts +26 -0
- package/src/dbs/auth-keys.ts +85 -0
- package/src/dbs/generic.ts +194 -0
- package/src/dbs/index.ts +13 -0
- package/src/dbs/messages.ts +16 -0
- package/src/dbs/recovery.ts +15 -0
- package/src/dbs/signatures.ts +15 -0
- package/src/dbs/transactions.ts +16 -0
- package/src/dbs/wallets.ts +16 -0
- package/src/identity/signer.ts +78 -0
- package/src/index.ts +2 -0
- package/src/sequence/cron.ts +134 -0
- package/src/sequence/devices.ts +53 -0
- package/src/sequence/handlers/authcode-pkce.ts +70 -0
- package/src/sequence/handlers/authcode.ts +116 -0
- package/src/sequence/handlers/devices.ts +53 -0
- package/src/sequence/handlers/handler.ts +14 -0
- package/src/sequence/handlers/identity.ts +101 -0
- package/src/sequence/handlers/index.ts +6 -0
- package/src/sequence/handlers/mnemonic.ts +88 -0
- package/src/sequence/handlers/otp.ts +107 -0
- package/src/sequence/handlers/passkeys.ts +84 -0
- package/src/sequence/handlers/recovery.ts +88 -0
- package/src/sequence/index.ts +25 -0
- package/src/sequence/logger.ts +11 -0
- package/src/sequence/manager.ts +634 -0
- package/src/sequence/messages.ts +146 -0
- package/src/sequence/recovery.ts +429 -0
- package/src/sequence/sessions.ts +238 -0
- package/src/sequence/signatures.ts +263 -0
- package/src/sequence/signers.ts +88 -0
- package/src/sequence/transactions.ts +281 -0
- package/src/sequence/types/index.ts +27 -0
- package/src/sequence/types/message-request.ts +26 -0
- package/src/sequence/types/recovery.ts +15 -0
- package/src/sequence/types/signature-request.ts +89 -0
- package/src/sequence/types/signer.ts +32 -0
- package/src/sequence/types/transaction-request.ts +47 -0
- package/src/sequence/types/wallet.ts +24 -0
- package/src/sequence/wallets.ts +853 -0
- package/test/constants.ts +62 -0
- package/test/recovery.test.ts +211 -0
- package/test/sessions.test.ts +324 -0
- package/test/setup.ts +63 -0
- package/test/transactions.test.ts +464 -0
- package/test/wallets.test.ts +381 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Generic } from './generic.js'
|
|
2
|
+
|
|
3
|
+
const TABLE_NAME = 'auth-keys'
|
|
4
|
+
|
|
5
|
+
export type AuthKey = {
|
|
6
|
+
address: string
|
|
7
|
+
privateKey: CryptoKey
|
|
8
|
+
identitySigner: string
|
|
9
|
+
expiresAt: Date
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class AuthKeys extends Generic<AuthKey, 'address'> {
|
|
13
|
+
private expirationTimers = new Map<string, number>()
|
|
14
|
+
|
|
15
|
+
constructor(dbName: string = 'sequence-auth-keys') {
|
|
16
|
+
super(dbName, TABLE_NAME, 'address', [
|
|
17
|
+
(db: IDBDatabase) => {
|
|
18
|
+
if (!db.objectStoreNames.contains(TABLE_NAME)) {
|
|
19
|
+
const store = db.createObjectStore(TABLE_NAME)
|
|
20
|
+
store.createIndex('identitySigner', 'identitySigner', { unique: true })
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handleOpenDB(): Promise<void> {
|
|
27
|
+
const authKeys = await this.list()
|
|
28
|
+
for (const authKey of authKeys) {
|
|
29
|
+
this.scheduleExpiration(authKey)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async set(item: AuthKey): Promise<AuthKey['address']> {
|
|
34
|
+
const result = await super.set(item)
|
|
35
|
+
this.scheduleExpiration(item)
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async del(address: AuthKey['address']): Promise<void> {
|
|
40
|
+
const result = await super.del(address)
|
|
41
|
+
this.clearExpiration(address)
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getBySigner(signer: string): Promise<AuthKey | undefined> {
|
|
46
|
+
const store = await this.getStore('readonly')
|
|
47
|
+
const index = store.index('identitySigner')
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const req = index.get(signer)
|
|
50
|
+
req.onsuccess = () => resolve(req.result)
|
|
51
|
+
req.onerror = () => reject(req.error)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async delBySigner(signer: string): Promise<void> {
|
|
56
|
+
const authKey = await this.getBySigner(signer)
|
|
57
|
+
if (authKey) {
|
|
58
|
+
await this.del(authKey.address)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async scheduleExpiration(authKey: AuthKey): Promise<void> {
|
|
63
|
+
this.clearExpiration(authKey.address)
|
|
64
|
+
|
|
65
|
+
const now = Date.now()
|
|
66
|
+
const delay = authKey.expiresAt.getTime() - now
|
|
67
|
+
if (delay <= 0) {
|
|
68
|
+
await this.del(authKey.address)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
const timer = window.setTimeout(() => {
|
|
72
|
+
console.log('removing expired auth key', authKey)
|
|
73
|
+
this.del(authKey.address)
|
|
74
|
+
}, delay)
|
|
75
|
+
this.expirationTimers.set(authKey.address, timer)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private clearExpiration(address: string): void {
|
|
79
|
+
const timer = this.expirationTimers.get(address)
|
|
80
|
+
if (timer) {
|
|
81
|
+
window.clearTimeout(timer)
|
|
82
|
+
this.expirationTimers.delete(address)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
export type DbUpdateType = 'added' | 'updated' | 'removed'
|
|
2
|
+
|
|
3
|
+
export type DbUpdateListener<T, K extends keyof T> = (
|
|
4
|
+
keyValue: T[K],
|
|
5
|
+
updateType: DbUpdateType,
|
|
6
|
+
oldItem?: T,
|
|
7
|
+
newItem?: T,
|
|
8
|
+
) => void
|
|
9
|
+
|
|
10
|
+
export type Migration = (db: IDBDatabase, transaction: IDBTransaction, event: IDBVersionChangeEvent) => void
|
|
11
|
+
|
|
12
|
+
function deepEqual(a: any, b: any): boolean {
|
|
13
|
+
if (a === b) {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const keysA = Object.keys(a)
|
|
22
|
+
const keysB = Object.keys(b)
|
|
23
|
+
if (keysA.length !== keysB.length) return false
|
|
24
|
+
|
|
25
|
+
for (const key of keysA) {
|
|
26
|
+
if (!keysB.includes(key)) return false
|
|
27
|
+
if (!deepEqual(a[key], b[key])) return false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class Generic<T extends { [P in K]: IDBValidKey }, K extends keyof T> {
|
|
34
|
+
private _db: IDBDatabase | null = null
|
|
35
|
+
private listeners: DbUpdateListener<T, K>[] = []
|
|
36
|
+
private broadcastChannel?: BroadcastChannel
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param dbName The name of the IndexedDB database.
|
|
40
|
+
* @param storeName The name of the object store.
|
|
41
|
+
* @param key The property key in T to be used as the primary key.
|
|
42
|
+
* @param migrations An array of migration functions; the database version is migrations.length + 1.
|
|
43
|
+
*/
|
|
44
|
+
constructor(
|
|
45
|
+
private dbName: string,
|
|
46
|
+
private storeName: string,
|
|
47
|
+
private key: K,
|
|
48
|
+
private migrations: Migration[] = [],
|
|
49
|
+
) {
|
|
50
|
+
if (typeof BroadcastChannel !== 'undefined') {
|
|
51
|
+
this.broadcastChannel = new BroadcastChannel(this.dbName + '-observer')
|
|
52
|
+
this.broadcastChannel.onmessage = (event) => {
|
|
53
|
+
if (event.data && event.data.keyValue !== undefined && event.data.updateType) {
|
|
54
|
+
this.listeners.forEach((cb) =>
|
|
55
|
+
cb(event.data.keyValue, event.data.updateType, event.data.oldItem, event.data.newItem),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async openDB(): Promise<IDBDatabase> {
|
|
63
|
+
if (this._db) return this._db
|
|
64
|
+
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const version = this.migrations.length + 1
|
|
67
|
+
const request = indexedDB.open(this.dbName, version)
|
|
68
|
+
|
|
69
|
+
request.onupgradeneeded = (event) => {
|
|
70
|
+
const db = request.result
|
|
71
|
+
const tx = request.transaction!
|
|
72
|
+
const oldVersion = (event.oldVersion as number) || 0
|
|
73
|
+
for (let i = oldVersion; i < this.migrations.length; i++) {
|
|
74
|
+
const migration = this.migrations[i]
|
|
75
|
+
if (!migration) throw new Error(`Migration ${i} not found`)
|
|
76
|
+
migration(db, tx, event)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
request.onsuccess = () => {
|
|
81
|
+
this._db = request.result
|
|
82
|
+
this.handleOpenDB().then(() => resolve(this._db!))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
request.onerror = () => reject(request.error)
|
|
86
|
+
request.onblocked = () => console.error('Database upgrade blocked')
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected async handleOpenDB(): Promise<void> {}
|
|
91
|
+
|
|
92
|
+
protected async getStore(mode: IDBTransactionMode): Promise<IDBObjectStore> {
|
|
93
|
+
const db = await this.openDB()
|
|
94
|
+
const tx = db.transaction(this.storeName, mode)
|
|
95
|
+
return tx.objectStore(this.storeName)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async get(keyValue: T[K]): Promise<T | undefined> {
|
|
99
|
+
const store = await this.getStore('readonly')
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const req = store.get(keyValue)
|
|
102
|
+
req.onsuccess = () => resolve(req.result as T)
|
|
103
|
+
req.onerror = () => reject(req.error)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async list(): Promise<T[]> {
|
|
108
|
+
const store = await this.getStore('readonly')
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const req = store.getAll()
|
|
111
|
+
req.onsuccess = () => resolve(req.result as T[])
|
|
112
|
+
req.onerror = () => reject(req.error)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async set(item: T): Promise<T[K]> {
|
|
117
|
+
const db = await this.openDB()
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const tx = db.transaction(this.storeName, 'readwrite')
|
|
120
|
+
const store = tx.objectStore(this.storeName)
|
|
121
|
+
const keyValue = item[this.key]
|
|
122
|
+
|
|
123
|
+
const getReq = store.get(keyValue)
|
|
124
|
+
getReq.onsuccess = () => {
|
|
125
|
+
const oldItem = getReq.result
|
|
126
|
+
const putReq = store.put(item, keyValue)
|
|
127
|
+
putReq.onsuccess = () => {
|
|
128
|
+
let updateType: DbUpdateType | null = null
|
|
129
|
+
if (!oldItem) {
|
|
130
|
+
updateType = 'added'
|
|
131
|
+
} else if (!deepEqual(oldItem, item)) {
|
|
132
|
+
updateType = 'updated'
|
|
133
|
+
}
|
|
134
|
+
if (updateType) {
|
|
135
|
+
try {
|
|
136
|
+
this.notifyUpdate(keyValue, updateType, oldItem, item)
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error('notifyUpdate failed', err)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
resolve(keyValue)
|
|
142
|
+
}
|
|
143
|
+
putReq.onerror = () => reject(putReq.error)
|
|
144
|
+
}
|
|
145
|
+
getReq.onerror = () => reject(getReq.error)
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async del(keyValue: T[K]): Promise<void> {
|
|
150
|
+
const oldItem = await this.get(keyValue)
|
|
151
|
+
const store = await this.getStore('readwrite')
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
const req = store.delete(keyValue)
|
|
154
|
+
req.onsuccess = () => {
|
|
155
|
+
if (oldItem) {
|
|
156
|
+
try {
|
|
157
|
+
this.notifyUpdate(keyValue, 'removed', oldItem, undefined)
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error('notifyUpdate failed', err)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
resolve()
|
|
163
|
+
}
|
|
164
|
+
req.onerror = () => reject(req.error)
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private notifyUpdate(keyValue: T[K], updateType: DbUpdateType, oldItem?: T, newItem?: T): void {
|
|
169
|
+
this.listeners.forEach((listener) => listener(keyValue, updateType, oldItem, newItem))
|
|
170
|
+
if (this.broadcastChannel) {
|
|
171
|
+
this.broadcastChannel.postMessage({ keyValue, updateType, oldItem, newItem })
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
addListener(listener: DbUpdateListener<T, K>): () => void {
|
|
176
|
+
this.listeners.push(listener)
|
|
177
|
+
return () => this.removeListener(listener)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
removeListener(listener: DbUpdateListener<T, K>): void {
|
|
181
|
+
this.listeners = this.listeners.filter((l) => l !== listener)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public async close(): Promise<void> {
|
|
185
|
+
if (this._db) {
|
|
186
|
+
this._db.close()
|
|
187
|
+
this._db = null
|
|
188
|
+
}
|
|
189
|
+
if (this.broadcastChannel) {
|
|
190
|
+
this.broadcastChannel.close()
|
|
191
|
+
this.broadcastChannel = undefined
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
package/src/dbs/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { AuthCommitment } from './auth-commitments.js'
|
|
2
|
+
export { AuthCommitments } from './auth-commitments.js'
|
|
3
|
+
|
|
4
|
+
export type { AuthKey } from './auth-keys.js'
|
|
5
|
+
export { AuthKeys } from './auth-keys.js'
|
|
6
|
+
|
|
7
|
+
export type { DbUpdateType, DbUpdateListener, Migration } from './generic.js'
|
|
8
|
+
export { Generic } from './generic.js'
|
|
9
|
+
export { Messages } from './messages.js'
|
|
10
|
+
export { Signatures } from './signatures.js'
|
|
11
|
+
export { Transactions } from './transactions.js'
|
|
12
|
+
export { Wallets } from './wallets.js'
|
|
13
|
+
export { Recovery } from './recovery.js'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Message } from '../sequence/types/message-request.js'
|
|
2
|
+
import { Generic } from './generic.js'
|
|
3
|
+
|
|
4
|
+
const TABLE_NAME = 'messages'
|
|
5
|
+
|
|
6
|
+
export class Messages extends Generic<Message, 'id'> {
|
|
7
|
+
constructor(dbName: string = 'sequence-messages') {
|
|
8
|
+
super(dbName, TABLE_NAME, 'id', [
|
|
9
|
+
(db: IDBDatabase) => {
|
|
10
|
+
if (!db.objectStoreNames.contains(TABLE_NAME)) {
|
|
11
|
+
db.createObjectStore(TABLE_NAME)
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
])
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Generic } from './generic.js'
|
|
2
|
+
import { QueuedRecoveryPayload } from '../sequence/types/recovery.js'
|
|
3
|
+
|
|
4
|
+
const TABLE_NAME = 'queued-recovery-payloads'
|
|
5
|
+
export class Recovery extends Generic<QueuedRecoveryPayload, 'id'> {
|
|
6
|
+
constructor(dbName: string = 'sequence-recovery') {
|
|
7
|
+
super(dbName, TABLE_NAME, 'id', [
|
|
8
|
+
(db: IDBDatabase) => {
|
|
9
|
+
if (!db.objectStoreNames.contains(TABLE_NAME)) {
|
|
10
|
+
db.createObjectStore(TABLE_NAME)
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
])
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseSignatureRequest } from '../sequence/index.js'
|
|
2
|
+
import { Generic } from './generic.js'
|
|
3
|
+
|
|
4
|
+
const TABLE_NAME = 'envelopes'
|
|
5
|
+
export class Signatures extends Generic<BaseSignatureRequest, 'id'> {
|
|
6
|
+
constructor(dbName: string = 'sequence-signature-requests') {
|
|
7
|
+
super(dbName, TABLE_NAME, 'id', [
|
|
8
|
+
(db: IDBDatabase) => {
|
|
9
|
+
if (!db.objectStoreNames.contains(TABLE_NAME)) {
|
|
10
|
+
db.createObjectStore(TABLE_NAME)
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
])
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Transaction } from '../sequence/types/transaction-request.js'
|
|
2
|
+
import { Generic } from './generic.js'
|
|
3
|
+
|
|
4
|
+
const TABLE_NAME = 'transactions'
|
|
5
|
+
|
|
6
|
+
export class Transactions extends Generic<Transaction, 'id'> {
|
|
7
|
+
constructor(dbName: string = 'sequence-transactions') {
|
|
8
|
+
super(dbName, TABLE_NAME, 'id', [
|
|
9
|
+
(db: IDBDatabase) => {
|
|
10
|
+
if (!db.objectStoreNames.contains(TABLE_NAME)) {
|
|
11
|
+
db.createObjectStore(TABLE_NAME)
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
])
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Generic } from './generic.js'
|
|
2
|
+
import { Wallet } from '../sequence/types/wallet.js'
|
|
3
|
+
|
|
4
|
+
const TABLE_NAME = 'wallets'
|
|
5
|
+
|
|
6
|
+
export class Wallets extends Generic<Wallet, 'address'> {
|
|
7
|
+
constructor(dbName: string = 'sequence-manager') {
|
|
8
|
+
super(dbName, TABLE_NAME, 'address', [
|
|
9
|
+
(db: IDBDatabase) => {
|
|
10
|
+
if (!db.objectStoreNames.contains(TABLE_NAME)) {
|
|
11
|
+
db.createObjectStore(TABLE_NAME)
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
])
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Address, Signature, Hex, Bytes, PersonalMessage } from 'ox'
|
|
2
|
+
import { Signers, State } from '@0xsequence/wallet-core'
|
|
3
|
+
import { IdentityInstrument, KeyType } from '@0xsequence/identity-instrument'
|
|
4
|
+
import { AuthKey } from '../dbs/auth-keys.js'
|
|
5
|
+
import { Payload, Signature as SequenceSignature } from '@0xsequence/wallet-primitives'
|
|
6
|
+
import * as Identity from '@0xsequence/identity-instrument'
|
|
7
|
+
|
|
8
|
+
export function toIdentityAuthKey(authKey: AuthKey): Identity.AuthKey {
|
|
9
|
+
return {
|
|
10
|
+
address: authKey.address,
|
|
11
|
+
keyType: Identity.KeyType.P256R1,
|
|
12
|
+
signer: authKey.identitySigner,
|
|
13
|
+
async sign(digest: Bytes.Bytes) {
|
|
14
|
+
const authKeySignature = await window.crypto.subtle.sign(
|
|
15
|
+
{
|
|
16
|
+
name: 'ECDSA',
|
|
17
|
+
hash: 'SHA-256',
|
|
18
|
+
},
|
|
19
|
+
authKey.privateKey,
|
|
20
|
+
digest,
|
|
21
|
+
)
|
|
22
|
+
return Hex.fromBytes(new Uint8Array(authKeySignature))
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class IdentitySigner implements Signers.Signer {
|
|
28
|
+
constructor(
|
|
29
|
+
readonly identityInstrument: IdentityInstrument,
|
|
30
|
+
readonly authKey: AuthKey,
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
get address(): `0x${string}` {
|
|
34
|
+
if (!Address.validate(this.authKey.identitySigner)) {
|
|
35
|
+
throw new Error('No signer address found')
|
|
36
|
+
}
|
|
37
|
+
return Address.checksum(this.authKey.identitySigner)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async sign(
|
|
41
|
+
wallet: Address.Address,
|
|
42
|
+
chainId: bigint,
|
|
43
|
+
payload: Payload.Parented,
|
|
44
|
+
): Promise<SequenceSignature.SignatureOfSignerLeaf> {
|
|
45
|
+
const payloadHash = Payload.hash(wallet, chainId, payload)
|
|
46
|
+
return this.signDigest(payloadHash)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async signDigest(digest: Bytes.Bytes): Promise<SequenceSignature.SignatureOfSignerLeafHash> {
|
|
50
|
+
const sigHex = await this.identityInstrument.sign(toIdentityAuthKey(this.authKey), digest)
|
|
51
|
+
const sig = Signature.fromHex(sigHex)
|
|
52
|
+
return {
|
|
53
|
+
type: 'hash',
|
|
54
|
+
...sig,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: Object): Promise<void> {
|
|
59
|
+
const payload = Payload.fromMessage(
|
|
60
|
+
Hex.fromString(
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
action: 'consent-to-be-part-of-wallet',
|
|
63
|
+
wallet,
|
|
64
|
+
signer: this.address,
|
|
65
|
+
timestamp: Date.now(),
|
|
66
|
+
...extra,
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const signature = await this.sign(wallet, 0n, payload)
|
|
72
|
+
await stateWriter.saveWitnesses(wallet, 0n, payload, {
|
|
73
|
+
type: 'unrecovered-signer',
|
|
74
|
+
weight: 1n,
|
|
75
|
+
signature,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Shared } from './manager.js'
|
|
2
|
+
|
|
3
|
+
interface CronJob {
|
|
4
|
+
id: string
|
|
5
|
+
interval: number
|
|
6
|
+
lastRun: number
|
|
7
|
+
handler: () => Promise<void>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class Cron {
|
|
11
|
+
private jobs: Map<string, CronJob> = new Map()
|
|
12
|
+
private checkInterval?: ReturnType<typeof setInterval>
|
|
13
|
+
private readonly STORAGE_KEY = 'sequence-cron-jobs'
|
|
14
|
+
private isStopping: boolean = false
|
|
15
|
+
private currentCheckJobsPromise: Promise<void> = Promise.resolve()
|
|
16
|
+
|
|
17
|
+
constructor(private readonly shared: Shared) {
|
|
18
|
+
this.start()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private start() {
|
|
22
|
+
if (this.isStopping) return
|
|
23
|
+
this.executeCheckJobsChain()
|
|
24
|
+
this.checkInterval = setInterval(() => this.executeCheckJobsChain(), 60 * 1000)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Wraps checkJobs to chain executions and manage currentCheckJobsPromise
|
|
28
|
+
private executeCheckJobsChain(): void {
|
|
29
|
+
this.currentCheckJobsPromise = this.currentCheckJobsPromise
|
|
30
|
+
.catch(() => {}) // Ignore errors from previous chain link for sequencing
|
|
31
|
+
.then(() => {
|
|
32
|
+
if (!this.isStopping) {
|
|
33
|
+
return this.checkJobs()
|
|
34
|
+
}
|
|
35
|
+
return Promise.resolve()
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async stop(): Promise<void> {
|
|
40
|
+
this.isStopping = true
|
|
41
|
+
|
|
42
|
+
if (this.checkInterval) {
|
|
43
|
+
clearInterval(this.checkInterval)
|
|
44
|
+
this.checkInterval = undefined
|
|
45
|
+
this.shared.modules.logger.log('Cron: Interval cleared.')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Wait for the promise of the last (or current) checkJobs execution
|
|
49
|
+
await this.currentCheckJobsPromise.catch((err) => {
|
|
50
|
+
console.error('Cron: Error during currentCheckJobsPromise settlement in stop():', err)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
registerJob(id: string, interval: number, handler: () => Promise<void>) {
|
|
55
|
+
if (this.jobs.has(id)) {
|
|
56
|
+
throw new Error(`Job with ID ${id} already exists`)
|
|
57
|
+
}
|
|
58
|
+
const job: CronJob = { id, interval, lastRun: 0, handler }
|
|
59
|
+
this.jobs.set(id, job)
|
|
60
|
+
// No syncWithStorage needed here, it happens in checkJobs
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
unregisterJob(id: string) {
|
|
64
|
+
this.jobs.delete(id)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async checkJobs(): Promise<void> {
|
|
68
|
+
if (this.isStopping) {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await navigator.locks.request('sequence-cron-jobs', async (lock: Lock | null) => {
|
|
74
|
+
if (this.isStopping) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
if (!lock) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const now = Date.now()
|
|
82
|
+
const storage = await this.getStorageState()
|
|
83
|
+
|
|
84
|
+
for (const [id, job] of this.jobs) {
|
|
85
|
+
if (this.isStopping) {
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const lastRun = storage.get(id)?.lastRun ?? job.lastRun
|
|
90
|
+
const timeSinceLastRun = now - lastRun
|
|
91
|
+
|
|
92
|
+
if (timeSinceLastRun >= job.interval) {
|
|
93
|
+
try {
|
|
94
|
+
await job.handler()
|
|
95
|
+
if (!this.isStopping) {
|
|
96
|
+
job.lastRun = now
|
|
97
|
+
storage.set(id, { lastRun: now })
|
|
98
|
+
} else {
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
102
|
+
this.shared.modules.logger.log(`Cron: Job ${id} was aborted.`)
|
|
103
|
+
} else {
|
|
104
|
+
console.error(`Cron job ${id} failed:`, error)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!this.isStopping) {
|
|
111
|
+
await this.syncWithStorage()
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
116
|
+
this.shared.modules.logger.log('Cron: navigator.locks.request was aborted.')
|
|
117
|
+
} else {
|
|
118
|
+
console.error('Cron: Error in navigator.locks.request:', error)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async getStorageState(): Promise<Map<string, { lastRun: number }>> {
|
|
124
|
+
if (this.isStopping) return new Map()
|
|
125
|
+
const state = localStorage.getItem(this.STORAGE_KEY)
|
|
126
|
+
return new Map(state ? JSON.parse(state) : [])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async syncWithStorage() {
|
|
130
|
+
if (this.isStopping) return
|
|
131
|
+
const state = Array.from(this.jobs.entries()).map(([id, job]) => [id, { lastRun: job.lastRun }])
|
|
132
|
+
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state))
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Signers } from '@0xsequence/wallet-core'
|
|
2
|
+
import { Address } from 'ox'
|
|
3
|
+
import { Kinds, WitnessExtraSignerKind } from './types/signer.js'
|
|
4
|
+
import { Shared } from './manager.js'
|
|
5
|
+
|
|
6
|
+
export class Devices {
|
|
7
|
+
constructor(private readonly shared: Shared) {}
|
|
8
|
+
|
|
9
|
+
async list() {
|
|
10
|
+
return this.shared.databases.encryptedPks.listAddresses()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async has(address: Address.Address) {
|
|
14
|
+
const entry = await this.shared.databases.encryptedPks.getEncryptedEntry(address)
|
|
15
|
+
return entry !== undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async create() {
|
|
19
|
+
const e = await this.shared.databases.encryptedPks.generateAndStore()
|
|
20
|
+
const s = await this.shared.databases.encryptedPks.getEncryptedPkStore(e.address)
|
|
21
|
+
|
|
22
|
+
if (!s) {
|
|
23
|
+
throw new Error('Failed to create session')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.shared.modules.logger.log('Created new session:', s.address)
|
|
27
|
+
return new Signers.Pk.Pk(s)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async get(address: Address.Address) {
|
|
31
|
+
const s = await this.shared.databases.encryptedPks.getEncryptedPkStore(address)
|
|
32
|
+
if (!s) {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new Signers.Pk.Pk(s)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async witness(address: Address.Address, wallet: Address.Address) {
|
|
40
|
+
const signer = await this.get(address)
|
|
41
|
+
if (!signer) {
|
|
42
|
+
throw new Error('Signer not found')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await signer.witness(this.shared.sequence.stateProvider, wallet, {
|
|
46
|
+
signerKind: Kinds.LocalDevice,
|
|
47
|
+
} as WitnessExtraSignerKind)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async remove(address: Address.Address) {
|
|
51
|
+
await this.shared.databases.encryptedPks.remove(address)
|
|
52
|
+
}
|
|
53
|
+
}
|