@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,62 @@
1
+ import { config as dotenvConfig } from 'dotenv'
2
+ import { Abi, Address } from 'ox'
3
+ import { Manager, ManagerOptions, ManagerOptionsDefaults } from '../src/sequence'
4
+ import { mockEthereum } from './setup'
5
+ import { Signers as CoreSigners } from '@0xsequence/wallet-core'
6
+ import * as Db from '../src/dbs'
7
+
8
+ const envFile = process.env.CI ? '.env.test' : '.env.test.local'
9
+ dotenvConfig({ path: envFile })
10
+
11
+ export const EMITTER_ADDRESS: Address.Address = '0x7F6e420Ed3017A36bE6e1DA8e3AFE61569eb4840'
12
+ export const EMITTER_ABI = Abi.from(['function explicitEmit()', 'function implicitEmit()'])
13
+
14
+ // Environment variables
15
+ export const { RPC_URL, PRIVATE_KEY } = process.env
16
+ export const CAN_RUN_LIVE = !!RPC_URL && !!PRIVATE_KEY
17
+ export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545'
18
+
19
+ let testIdCounter = 0
20
+
21
+ export function newManager(options?: ManagerOptions, noEthereumMock?: boolean, tag?: string) {
22
+ if (!noEthereumMock) {
23
+ mockEthereum()
24
+ }
25
+
26
+ testIdCounter++
27
+ const dbSuffix = tag ? `_${tag}_testrun_${testIdCounter}` : `_testrun_${testIdCounter}`
28
+
29
+ // Ensure options and its identity sub-object exist for easier merging
30
+ const effectiveOptions = {
31
+ ...options,
32
+ identity: { ...ManagerOptionsDefaults.identity, ...options?.identity },
33
+ }
34
+
35
+ return new Manager({
36
+ networks: [
37
+ {
38
+ name: 'Arbitrum (local fork)',
39
+ rpc: LOCAL_RPC_URL,
40
+ chainId: 42161n,
41
+ explorer: 'https://arbiscan.io/',
42
+ nativeCurrency: {
43
+ name: 'Ether',
44
+ symbol: 'ETH',
45
+ decimals: 18,
46
+ },
47
+ },
48
+ ],
49
+ // Override DBs with unique names if not provided in options,
50
+ // otherwise, use the provided DB instance.
51
+ // This assumes options?.someDb is either undefined or a fully constructed DB instance.
52
+ encryptedPksDb: effectiveOptions.encryptedPksDb || new CoreSigners.Pk.Encrypted.EncryptedPksDb('pk-db' + dbSuffix),
53
+ managerDb: effectiveOptions.managerDb || new Db.Wallets('sequence-manager' + dbSuffix),
54
+ transactionsDb: effectiveOptions.transactionsDb || new Db.Transactions('sequence-transactions' + dbSuffix),
55
+ signaturesDb: effectiveOptions.signaturesDb || new Db.Signatures('sequence-signature-requests' + dbSuffix),
56
+ authCommitmentsDb:
57
+ effectiveOptions.authCommitmentsDb || new Db.AuthCommitments('sequence-auth-commitments' + dbSuffix),
58
+ authKeysDb: effectiveOptions.authKeysDb || new Db.AuthKeys('sequence-auth-keys' + dbSuffix),
59
+ recoveryDb: effectiveOptions.recoveryDb || new Db.Recovery('sequence-recovery' + dbSuffix),
60
+ ...effectiveOptions,
61
+ })
62
+ }
@@ -0,0 +1,211 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { Manager, QueuedRecoveryPayload, SignerReady, TransactionDefined } from '../src/sequence'
3
+ import { Bytes, Hex, Mnemonic, Provider, RpcTransport } from 'ox'
4
+ import { Payload } from '@0xsequence/wallet-primitives'
5
+ import { LOCAL_RPC_URL, newManager } from './constants'
6
+
7
+ describe('Recovery', () => {
8
+ it('Should execute a recovery', async () => {
9
+ const manager = newManager({
10
+ defaultRecoverySettings: {
11
+ requiredDeltaTime: 2n, // 2 seconds
12
+ minTimestamp: 0n,
13
+ },
14
+ })
15
+
16
+ const mnemonic = Mnemonic.random(Mnemonic.english)
17
+ const wallet = await manager.signUp({ mnemonic, kind: 'mnemonic', noGuard: true })
18
+ expect(wallet).toBeDefined()
19
+
20
+ // Add recovery mnemonic
21
+ const mnemonic2 = Mnemonic.random(Mnemonic.english)
22
+ const requestId1 = await manager.addRecoveryMnemonic(wallet!, mnemonic2)
23
+
24
+ expect(requestId1).toBeDefined()
25
+
26
+ // Sign add recovery mnemonic
27
+ const request1 = await manager.getSignatureRequest(requestId1)
28
+ expect(request1).toBeDefined()
29
+
30
+ // Device must be the only ready signer now
31
+ const device = request1.signers.find((s) => s.status === 'ready')
32
+ expect(device).toBeDefined()
33
+
34
+ const result1 = await device?.handle()
35
+ expect(result1).toBeDefined()
36
+ expect(result1).toBeTruthy()
37
+
38
+ // Complete the add of the recovery mnemonic
39
+ await manager.completeRecoveryUpdate(requestId1)
40
+
41
+ // Get the recovery signers, there should be two one
42
+ // and one should not be the device address
43
+ const recoverySigners = await manager.getRecoverySigners(wallet!)
44
+ expect(recoverySigners).toBeDefined()
45
+ expect(recoverySigners!.length).toBe(2)
46
+ const nonDeviceSigner = recoverySigners!.find((s) => s.address !== device?.address)
47
+ expect(nonDeviceSigner).toBeDefined()
48
+
49
+ // Transfer 1 wei to the wallet
50
+ const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
51
+ await provider.request({
52
+ method: 'anvil_setBalance',
53
+ params: [wallet!, '0x1'],
54
+ })
55
+
56
+ // Create a new recovery payload
57
+ const requestId2 = await manager.queueRecoveryPayload(wallet!, 42161n, {
58
+ type: 'call',
59
+ space: Bytes.toBigInt(Bytes.random(20)),
60
+ nonce: 0n,
61
+ calls: [
62
+ {
63
+ to: Hex.from(Bytes.random(20)),
64
+ value: 1n,
65
+ data: '0x',
66
+ gasLimit: 1000000n,
67
+ delegateCall: false,
68
+ onlyFallback: false,
69
+ behaviorOnError: 'revert',
70
+ },
71
+ ],
72
+ })
73
+
74
+ // Needs to be signed using the recovery mnemonic
75
+ // for this we need to define a handler for it
76
+ let handledMnemonic2 = 0
77
+ const unregisterHandler = manager.registerMnemonicUI(async (respond) => {
78
+ handledMnemonic2++
79
+ await respond(mnemonic2)
80
+ })
81
+
82
+ // Sign the queue recovery payload
83
+ const request2 = await manager.getSignatureRequest(requestId2)
84
+ expect(request2).toBeDefined()
85
+
86
+ // Complete the queue recovery payload
87
+ // the only signer available should be the device and the recovery mnemonic
88
+ // the both recovery deviecs that we have
89
+ expect(request2.signers.length).toBe(2)
90
+ expect(request2.signers.some((s) => s.handler?.kind === 'local-device')).toBeTruthy()
91
+ expect(request2.signers.some((s) => s.handler?.kind === 'login-mnemonic')).toBeTruthy()
92
+
93
+ // Handle the login-mnemonic signer
94
+ const request2Signer = request2.signers.find((s) => s.handler?.kind === 'login-mnemonic')
95
+ expect(request2Signer).toBeDefined()
96
+ const result2 = await (request2Signer as SignerReady).handle()
97
+ expect(result2).toBeDefined()
98
+ expect(result2).toBeTruthy()
99
+ expect(handledMnemonic2).toBe(1)
100
+ unregisterHandler()
101
+
102
+ // Complete the recovery payload
103
+ const { to, data } = await manager.completeRecoveryPayload(requestId2)
104
+
105
+ // Send this transaction to anvil so we queue the payload
106
+ await provider.request({
107
+ method: 'eth_sendTransaction',
108
+ params: [
109
+ {
110
+ to,
111
+ data,
112
+ },
113
+ ],
114
+ })
115
+
116
+ // Wait 3 seconds for the payload to become valid
117
+ await new Promise((resolve) => setTimeout(resolve, 3000))
118
+ await manager.updateQueuedRecoveryPayloads()
119
+
120
+ // Get the recovery payloads
121
+ const recoveryPayloads = await new Promise<QueuedRecoveryPayload[]>((resolve) => {
122
+ const unsubscribe = manager.onQueuedRecoveryPayloadsUpdate(
123
+ wallet!,
124
+ (payloads) => {
125
+ unsubscribe()
126
+ resolve(payloads)
127
+ },
128
+ true,
129
+ )
130
+ })
131
+
132
+ expect(recoveryPayloads).toBeDefined()
133
+ expect(recoveryPayloads.length).toBe(1)
134
+ const recoveryPayload = recoveryPayloads![0]
135
+ expect(recoveryPayload).toBeDefined()
136
+ expect(Payload.isCalls(recoveryPayload!.payload!)).toBeTruthy()
137
+ expect((recoveryPayload!.payload as Payload.Calls).calls.length).toBe(1)
138
+
139
+ // Send this transaction as any other regular transaction
140
+ const requestId3 = await manager.requestTransaction(
141
+ wallet!,
142
+ 42161n,
143
+ (recoveryPayload!.payload as Payload.Calls).calls,
144
+ {
145
+ noConfigUpdate: true,
146
+ },
147
+ )
148
+ expect(requestId3).toBeDefined()
149
+
150
+ // Define the same nonce and space for the recovery payload
151
+ await manager.defineTransaction(requestId3, {
152
+ nonce: (recoveryPayload!.payload as Payload.Calls).nonce,
153
+ space: (recoveryPayload!.payload as Payload.Calls).space,
154
+ })
155
+
156
+ // Complete the transaction
157
+ const tx = await manager.getTransaction(requestId3)
158
+ expect(tx).toBeDefined()
159
+ expect(tx.status).toBe('defined')
160
+ expect((tx as TransactionDefined).relayerOptions.length).toBe(1)
161
+
162
+ const localRelayer = (tx as TransactionDefined).relayerOptions[0]
163
+ expect(localRelayer).toBeDefined()
164
+ expect(localRelayer.relayerId).toBe('local')
165
+
166
+ // Define the relayer
167
+ const requestId4 = await manager.selectTransactionRelayer(requestId3, localRelayer.id)
168
+ expect(requestId4).toBeDefined()
169
+
170
+ // Now we sign using the recovery module
171
+ const request4 = await manager.getSignatureRequest(requestId4)
172
+
173
+ // Find the signer that is the recovery module handler
174
+ const recoverySigner = request4.signers.find((s) => s.handler?.kind === 'recovery-extension')
175
+ expect(recoverySigner).toBeDefined()
176
+ expect(recoverySigner!.status).toBe('ready')
177
+ 1
178
+ // Handle the recovery signer
179
+ const result4 = await (recoverySigner as SignerReady).handle()
180
+ expect(result4).toBeDefined()
181
+ expect(result4).toBeTruthy()
182
+
183
+ // Complete the transaction
184
+ const txHash = await manager.relayTransaction(requestId4)
185
+ expect(txHash).toBeDefined()
186
+
187
+ // The balance of the wallet should be 0 wei
188
+ const balance = await provider.request({
189
+ method: 'eth_getBalance',
190
+ params: [wallet!, 'latest'],
191
+ })
192
+ expect(balance).toBeDefined()
193
+ expect(balance).toBe('0x0')
194
+
195
+ // Refresh the queued recovery payloads, the executed one
196
+ // should be removed
197
+ await manager.updateQueuedRecoveryPayloads()
198
+ const recoveryPayloads2 = await new Promise<QueuedRecoveryPayload[]>((resolve) => {
199
+ const unsubscribe = manager.onQueuedRecoveryPayloadsUpdate(
200
+ wallet!,
201
+ (payloads) => {
202
+ unsubscribe()
203
+ resolve(payloads)
204
+ },
205
+ true,
206
+ )
207
+ })
208
+ expect(recoveryPayloads2).toBeDefined()
209
+ expect(recoveryPayloads2.length).toBe(0)
210
+ }, 30000)
211
+ })
@@ -0,0 +1,324 @@
1
+ import { AbiFunction, Address, Bytes, Hex, Mnemonic, Provider, RpcTransport } from 'ox'
2
+ import { beforeEach, describe, it, vi } from 'vitest'
3
+ import { Signers as CoreSigners, Wallet as CoreWallet, Envelope, Relayer, State } from '../../core/src/index.js'
4
+ import { Attestation, Constants, Payload, Permission } from '../../primitives/src/index.js'
5
+ import { Sequence } from '../src/index.js'
6
+ import { CAN_RUN_LIVE, EMITTER_ABI, EMITTER_ADDRESS, PRIVATE_KEY, RPC_URL } from './constants'
7
+
8
+ describe('Sessions (via Manager)', () => {
9
+ // Shared components
10
+ let provider: Provider.Provider
11
+ let chainId: bigint
12
+ let stateProvider: State.Provider
13
+
14
+ // Wallet webapp components
15
+ let wdk: {
16
+ identitySignerAddress: Address.Address
17
+ manager: Sequence.Manager
18
+ }
19
+
20
+ // Dapp components
21
+ let dapp: {
22
+ pkStore: CoreSigners.Pk.Encrypted.EncryptedPksDb
23
+ wallet: CoreWallet
24
+ sessionManager: CoreSigners.SessionManager
25
+ }
26
+
27
+ beforeEach(async () => {
28
+ // Create provider or use arbitrum sepolia
29
+ if (RPC_URL) {
30
+ provider = Provider.from(
31
+ RpcTransport.fromHttp(RPC_URL, {
32
+ fetchOptions: {
33
+ headers: {
34
+ 'x-requested-with': 'XMLHttpRequest',
35
+ },
36
+ },
37
+ }),
38
+ )
39
+ chainId = BigInt(await provider.request({ method: 'eth_chainId' }))
40
+ } else {
41
+ provider = vi.mocked<Provider.Provider>({
42
+ request: vi.fn(),
43
+ on: vi.fn(),
44
+ removeListener: vi.fn(),
45
+ })
46
+ chainId = 1n
47
+ }
48
+
49
+ // Create state provider
50
+ stateProvider = new State.Local.Provider()
51
+
52
+ // Create manager
53
+ const opts = Sequence.applyManagerOptionsDefaults({
54
+ stateProvider,
55
+ relayers: [], // No relayers needed for testing
56
+ networks: [
57
+ {
58
+ chainId,
59
+ rpc: RPC_URL ?? 'XXX',
60
+ name: 'XXX',
61
+ explorer: 'XXX',
62
+ nativeCurrency: {
63
+ name: 'Ether',
64
+ symbol: 'ETH',
65
+ decimals: 18,
66
+ },
67
+ },
68
+ ],
69
+ })
70
+
71
+ // Create manager
72
+ const manager = new Sequence.Manager(opts)
73
+
74
+ // Use a mnemonic to create the wallet
75
+ const identitySignerMnemonic = Mnemonic.random(Mnemonic.english)
76
+ const identitySignerPk = Mnemonic.toPrivateKey(identitySignerMnemonic, { as: 'Hex' })
77
+ const identitySignerAddress = new CoreSigners.Pk.Pk(identitySignerPk).address
78
+ const walletAddress = await manager.signUp({
79
+ kind: 'mnemonic',
80
+ mnemonic: identitySignerMnemonic,
81
+ noGuard: true,
82
+ noSessionManager: false,
83
+ })
84
+ if (!walletAddress) {
85
+ throw new Error('Failed to create wallet')
86
+ }
87
+
88
+ // Initialize the wdk components
89
+ wdk = {
90
+ identitySignerAddress,
91
+ manager,
92
+ }
93
+ manager.registerMnemonicUI(async (respond) => {
94
+ await respond(identitySignerMnemonic)
95
+ })
96
+
97
+ // Create the pk store and pk
98
+ const pkStore = new CoreSigners.Pk.Encrypted.EncryptedPksDb()
99
+
100
+ // Create wallet in core
101
+ const coreWallet = new CoreWallet(walletAddress, {
102
+ context: opts.context,
103
+ guest: opts.guest,
104
+ // Share the state provider with wdk. In practice this will be the key machine.
105
+ stateProvider,
106
+ })
107
+
108
+ dapp = {
109
+ pkStore,
110
+ wallet: coreWallet,
111
+ sessionManager: new CoreSigners.SessionManager(coreWallet, {
112
+ provider,
113
+ }),
114
+ }
115
+ })
116
+
117
+ const signAndSend = async (call: Payload.Call) => {
118
+ const envelope = await dapp.wallet.prepareTransaction(provider, [call], { noConfigUpdate: true })
119
+ const parentedEnvelope: Payload.Parented = {
120
+ ...envelope.payload,
121
+ parentWallets: [dapp.wallet.address],
122
+ }
123
+
124
+ // Sign the envelope
125
+ const sessionImageHash = await dapp.sessionManager.imageHash
126
+ if (!sessionImageHash) {
127
+ throw new Error('Session image hash not found')
128
+ }
129
+ const signature = await dapp.sessionManager.signSapient(
130
+ dapp.wallet.address,
131
+ chainId ?? 1n,
132
+ parentedEnvelope,
133
+ sessionImageHash,
134
+ )
135
+ const sapientSignature: Envelope.SapientSignature = {
136
+ imageHash: sessionImageHash,
137
+ signature,
138
+ }
139
+ const signedEnvelope = Envelope.toSigned(envelope, [sapientSignature])
140
+
141
+ // Build the transaction
142
+ const transaction = await dapp.wallet.buildTransaction(provider, signedEnvelope)
143
+ console.log('tx', transaction)
144
+
145
+ // Send the transaction
146
+ if (CAN_RUN_LIVE && PRIVATE_KEY) {
147
+ // Load the sender
148
+ const senderPk = Hex.from(PRIVATE_KEY as `0x${string}`)
149
+ const pkRelayer = new Relayer.Pk.PkRelayer(senderPk, provider)
150
+ const tx = await pkRelayer.relay(transaction.to, transaction.data, chainId, undefined)
151
+ console.log('Transaction sent', tx)
152
+ await new Promise((resolve) => setTimeout(resolve, 3000))
153
+ const receipt = await provider.request({ method: 'eth_getTransactionReceipt', params: [tx.opHash] })
154
+ console.log('Transaction receipt', receipt)
155
+ return tx.opHash
156
+ }
157
+ }
158
+
159
+ it(
160
+ 'should create and sign with an explicit session',
161
+ async () => {
162
+ // Create the explicit session signer
163
+ const e = await dapp.pkStore.generateAndStore()
164
+ const s = await dapp.pkStore.getEncryptedPkStore(e.address)
165
+ if (!s) {
166
+ throw new Error('Failed to create pk store')
167
+ }
168
+ const permission: Permission.SessionPermissions = {
169
+ signer: e.address,
170
+ valueLimit: 0n,
171
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
172
+ permissions: [
173
+ {
174
+ target: EMITTER_ADDRESS,
175
+ rules: [
176
+ {
177
+ // Require the explicitEmit selector
178
+ cumulative: false,
179
+ operation: Permission.ParameterOperation.EQUAL,
180
+ value: Bytes.padRight(Bytes.fromHex(AbiFunction.getSelector(EMITTER_ABI[0])), 32),
181
+ offset: 0n,
182
+ mask: Bytes.padRight(Bytes.fromHex('0xffffffff'), 32),
183
+ },
184
+ ],
185
+ },
186
+ ],
187
+ }
188
+ const explicitSigner = new CoreSigners.Session.Explicit(s, permission)
189
+ // Add to manager
190
+ dapp.sessionManager = dapp.sessionManager.withExplicitSigner(explicitSigner)
191
+
192
+ // Request the session permissions from the WDK
193
+ const requestId = await wdk.manager.addExplicitSession(dapp.wallet.address, explicitSigner.address, permission)
194
+
195
+ // Sign and complete the request
196
+ const sigRequest = await wdk.manager.getSignatureRequest(requestId)
197
+ const identitySigner = sigRequest.signers.find((s) => s.address === wdk.identitySignerAddress)
198
+ if (!identitySigner || (identitySigner.status !== 'actionable' && identitySigner.status !== 'ready')) {
199
+ throw new Error(`Identity signer not found or not ready/actionable: ${identitySigner?.status}`)
200
+ }
201
+ const handled = await identitySigner.handle()
202
+ if (!handled) {
203
+ throw new Error('Failed to handle identity signer')
204
+ }
205
+ await wdk.manager.completeSessionUpdate(requestId)
206
+
207
+ // Create a call payload
208
+ const call: Payload.Call = {
209
+ to: EMITTER_ADDRESS,
210
+ value: 0n,
211
+ data: AbiFunction.encodeData(EMITTER_ABI[0]),
212
+ gasLimit: 0n,
213
+ delegateCall: false,
214
+ onlyFallback: false,
215
+ behaviorOnError: 'revert',
216
+ }
217
+
218
+ if (!RPC_URL) {
219
+ // Configure mock provider
220
+ ;(provider as any).request.mockImplementation(({ method, params }) => {
221
+ if (method === 'eth_chainId') {
222
+ return Promise.resolve(chainId.toString())
223
+ }
224
+ if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) {
225
+ // Undeployed wallet
226
+ return Promise.resolve('0x')
227
+ }
228
+ if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.READ_NONCE, [0n])) {
229
+ // Nonce is 0
230
+ return Promise.resolve('0x00')
231
+ }
232
+ })
233
+ }
234
+
235
+ // Sign and send the transaction
236
+ await signAndSend(call)
237
+ },
238
+ PRIVATE_KEY || RPC_URL ? { timeout: 60000 } : undefined,
239
+ )
240
+
241
+ it(
242
+ 'should create and sign with an implicit session',
243
+ async () => {
244
+ // Create the implicit session signer
245
+ const e = await dapp.pkStore.generateAndStore()
246
+ const s = await dapp.pkStore.getEncryptedPkStore(e.address)
247
+ if (!s) {
248
+ throw new Error('Failed to create pk store')
249
+ }
250
+
251
+ // Request the session authorization from the WDK
252
+ const requestId = await wdk.manager.prepareAuthorizeImplicitSession(dapp.wallet.address, e.address, {
253
+ target: 'https://example.com',
254
+ })
255
+
256
+ // Sign the request (Wallet UI action)
257
+ const sigRequest = await wdk.manager.getSignatureRequest(requestId)
258
+ const identitySigner = sigRequest.signers[0]
259
+ if (!identitySigner || (identitySigner.status !== 'actionable' && identitySigner.status !== 'ready')) {
260
+ throw new Error(`Identity signer not found or not ready/actionable: ${identitySigner?.status}`)
261
+ }
262
+ const handled = await identitySigner.handle()
263
+ if (!handled) {
264
+ throw new Error('Failed to handle identity signer')
265
+ }
266
+
267
+ // Complete the request
268
+ const { attestation, signature: identitySignature } =
269
+ await wdk.manager.completeAuthorizeImplicitSession(requestId)
270
+
271
+ // Load the implicit signer
272
+ const implicitSigner = new CoreSigners.Session.Implicit(
273
+ s,
274
+ attestation,
275
+ identitySignature,
276
+ dapp.sessionManager.address,
277
+ )
278
+ dapp.sessionManager = dapp.sessionManager.withImplicitSigner(implicitSigner)
279
+
280
+ // Create a call payload
281
+ const call: Payload.Call = {
282
+ to: EMITTER_ADDRESS,
283
+ value: 0n,
284
+ data: AbiFunction.encodeData(EMITTER_ABI[1]), // implicitEmit
285
+ gasLimit: 0n,
286
+ delegateCall: false,
287
+ onlyFallback: false,
288
+ behaviorOnError: 'revert',
289
+ }
290
+
291
+ if (!RPC_URL) {
292
+ // Configure mock provider
293
+ ;(provider as any).request.mockImplementation(({ method, params }) => {
294
+ if (method === 'eth_chainId') {
295
+ return Promise.resolve(chainId.toString())
296
+ }
297
+ if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) {
298
+ // Undeployed wallet
299
+ return Promise.resolve('0x')
300
+ }
301
+ if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.READ_NONCE, [0n])) {
302
+ // Nonce is 0
303
+ return Promise.resolve('0x00')
304
+ }
305
+ if (
306
+ method === 'eth_call' &&
307
+ Address.isEqual(params[0].from, dapp.sessionManager.address) &&
308
+ Address.isEqual(params[0].to, call.to)
309
+ ) {
310
+ // Implicit request simulation result
311
+ const expectedResult = Bytes.toHex(
312
+ Attestation.generateImplicitRequestMagic(attestation, dapp.wallet.address),
313
+ )
314
+ return Promise.resolve(expectedResult)
315
+ }
316
+ })
317
+ }
318
+
319
+ // Sign and send the transaction
320
+ await signAndSend(call)
321
+ },
322
+ PRIVATE_KEY || RPC_URL ? { timeout: 60000 } : undefined,
323
+ )
324
+ })
package/test/setup.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { indexedDB, IDBFactory } from 'fake-indexeddb'
2
+ import { Address, Bytes, Hex, Provider, RpcTransport } from 'ox'
3
+ import { vi } from 'vitest'
4
+ import { LOCAL_RPC_URL } from './constants'
5
+
6
+ // Add IndexedDB support to the test environment
7
+ global.indexedDB = indexedDB
8
+ global.IDBFactory = IDBFactory
9
+
10
+ // Mock navigator.locks API for Node.js environment ---
11
+
12
+ // 1. Ensure the global navigator object exists
13
+ if (typeof global.navigator === 'undefined') {
14
+ console.log('mocking navigator')
15
+ global.navigator = {} as Navigator
16
+ }
17
+
18
+ // 2. Define or redefine the 'locks' property on navigator
19
+ // Check if 'locks' is falsy (null or undefined), OR if it's an object
20
+ // that doesn't have the 'request' property we expect in our mock.
21
+ if (!global.navigator.locks || !('request' in global.navigator.locks)) {
22
+ Object.defineProperty(global.navigator, 'locks', {
23
+ // The value of the 'locks' property will be our mock object
24
+ value: {
25
+ // Mock the 'request' method
26
+ request: vi
27
+ .fn()
28
+ .mockImplementation(async (name: string, callback: (lock: { name: string } | null) => Promise<any>) => {
29
+ // Simulate acquiring the lock immediately in the test environment.
30
+ const mockLock = { name } // A minimal mock lock object
31
+ try {
32
+ // Execute the callback provided to navigator.locks.request
33
+ const result = await callback(mockLock)
34
+ return result // Return the result of the callback
35
+ } catch (e) {
36
+ // Log errors from the callback for better debugging in tests
37
+ console.error(`Error occurred within mocked lock callback for lock "${name}":`, e)
38
+ throw e // Re-throw the error so the test potentially fails
39
+ }
40
+ }),
41
+ // Mock the 'query' method
42
+ query: vi.fn().mockResolvedValue({ held: [], pending: [] }),
43
+ },
44
+ writable: true,
45
+ configurable: true,
46
+ enumerable: true,
47
+ })
48
+ } else {
49
+ console.log('navigator.locks already exists and appears to have a "request" property.')
50
+ }
51
+
52
+ export function mockEthereum() {
53
+ // Add window.ethereum support, pointing to the the Anvil local RPC
54
+ if (typeof (window as any).ethereum === 'undefined') {
55
+ ;(window as any).ethereum = {
56
+ request: vi.fn().mockImplementation(async (args: any) => {
57
+ // Pipe the request to the Anvil local RPC
58
+ const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL))
59
+ return provider.request(args)
60
+ }),
61
+ }
62
+ }
63
+ }