@0xsequence/wallet-core 3.0.0-beta.8 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-lint.log +4 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/CHANGELOG.md +158 -0
  5. package/dist/bundler/bundlers/pimlico.d.ts +3 -2
  6. package/dist/bundler/bundlers/pimlico.d.ts.map +1 -1
  7. package/dist/bundler/bundlers/pimlico.js +9 -3
  8. package/dist/env.d.ts +22 -0
  9. package/dist/env.d.ts.map +1 -0
  10. package/dist/env.js +42 -0
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/signers/index.d.ts +1 -1
  15. package/dist/signers/passkey.d.ts +11 -3
  16. package/dist/signers/passkey.d.ts.map +1 -1
  17. package/dist/signers/passkey.js +12 -6
  18. package/dist/signers/pk/encrypted.d.ts +13 -2
  19. package/dist/signers/pk/encrypted.d.ts.map +1 -1
  20. package/dist/signers/pk/encrypted.js +94 -15
  21. package/dist/signers/pk/index.d.ts +1 -1
  22. package/dist/signers/session/explicit.d.ts.map +1 -1
  23. package/dist/signers/session/explicit.js +2 -2
  24. package/dist/signers/session/implicit.d.ts.map +1 -1
  25. package/dist/signers/session/implicit.js +1 -1
  26. package/dist/state/local/index.d.ts.map +1 -1
  27. package/dist/state/local/index.js +3 -2
  28. package/dist/state/local/indexed-db.d.ts +4 -1
  29. package/dist/state/local/indexed-db.d.ts.map +1 -1
  30. package/dist/state/local/indexed-db.js +12 -2
  31. package/dist/state/remote/dev-http.d.ts +2 -1
  32. package/dist/state/remote/dev-http.d.ts.map +1 -1
  33. package/dist/state/remote/dev-http.js +11 -5
  34. package/dist/state/sequence/index.d.ts +2 -1
  35. package/dist/state/sequence/index.d.ts.map +1 -1
  36. package/dist/state/sequence/index.js +14 -5
  37. package/dist/wallet.js +2 -2
  38. package/eslint.config.js +12 -0
  39. package/package.json +12 -10
  40. package/src/bundler/bundlers/pimlico.ts +10 -4
  41. package/src/env.ts +68 -0
  42. package/src/index.ts +1 -0
  43. package/src/signers/index.ts +1 -1
  44. package/src/signers/passkey.ts +21 -5
  45. package/src/signers/pk/encrypted.ts +103 -14
  46. package/src/signers/pk/index.ts +1 -1
  47. package/src/signers/session/explicit.ts +2 -9
  48. package/src/signers/session/implicit.ts +1 -2
  49. package/src/state/local/index.ts +4 -2
  50. package/src/state/local/indexed-db.ts +15 -2
  51. package/src/state/remote/dev-http.ts +11 -5
  52. package/src/state/sequence/index.ts +15 -6
  53. package/src/wallet.ts +2 -2
  54. package/test/constants.ts +2 -0
  55. package/test/envelope.test.ts +0 -1
  56. package/test/signers-pk.test.ts +1 -1
  57. package/test/signers-session-implicit.test.ts +0 -2
  58. package/test/state/debug.test.ts +2 -3
