@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.
Files changed (173) hide show
  1. package/.env.test +3 -0
  2. package/.turbo/turbo-build.log +5 -0
  3. package/CHANGELOG.md +11 -0
  4. package/LICENSE +202 -0
  5. package/dist/dbs/auth-commitments.d.ts +17 -0
  6. package/dist/dbs/auth-commitments.d.ts.map +1 -0
  7. package/dist/dbs/auth-commitments.js +13 -0
  8. package/dist/dbs/auth-keys.d.ts +19 -0
  9. package/dist/dbs/auth-keys.d.ts.map +1 -0
  10. package/dist/dbs/auth-keys.js +67 -0
  11. package/dist/dbs/generic.d.ts +33 -0
  12. package/dist/dbs/generic.d.ts.map +1 -0
  13. package/dist/dbs/generic.js +170 -0
  14. package/dist/dbs/index.d.ts +12 -0
  15. package/dist/dbs/index.d.ts.map +1 -0
  16. package/dist/dbs/index.js +8 -0
  17. package/dist/dbs/messages.d.ts +6 -0
  18. package/dist/dbs/messages.d.ts.map +1 -0
  19. package/dist/dbs/messages.js +13 -0
  20. package/dist/dbs/recovery.d.ts +6 -0
  21. package/dist/dbs/recovery.d.ts.map +1 -0
  22. package/dist/dbs/recovery.js +13 -0
  23. package/dist/dbs/signatures.d.ts +6 -0
  24. package/dist/dbs/signatures.d.ts.map +1 -0
  25. package/dist/dbs/signatures.js +13 -0
  26. package/dist/dbs/transactions.d.ts +6 -0
  27. package/dist/dbs/transactions.d.ts.map +1 -0
  28. package/dist/dbs/transactions.js +13 -0
  29. package/dist/dbs/wallets.d.ts +6 -0
  30. package/dist/dbs/wallets.d.ts.map +1 -0
  31. package/dist/dbs/wallets.js +13 -0
  32. package/dist/identity/signer.d.ts +17 -0
  33. package/dist/identity/signer.d.ts.map +1 -0
  34. package/dist/identity/signer.js +58 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +2 -0
  38. package/dist/sequence/cron.d.ts +19 -0
  39. package/dist/sequence/cron.d.ts.map +1 -0
  40. package/dist/sequence/cron.js +118 -0
  41. package/dist/sequence/devices.d.ts +14 -0
  42. package/dist/sequence/devices.d.ts.map +1 -0
  43. package/dist/sequence/devices.js +43 -0
  44. package/dist/sequence/handlers/authcode-pkce.d.ts +14 -0
  45. package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -0
  46. package/dist/sequence/handlers/authcode-pkce.js +48 -0
  47. package/dist/sequence/handlers/authcode.d.ts +25 -0
  48. package/dist/sequence/handlers/authcode.d.ts.map +1 -0
  49. package/dist/sequence/handlers/authcode.js +91 -0
  50. package/dist/sequence/handlers/devices.d.ts +14 -0
  51. package/dist/sequence/handlers/devices.d.ts.map +1 -0
  52. package/dist/sequence/handlers/devices.js +39 -0
  53. package/dist/sequence/handlers/handler.d.ts +8 -0
  54. package/dist/sequence/handlers/handler.d.ts.map +1 -0
  55. package/dist/sequence/handlers/handler.js +1 -0
  56. package/dist/sequence/handlers/identity.d.ts +21 -0
  57. package/dist/sequence/handlers/identity.d.ts.map +1 -0
  58. package/dist/sequence/handlers/identity.js +86 -0
  59. package/dist/sequence/handlers/index.d.ts +7 -0
  60. package/dist/sequence/handlers/index.d.ts.map +1 -0
  61. package/dist/sequence/handlers/index.js +5 -0
  62. package/dist/sequence/handlers/mnemonic.d.ts +19 -0
  63. package/dist/sequence/handlers/mnemonic.d.ts.map +1 -0
  64. package/dist/sequence/handlers/mnemonic.js +67 -0
  65. package/dist/sequence/handlers/otp.d.ts +20 -0
  66. package/dist/sequence/handlers/otp.d.ts.map +1 -0
  67. package/dist/sequence/handlers/otp.js +83 -0
  68. package/dist/sequence/handlers/passkeys.d.ts +17 -0
  69. package/dist/sequence/handlers/passkeys.d.ts.map +1 -0
  70. package/dist/sequence/handlers/passkeys.js +63 -0
  71. package/dist/sequence/handlers/recovery.d.ts +15 -0
  72. package/dist/sequence/handlers/recovery.d.ts.map +1 -0
  73. package/dist/sequence/handlers/recovery.js +72 -0
  74. package/dist/sequence/index.d.ts +12 -0
  75. package/dist/sequence/index.d.ts.map +1 -0
  76. package/dist/sequence/index.js +9 -0
  77. package/dist/sequence/logger.d.ts +7 -0
  78. package/dist/sequence/logger.d.ts.map +1 -0
  79. package/dist/sequence/logger.js +11 -0
  80. package/dist/sequence/manager.d.ts +287 -0
  81. package/dist/sequence/manager.d.ts.map +1 -0
  82. package/dist/sequence/manager.js +356 -0
  83. package/dist/sequence/messages.d.ts +18 -0
  84. package/dist/sequence/messages.d.ts.map +1 -0
  85. package/dist/sequence/messages.js +115 -0
  86. package/dist/sequence/recovery.d.ts +30 -0
  87. package/dist/sequence/recovery.d.ts.map +1 -0
  88. package/dist/sequence/recovery.js +314 -0
  89. package/dist/sequence/sessions.d.ts +26 -0
  90. package/dist/sequence/sessions.d.ts.map +1 -0
  91. package/dist/sequence/sessions.js +169 -0
  92. package/dist/sequence/signatures.d.ts +21 -0
  93. package/dist/sequence/signatures.d.ts.map +1 -0
  94. package/dist/sequence/signatures.js +192 -0
  95. package/dist/sequence/signers.d.ts +14 -0
  96. package/dist/sequence/signers.d.ts.map +1 -0
  97. package/dist/sequence/signers.js +74 -0
  98. package/dist/sequence/transactions.d.ts +26 -0
  99. package/dist/sequence/transactions.d.ts.map +1 -0
  100. package/dist/sequence/transactions.js +201 -0
  101. package/dist/sequence/types/index.d.ts +9 -0
  102. package/dist/sequence/types/index.d.ts.map +1 -0
  103. package/dist/sequence/types/index.js +2 -0
  104. package/dist/sequence/types/message-request.d.ts +23 -0
  105. package/dist/sequence/types/message-request.d.ts.map +1 -0
  106. package/dist/sequence/types/message-request.js +1 -0
  107. package/dist/sequence/types/recovery.d.ts +15 -0
  108. package/dist/sequence/types/recovery.d.ts.map +1 -0
  109. package/dist/sequence/types/recovery.js +1 -0
  110. package/dist/sequence/types/signature-request.d.ts +76 -0
  111. package/dist/sequence/types/signature-request.d.ts.map +1 -0
  112. package/dist/sequence/types/signature-request.js +11 -0
  113. package/dist/sequence/types/signer.d.ts +28 -0
  114. package/dist/sequence/types/signer.d.ts.map +1 -0
  115. package/dist/sequence/types/signer.js +10 -0
  116. package/dist/sequence/types/transaction-request.d.ts +41 -0
  117. package/dist/sequence/types/transaction-request.d.ts.map +1 -0
  118. package/dist/sequence/types/transaction-request.js +1 -0
  119. package/dist/sequence/types/wallet.d.ts +21 -0
  120. package/dist/sequence/types/wallet.d.ts.map +1 -0
  121. package/dist/sequence/types/wallet.js +1 -0
  122. package/dist/sequence/wallets.d.ts +121 -0
  123. package/dist/sequence/wallets.d.ts.map +1 -0
  124. package/dist/sequence/wallets.js +632 -0
  125. package/package.json +40 -0
  126. package/src/dbs/auth-commitments.ts +26 -0
  127. package/src/dbs/auth-keys.ts +85 -0
  128. package/src/dbs/generic.ts +194 -0
  129. package/src/dbs/index.ts +13 -0
  130. package/src/dbs/messages.ts +16 -0
  131. package/src/dbs/recovery.ts +15 -0
  132. package/src/dbs/signatures.ts +15 -0
  133. package/src/dbs/transactions.ts +16 -0
  134. package/src/dbs/wallets.ts +16 -0
  135. package/src/identity/signer.ts +78 -0
  136. package/src/index.ts +2 -0
  137. package/src/sequence/cron.ts +134 -0
  138. package/src/sequence/devices.ts +53 -0
  139. package/src/sequence/handlers/authcode-pkce.ts +70 -0
  140. package/src/sequence/handlers/authcode.ts +116 -0
  141. package/src/sequence/handlers/devices.ts +53 -0
  142. package/src/sequence/handlers/handler.ts +14 -0
  143. package/src/sequence/handlers/identity.ts +101 -0
  144. package/src/sequence/handlers/index.ts +6 -0
  145. package/src/sequence/handlers/mnemonic.ts +88 -0
  146. package/src/sequence/handlers/otp.ts +107 -0
  147. package/src/sequence/handlers/passkeys.ts +84 -0
  148. package/src/sequence/handlers/recovery.ts +88 -0
  149. package/src/sequence/index.ts +25 -0
  150. package/src/sequence/logger.ts +11 -0
  151. package/src/sequence/manager.ts +634 -0
  152. package/src/sequence/messages.ts +146 -0
  153. package/src/sequence/recovery.ts +429 -0
  154. package/src/sequence/sessions.ts +238 -0
  155. package/src/sequence/signatures.ts +263 -0
  156. package/src/sequence/signers.ts +88 -0
  157. package/src/sequence/transactions.ts +281 -0
  158. package/src/sequence/types/index.ts +27 -0
  159. package/src/sequence/types/message-request.ts +26 -0
  160. package/src/sequence/types/recovery.ts +15 -0
  161. package/src/sequence/types/signature-request.ts +89 -0
  162. package/src/sequence/types/signer.ts +32 -0
  163. package/src/sequence/types/transaction-request.ts +47 -0
  164. package/src/sequence/types/wallet.ts +24 -0
  165. package/src/sequence/wallets.ts +853 -0
  166. package/test/constants.ts +62 -0
  167. package/test/recovery.test.ts +211 -0
  168. package/test/sessions.test.ts +324 -0
  169. package/test/setup.ts +63 -0
  170. package/test/transactions.test.ts +464 -0
  171. package/test/wallets.test.ts +381 -0
  172. package/tsconfig.json +10 -0
  173. 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
+ }
@@ -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,2 @@
1
+ export * as Identity from './identity/signer.js'
2
+ export * as Sequence from './sequence/index.js'
@@ -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
+ }