package/src/env.ts ADDED
@@ -0,0 +1,68 @@
1
+ export type StorageLike = {
2
+ getItem: (key: string) => string | null
3
+ setItem: (key: string, value: string) => void
4
+ removeItem: (key: string) => void
5
+ }
6
+
7
+ export type CryptoLike = {
8
+ subtle: SubtleCrypto
9
+ getRandomValues: <T extends ArrayBufferView>(array: T) => T
10
+ }
11
+
12
+ export type TextEncodingLike = {
13
+ TextEncoder: typeof TextEncoder
14
+ TextDecoder: typeof TextDecoder
15
+ }
16
+
17
+ export type CoreEnv = {
18
+ fetch?: typeof fetch
19
+ crypto?: CryptoLike
20
+ storage?: StorageLike
21
+ indexedDB?: IDBFactory
22
+ text?: Partial<TextEncodingLike>
23
+ }
24
+
25
+ function isStorageLike(value: unknown): value is StorageLike {
26
+ if (!value || typeof value !== 'object') return false
27
+ const candidate = value as StorageLike
28
+ return (
29
+ typeof candidate.getItem === 'function' &&
30
+ typeof candidate.setItem === 'function' &&
31
+ typeof candidate.removeItem === 'function'
32
+ )
33
+ }
34
+
35
+ export function resolveCoreEnv(env?: CoreEnv): CoreEnv {
36
+ const globalObj = globalThis as any
37
+ const windowObj = typeof window !== 'undefined' ? window : (globalObj.window ?? {})
38
+ let storage: StorageLike | undefined
39
+ let text: Partial<TextEncodingLike> | undefined
40
+
41
+ if (isStorageLike(env?.storage)) {
42
+ storage = env.storage
43
+ } else if (isStorageLike(windowObj.localStorage)) {
44
+ storage = windowObj.localStorage
45
+ } else if (isStorageLike(globalObj.localStorage)) {
46
+ storage = globalObj.localStorage
47
+ }
48
+
49
+ if (env?.text) {
50
+ if (!env.text.TextEncoder || !env.text.TextDecoder) {
51
+ throw new Error('env.text must provide both TextEncoder and TextDecoder')
52
+ }
53
+ text = env.text
54
+ } else {
55
+ text = {
56
+ TextEncoder: windowObj.TextEncoder ?? globalObj.TextEncoder,
57
+ TextDecoder: windowObj.TextDecoder ?? globalObj.TextDecoder,
58
+ }
59
+ }
60
+
61
+ return {
62
+ fetch: env?.fetch ?? windowObj.fetch ?? globalObj.fetch,
63
+ crypto: env?.crypto ?? windowObj.crypto ?? globalObj.crypto,
64
+ storage,
65
+ indexedDB: env?.indexedDB ?? windowObj.indexedDB ?? globalObj.indexedDB,
66
+ text,
67
+ }
68
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export * as State from './state/index.js'
5
5
  export * as Bundler from './bundler/index.js'
6
6
  export * as Envelope from './envelope.js'
7
7
  export * as Utils from './utils/index.js'
8
+ export * from './env.js'
8
9
  export {
9
10
  type ExplicitSessionConfig,
10
11
  type ExplicitSession,
@@ -31,7 +31,7 @@ export interface SapientSigner {
31
31
  }
32
32
 
33
33
  export interface Witnessable {
34
- witness: (stateWriter: State.Writer, wallet: Address.Address, extra?: Object) => Promise<void>
34
+ witness: (stateWriter: State.Writer, wallet: Address.Address, extra?: object) => Promise<void>
35
35
  }
36
36
 
37
37
  type MaybePromise<T> = T | Promise<T>
@@ -5,12 +5,15 @@ import { WebAuthnP256 } from 'ox'
5
5
  import { State } from '../index.js'
6
6
  import { SapientSigner, Witnessable } from './index.js'
7
7
 
8
+ export type WebAuthnLike = Pick<typeof WebAuthnP256, 'createCredential' | 'sign'>
9
+
8
10
  export type PasskeyOptions = {
9
11
  extensions: Pick<Extensions.Extensions, 'passkeys'>
10
12
  publicKey: Extensions.Passkeys.PublicKey
11
13
  credentialId: string
12
14
  embedMetadata?: boolean
13
15
  metadata?: Extensions.Passkeys.PasskeyMetadata
16
+ webauthn?: WebAuthnLike
14
17
  }
15
18
 
16
19
  export type CreatePasskeyOptions = {
@@ -18,6 +21,11 @@ export type CreatePasskeyOptions = {
18
21
  requireUserVerification?: boolean
19
22
  credentialName?: string
20
23
  embedMetadata?: boolean
24
+ webauthn?: WebAuthnLike
25
+ }
26
+
27
+ export type FindPasskeyOptions = {
28
+ webauthn?: WebAuthnLike
21
29
  }
22
30
 
23
31
  export type WitnessMessage = {
@@ -45,6 +53,7 @@ export class Passkey implements SapientSigner, Witnessable {
45
53
  public readonly imageHash: Hex.Hex
46
54
  public readonly embedMetadata: boolean
47
55
  public readonly metadata?: Extensions.Passkeys.PasskeyMetadata
56
+ private readonly webauthn: WebAuthnLike
48
57
 
49
58
  constructor(options: PasskeyOptions) {
50
59
  this.address = options.extensions.passkeys
@@ -53,6 +62,7 @@ export class Passkey implements SapientSigner, Witnessable {
53
62
  this.embedMetadata = options.embedMetadata ?? false
54
63
  this.imageHash = Extensions.Passkeys.rootFor(options.publicKey)
55
64
  this.metadata = options.metadata
65
+ this.webauthn = options.webauthn ?? WebAuthnP256
56
66
  }
57
67
 
58
68
  static async loadFromWitness(
@@ -60,6 +70,7 @@ export class Passkey implements SapientSigner, Witnessable {
60
70
  extensions: Pick<Extensions.Extensions, 'passkeys'>,
61
71
  wallet: Address.Address,
62
72
  imageHash: Hex.Hex,
73
+ options?: FindPasskeyOptions,
63
74
  ) {
64
75
  // In the witness we will find the public key, and may find the credential id
65
76
  const witness = await stateReader.getWitnessForSapient(wallet, extensions.passkeys, imageHash)
@@ -90,13 +101,15 @@ export class Passkey implements SapientSigner, Witnessable {
90
101
  publicKey: message.publicKey,
91
102
  embedMetadata: decodedSignature.embedMetadata,
92
103
  metadata,
104
+ webauthn: options?.webauthn,
93
105
  })
94
106
  }
95
107
 
96
108
  static async create(extensions: Pick<Extensions.Extensions, 'passkeys'>, options?: CreatePasskeyOptions) {
109
+ const webauthn = options?.webauthn ?? WebAuthnP256
97
110
  const name = options?.credentialName ?? `Sequence (${Date.now()})`
98
111
 
99
- const credential = await WebAuthnP256.createCredential({
112
+ const credential = await webauthn.createCredential({
100
113
  user: {
101
114
  name,
102
115
  },
@@ -120,6 +133,7 @@ export class Passkey implements SapientSigner, Witnessable {
120
133
  },
121
134
  embedMetadata: options?.embedMetadata,
122
135
  metadata,
136
+ webauthn,
123
137
  })
124
138
 
125
139
  if (options?.stateProvider) {
@@ -132,8 +146,10 @@ export class Passkey implements SapientSigner, Witnessable {
132
146
  static async find(
133
147
  stateReader: State.Reader,
134
148
  extensions: Pick<Extensions.Extensions, 'passkeys'>,
149
+ options?: FindPasskeyOptions,
135
150
  ): Promise<Passkey | undefined> {
136
- const response = await WebAuthnP256.sign({ challenge: Hex.random(32) })
151
+ const webauthn = options?.webauthn ?? WebAuthnP256
152
+ const response = await webauthn.sign({ challenge: Hex.random(32) })
137
153
  if (!response.raw) throw new Error('No credential returned')
138
154
 
139
155
  const authenticatorDataBytes = Bytes.fromHex(response.metadata.authenticatorData)
@@ -218,7 +234,7 @@ export class Passkey implements SapientSigner, Witnessable {
218
234
  console.warn('Multiple signers found for passkey', flattened)
219
235
  }
220
236
 
221
- return Passkey.loadFromWitness(stateReader, extensions, flattened[0]!.wallet, flattened[0]!.imageHash)
237
+ return Passkey.loadFromWitness(stateReader, extensions, flattened[0]!.wallet, flattened[0]!.imageHash, options)
222
238
  }
223
239
 
224
240
  async signSapient(
@@ -234,7 +250,7 @@ export class Passkey implements SapientSigner, Witnessable {
234
250
 
235
251
  const challenge = Hex.fromBytes(Payload.hash(wallet, chainId, payload))
236
252
 
237
- const response = await WebAuthnP256.sign({
253
+ const response = await this.webauthn.sign({
238
254
  challenge,
239
255
  credentialId: this.credentialId,
240
256
  userVerification: this.publicKey.requireUserVerification ? 'required' : 'discouraged',
@@ -260,7 +276,7 @@ export class Passkey implements SapientSigner, Witnessable {
260
276
  }
261
277
  }
262
278
 
263
- async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: Object): Promise<void> {
279
+ async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise<void> {
264
280
  const payload = Payload.fromMessage(
265
281
  Hex.fromString(
266
282
  JSON.stringify({
@@ -1,4 +1,5 @@
1
1
  import { Hex, Address, PublicKey, Secp256k1, Bytes } from 'ox'
2
+ import { resolveCoreEnv, type CoreEnv, type CryptoLike, type StorageLike, type TextEncodingLike } from '../../env.js'
2
3
  import { PkStore } from './index.js'
3
4
 
4
5
  export interface EncryptedData {
@@ -17,6 +18,7 @@ export class EncryptedPksDb {
17
18
  constructor(
18
19
  private readonly localStorageKeyPrefix: string = 'e_pk_key_',
19
20
  tableName: string = 'e_pk',
21
+ private readonly env?: CoreEnv,
20
22
  ) {
21
23
  this.tableName = tableName
22
24
  }
@@ -25,9 +27,59 @@ export class EncryptedPksDb {
25
27
  return `pk_${address.toLowerCase()}`
26
28
  }
27
29
 
30
+ private getIndexedDB(): IDBFactory {
31
+ const globalObj = globalThis as any
32
+ const indexedDb = this.env?.indexedDB ?? globalObj.indexedDB ?? globalObj.window?.indexedDB
33
+ if (!indexedDb) {
34
+ throw new Error('indexedDB is not available')
35
+ }
36
+ return indexedDb
37
+ }
38
+
39
+ private getStorage(): StorageLike {
40
+ const storage = resolveCoreEnv(this.env).storage
41
+ if (!storage) {
42
+ throw new Error('storage is not available')
43
+ }
44
+ return storage
45
+ }
46
+
47
+ private getCrypto(): CryptoLike {
48
+ const globalObj = globalThis as any
49
+ const crypto = this.env?.crypto ?? globalObj.crypto ?? globalObj.window?.crypto
50
+ if (!crypto?.subtle || !crypto?.getRandomValues) {
51
+ throw new Error('crypto.subtle is not available')
52
+ }
53
+ return crypto
54
+ }
55
+
56
+ private getTextEncoderCtor(): TextEncodingLike['TextEncoder'] {
57
+ const globalObj = globalThis as any
58
+ if (this.env?.text && (!this.env.text.TextEncoder || !this.env.text.TextDecoder)) {
59
+ throw new Error('env.text must provide both TextEncoder and TextDecoder')
60
+ }
61
+ const encoderCtor = this.env?.text?.TextEncoder ?? globalObj.TextEncoder ?? globalObj.window?.TextEncoder
62
+ if (!encoderCtor) {
63
+ throw new Error('TextEncoder is not available')
64
+ }
65
+ return encoderCtor
66
+ }
67
+
68
+ private getTextDecoderCtor(): TextEncodingLike['TextDecoder'] {
69
+ const globalObj = globalThis as any
70
+ if (this.env?.text && (!this.env.text.TextEncoder || !this.env.text.TextDecoder)) {
71
+ throw new Error('env.text must provide both TextEncoder and TextDecoder')
72
+ }
73
+ const decoderCtor = this.env?.text?.TextDecoder ?? globalObj.TextDecoder ?? globalObj.window?.TextDecoder
74
+ if (!decoderCtor) {
75
+ throw new Error('TextDecoder is not available')
76
+ }
77
+ return decoderCtor
78
+ }
79
+
28
80
  private openDB(): Promise<IDBDatabase> {
29
81
  return new Promise((resolve, reject) => {
30
- const request = indexedDB.open(this.dbName, this.dbVersion)
82
+ const request = this.getIndexedDB().open(this.dbName, this.dbVersion)
31
83
  request.onupgradeneeded = () => {
32
84
  const db = request.result
33
85
  if (!db.objectStoreNames.contains(this.tableName)) {
@@ -73,7 +125,11 @@ export class EncryptedPksDb {
73
125
  }
74
126
 
75
127
  async generateAndStore(): Promise<EncryptedData> {
76
- const encryptionKey = await window.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
128
+ const crypto = this.getCrypto()
129
+ const storage = this.getStorage()
130
+ const TextEncoderCtor = this.getTextEncoderCtor()
131
+
132
+ const encryptionKey = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
77
133
  'encrypt',
78
134
  'decrypt',
79
135
  ])
@@ -84,13 +140,13 @@ export class EncryptedPksDb {
84
140
  const address = Address.fromPublicKey(publicKey)
85
141
  const keyPointer = this.localStorageKeyPrefix + address
86
142
 
87
- const exportedKey = await window.crypto.subtle.exportKey('jwk', encryptionKey)
88
- window.localStorage.setItem(keyPointer, JSON.stringify(exportedKey))
143
+ const exportedKey = await crypto.subtle.exportKey('jwk', encryptionKey)
144
+ storage.setItem(keyPointer, JSON.stringify(exportedKey))
89
145
 
90
- const encoder = new TextEncoder()
146
+ const encoder = new TextEncoderCtor()
91
147
  const encodedPk = encoder.encode(privateKey)
92
- const iv = window.crypto.getRandomValues(new Uint8Array(12))
93
- const encryptedBuffer = await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, encodedPk)
148
+ const iv = crypto.getRandomValues(new Uint8Array(12))
149
+ const encryptedBuffer = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, encodedPk)
94
150
 
95
151
  const encrypted: EncryptedData = {
96
152
  iv,
@@ -113,7 +169,7 @@ export class EncryptedPksDb {
113
169
  async getEncryptedPkStore(address: Address.Address): Promise<EncryptedPkStore | undefined> {
114
170
  const entry = await this.getEncryptedEntry(address)
115
171
  if (!entry) return
116
- return new EncryptedPkStore(entry)
172
+ return new EncryptedPkStore(entry, this.env)
117
173
  }
118
174
 
119
175
  async listAddresses(): Promise<Address.Address[]> {
@@ -125,12 +181,41 @@ export class EncryptedPksDb {
125
181
  const dbKey = this.computeDbKey(address)
126
182
  await this.putData(dbKey, undefined)
127
183
  const keyPointer = this.localStorageKeyPrefix + address
128
- window.localStorage.removeItem(keyPointer)
184
+ this.getStorage().removeItem(keyPointer)
129
185
  }
130
186
  }
131
187
 
132
188
  export class EncryptedPkStore implements PkStore {
133
- constructor(private readonly encrypted: EncryptedData) {}
189
+ constructor(
190
+ private readonly encrypted: EncryptedData,
191
+ private readonly env?: CoreEnv,
192
+ ) {}
193
+
194
+ private getStorage(): StorageLike {
195
+ const storage = resolveCoreEnv(this.env).storage
196
+ if (!storage) {
197
+ throw new Error('storage is not available')
198
+ }
199
+ return storage
200
+ }
201
+
202
+ private getCrypto(): CryptoLike {
203
+ const globalObj = globalThis as any
204
+ const crypto = this.env?.crypto ?? globalObj.crypto ?? globalObj.window?.crypto
205
+ if (!crypto?.subtle) {
206
+ throw new Error('crypto.subtle is not available')
207
+ }
208
+ return crypto
209
+ }
210
+
211
+ private getTextDecoderCtor(): TextEncodingLike['TextDecoder'] {
212
+ const globalObj = globalThis as any
213
+ const decoderCtor = this.env?.text?.TextDecoder ?? globalObj.TextDecoder ?? globalObj.window?.TextDecoder
214
+ if (!decoderCtor) {
215
+ throw new Error('TextDecoder is not available')
216
+ }
217
+ return decoderCtor
218
+ }
134
219
 
135
220
  address(): Address.Address {
136
221
  return this.encrypted.address
@@ -141,16 +226,20 @@ export class EncryptedPkStore implements PkStore {
141
226
  }
142
227
 
143
228
  async signDigest(digest: Bytes.Bytes): Promise<{ r: bigint; s: bigint; yParity: number }> {
144
- const keyJson = window.localStorage.getItem(this.encrypted.keyPointer)
229
+ const storage = this.getStorage()
230
+ const crypto = this.getCrypto()
231
+ const TextDecoderCtor = this.getTextDecoderCtor()
232
+
233
+ const keyJson = storage.getItem(this.encrypted.keyPointer)
145
234
  if (!keyJson) throw new Error('Encryption key not found in localStorage')
146
235
  const jwk = JSON.parse(keyJson)
147
- const encryptionKey = await window.crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, false, ['decrypt'])
148
- const decryptedBuffer = await window.crypto.subtle.decrypt(
236
+ const encryptionKey = await crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, false, ['decrypt'])
237
+ const decryptedBuffer = await crypto.subtle.decrypt(
149
238
  { name: 'AES-GCM', iv: this.encrypted.iv },
150
239
  encryptionKey,
151
240
  this.encrypted.data,
152
241
  )
153
- const decoder = new TextDecoder()
242
+ const decoder = new TextDecoderCtor()
154
243
  const privateKey = decoder.decode(decryptedBuffer) as Hex.Hex
155
244
  return Secp256k1.sign({ payload: digest, privateKey })
156
245
  }
@@ -52,7 +52,7 @@ export class Pk implements SignerInterface, Witnessable {
52
52
  return { ...signature, type: 'hash' }
53
53
  }
54
54
 
55
- async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: Object): Promise<void> {
55
+ async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise<void> {
56
56
  const payload = Payload.fromMessage(
57
57
  Hex.fromString(
58
58
  JSON.stringify({
@@ -1,11 +1,4 @@
1
- import {
2
- Constants,
3
- Extensions,
4
- Payload,
5
- Permission,
6
- SessionConfig,
7
- SessionSignature,
8
- } from '@0xsequence/wallet-primitives'
1
+ import { Constants, Payload, Permission, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives'
9
2
  import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider } from 'ox'
10
3
  import { MemoryPkStore, PkStore } from '../pk/index.js'
11
4
  import { ExplicitSessionSigner, SessionSignerValidity, UsageLimit } from './session.js'
@@ -323,7 +316,7 @@ export class Explicit implements ExplicitSessionSigner {
323
316
  Bytes.fromHex(call.data).slice(Number(rule.offset), Number(rule.offset) + 32),
324
317
  32,
325
318
  )
326
- let value: Bytes.Bytes = callDataValue.map((b, i) => b & rule.mask[i]!)
319
+ const value: Bytes.Bytes = callDataValue.map((b, i) => b & rule.mask[i]!)
327
320
  if (Bytes.toBigInt(value) === 0n) continue
328
321
 
329
322
  // Add to list
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  Attestation,
3
- Extensions,
4
3
  Payload,
5
4
  Signature as SequenceSignature,
6
5
  SessionConfig,
@@ -96,7 +95,7 @@ export class Implicit implements ImplicitSessionSigner {
96
95
  )
97
96
  const expectedResult = Bytes.toHex(Attestation.generateImplicitRequestMagic(this._attestation, wallet))
98
97
  return acceptImplicitRequest === expectedResult
99
- } catch (error) {
98
+ } catch {
100
99
  // console.log('implicit signer unsupported call', call, error)
101
100
  return false
102
101
  }
@@ -203,7 +203,7 @@ export class Provider implements ProviderInterface {
203
203
  fromImageHash: Hex.Hex,
204
204
  options?: { allUpdates?: boolean },
205
205
  ): Promise<{ imageHash: Hex.Hex; signature: Signature.RawSignature }[]> {
206
- let fromConfig = await this.store.loadConfig(fromImageHash)
206
+ const fromConfig = await this.store.loadConfig(fromImageHash)
207
207
  if (!fromConfig) {
208
208
  return []
209
209
  }
@@ -384,7 +384,7 @@ export class Provider implements ProviderInterface {
384
384
 
385
385
  if (Signature.isSignatureOfSapientSignerLeaf(topology.signature)) {
386
386
  switch (topology.signature.address.toLowerCase()) {
387
- case this.extensions.passkeys.toLowerCase():
387
+ case this.extensions.passkeys.toLowerCase(): {
388
388
  const decoded = Extensions.Passkeys.decode(Bytes.fromHex(topology.signature.data))
389
389
 
390
390
  if (!Extensions.Passkeys.isValidSignature(subdigest, decoded)) {
@@ -397,6 +397,8 @@ export class Provider implements ProviderInterface {
397
397
  Extensions.Passkeys.rootFor(decoded.publicKey),
398
398
  topology.signature,
399
399
  )
400
+ }
401
+
400
402
  default:
401
403
  throw new Error(`Unsupported sapient signer: ${topology.signature.address}`)
402
404
  }
@@ -1,5 +1,6 @@
1
1
  import { Context, Payload, Signature, Config, GenericTree } from '@0xsequence/wallet-primitives'
2
2
  import { Address, Hex } from 'ox'
3
+ import type { CoreEnv } from '../../env.js'
3
4
  import { Store } from './index.js'
4
5
 
5
6
  const DB_VERSION = 1
@@ -16,15 +17,27 @@ export class IndexedDbStore implements Store {
16
17
  private _db: IDBDatabase | null = null
17
18
  private dbName: string
18
19
 
19
- constructor(dbName: string = 'sequence-indexeddb') {
20
+ constructor(
21
+ dbName: string = 'sequence-indexeddb',
22
+ private readonly env?: CoreEnv,
23
+ ) {
20
24
  this.dbName = dbName
21
25
  }
22
26
 
27
+ private getIndexedDB(): IDBFactory {
28
+ const globalObj = globalThis as any
29
+ const indexedDb = this.env?.indexedDB ?? globalObj.indexedDB ?? globalObj.window?.indexedDB
30
+ if (!indexedDb) {
31
+ throw new Error('indexedDB is not available')
32
+ }
33
+ return indexedDb
34
+ }
35
+
23
36
  private async openDB(): Promise<IDBDatabase> {
24
37
  if (this._db) return this._db
25
38
 
26
39
  return new Promise((resolve, reject) => {
27
- const request = indexedDB.open(this.dbName, DB_VERSION)
40
+ const request = this.getIndexedDB().open(this.dbName, DB_VERSION)
28
41
 
29
42
  request.onupgradeneeded = () => {
30
43
  const db = request.result
@@ -4,10 +4,16 @@ import { Provider } from '../index.js'
4
4
 
5
5
  export class DevHttpProvider implements Provider {
6
6
  private readonly baseUrl: string
7
+ private readonly fetcher: typeof fetch
7
8
 
8
- constructor(baseUrl: string) {
9
+ constructor(baseUrl: string, fetcher?: typeof fetch) {
9
10
  // Remove trailing slash if present
10
11
  this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl
12
+ const resolvedFetch = fetcher ?? (globalThis as any).fetch
13
+ if (!resolvedFetch) {
14
+ throw new Error('fetch is not available')
15
+ }
16
+ this.fetcher = resolvedFetch
11
17
  }
12
18
 
13
19
  private async request<T>(method: 'GET' | 'POST', path: string, body?: any): Promise<T> {
@@ -24,7 +30,7 @@ export class DevHttpProvider implements Provider {
24
30
 
25
31
  let response: Response
26
32
  try {
27
- response = await fetch(url, options)
33
+ response = await this.fetcher(url, options)
28
34
  } catch (networkError) {
29
35
  // Handle immediate network errors (e.g., DNS resolution failure, refused connection)
30
36
  console.error(`Network error during ${method} request to ${url}:`, networkError)
@@ -38,12 +44,12 @@ export class DevHttpProvider implements Provider {
38
44
  const errorText = await response.text()
39
45
  const errorJson = await Utils.fromJSON(errorText)
40
46
  errorPayload = { ...errorPayload, ...errorJson }
41
- } catch (e) {
47
+ } catch {
42
48
  try {
43
49
  // If JSON parsing fails, try getting text for better error message
44
50
  const errorText = await response.text()
45
51
  errorPayload.body = errorText
46
- } catch (textErr) {
52
+ } catch {
47
53
  // Ignore if reading text also fails
48
54
  }
49
55
  }
@@ -99,7 +105,7 @@ export class DevHttpProvider implements Provider {
99
105
  throw new Error(
100
106
  `Failed to parse JSON response from server. Status: ${response.status}. Body: "${text}". Original error: ${error instanceof Error ? error.message : String(error)}`,
101
107
  )
102
- } catch (readError) {
108
+ } catch {
103
109
  throw new Error(
104
110
  `Failed to parse JSON response from server and could not read response body as text. Status: ${response.status}. Original error: ${error instanceof Error ? error.message : String(error)}`,
105
111
  )
@@ -9,13 +9,17 @@ import {
9
9
  TransactionRequest,
10
10
  } from 'ox'
11
11
  import { normalizeAddressKeys, Provider as ProviderInterface } from '../index.js'
12
- import { Sessions, SignatureType } from './sessions.gen.js'
12
+ import { Sessions, SignatureType, type Fetch } from './sessions.gen.js'
13
13
 
14
14
  export class Provider implements ProviderInterface {
15
15
  private readonly service: Sessions
16
16
 
17
- constructor(host = 'https://keymachine.sequence.app') {
18
- this.service = new Sessions(host, fetch)
17
+ constructor(host = 'https://keymachine.sequence.app', fetcher?: Fetch) {
18
+ const resolvedFetch = fetcher ?? (globalThis as any).fetch
19
+ if (!resolvedFetch) {
20
+ throw new Error('fetch is not available')
21
+ }
22
+ this.service = new Sessions(host, resolvedFetch)
19
23
  }
20
24
 
21
25
  async getConfiguration(imageHash: Hex.Hex): Promise<Config.Config | undefined> {
@@ -186,7 +190,9 @@ export class Provider implements ProviderInterface {
186
190
  case SignatureType.SapientCompact:
187
191
  throw new Error(`unexpected compact sapient signature by ${signer}`)
188
192
  }
189
- } catch {}
193
+ } catch {
194
+ // ignore
195
+ }
190
196
  }
191
197
 
192
198
  async getWitnessForSapient(
@@ -221,7 +227,9 @@ export class Provider implements ProviderInterface {
221
227
  signature: { type: 'sapient_compact', address: signer, data: witness.signature },
222
228
  }
223
229
  }
224
- } catch {}
230
+ } catch {
231
+ // ignore
232
+ }
225
233
  }
226
234
 
227
235
  async getConfigurationUpdates(
@@ -380,7 +388,7 @@ const recoverSapientSignatureCompactFunction = AbiFunction.from(recoverSapientSi
380
388
  class PasskeySignatureValidator implements oxProvider.Provider {
381
389
  request: oxProvider.Provider['request'] = (async (request) => {
382
390
  switch (request.method) {
383
- case 'eth_call':
391
+ case 'eth_call': {
384
392
  if (!request.params || !Array.isArray(request.params) || request.params.length === 0) {
385
393
  throw new Error('eth_call requires transaction parameters')
386
394
  }
@@ -406,6 +414,7 @@ class PasskeySignatureValidator implements oxProvider.Provider {
406
414
  } else {
407
415
  throw new Error(`invalid passkey signature ${signature} for digest ${digest}`)
408
416
  }
417
+ }
409
418
 
410
419
  default:
411
420
  throw new Error(`method ${request.method} not implemented`)
package/src/wallet.ts CHANGED
@@ -355,7 +355,7 @@ export class Wallet {
355
355
  throw new Error('4337 is not enabled in this wallet')
356
356
  }
357
357
 
358
- const noncePromise = this.get4337Nonce(provider, status.context.capabilities?.erc4337?.entrypoint!, space)
358
+ const noncePromise = this.get4337Nonce(provider, status.context.capabilities.erc4337.entrypoint, space)
359
359
 
360
360
  // If the wallet is not deployed, then we need to include the initCode on
361
361
  // the 4337 transaction
@@ -570,7 +570,7 @@ export class Wallet {
570
570
  if (typeof message !== 'string') {
571
571
  encodedMessage = TypedData.encode(message)
572
572
  } else {
573
- let hexMessage = Hex.validate(message) ? message : Hex.fromString(message)
573
+ const hexMessage = Hex.validate(message) ? message : Hex.fromString(message)
574
574
  const messageSize = Hex.size(hexMessage)
575
575
  encodedMessage = Hex.concat(Hex.fromString(`${`\x19Ethereum Signed Message:\n${messageSize}`}`), hexMessage)
576
576
  }
package/test/constants.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { config as dotenvConfig } from 'dotenv'
2
2
  import { Abi, AbiEvent, Address } from 'ox'
3
3
 
4
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
4
5
  const envFile = process.env.CI ? '.env.test' : '.env.test.local'
5
6
  dotenvConfig({ path: envFile })
6
7
 
@@ -16,4 +17,5 @@ export const EMITTER_EVENT_TOPICS = [
16
17
  export const USDC_ADDRESS: Address.Address = '0xaf88d065e77c8cc2239327c5edb3a432268e5831'
17
18
 
18
19
  // Environment variables
20
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
19
21
  export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545'
@@ -6,7 +6,6 @@ import * as Envelope from '../src/envelope.js'
6
6
 
7
7
  // Test addresses and data
8
8
  const TEST_ADDRESS_1 = Address.from('0x1234567890123456789012345678901234567890')
9
- const TEST_ADDRESS_2 = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
10
9
  const TEST_ADDRESS_3 = Address.from('0x9876543210987654321098765432109876543210')
11
10
  const TEST_WALLET = Address.from('0xfedcbafedcbafedcbafedcbafedcbafedcbafe00')
12
11
  const TEST_IMAGE_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef')
@@ -143,7 +143,7 @@ describe('Private Key Signers', () => {
143
143
  await pk.witness(mockStateWriter, testWallet)
144
144
 
145
145
  expect(mockStateWriter.saveWitnesses).toHaveBeenCalledTimes(1)
146
- const [wallet, chainId, payload, witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0]
146
+ const [wallet, chainId, _payload, witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0]
147
147
 
148
148
  expect(wallet).toBe(testWallet)
149
149
  expect(chainId).toBe(0)
@@ -236,7 +236,6 @@ describe('Implicit Session', () => {
236
236
  },
237
237
  }
238
238
  const identitySignature = createValidIdentitySignature(attestation)
239
- const topology = createValidTopology()
240
239
 
241
240
  // This should throw an error during construction due to future issued time
242
241
  expect(() => {
@@ -250,7 +249,6 @@ describe('Implicit Session', () => {
250
249
  approvedSigner: randomAddress(), // Different approved signer
251
250
  }
252
251
  const identitySignature = createValidIdentitySignature(attestation)
253
- const topology = createValidTopology()
254
252
 
255
253
  // This should throw an error during construction due to mismatched approved signer
256
254
  expect(() => {