@0xsequence/provider 0.0.0-2021823181734

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 (54) hide show
  1. package/CHANGELOG.md +909 -0
  2. package/README.md +4 -0
  3. package/dist/0xsequence-provider.cjs.d.ts +1 -0
  4. package/dist/0xsequence-provider.cjs.dev.js +3540 -0
  5. package/dist/0xsequence-provider.cjs.js +7 -0
  6. package/dist/0xsequence-provider.cjs.prod.js +3540 -0
  7. package/dist/0xsequence-provider.esm.js +3508 -0
  8. package/dist/declarations/src/commands/index.d.ts +20 -0
  9. package/dist/declarations/src/index.d.ts +5 -0
  10. package/dist/declarations/src/provider.d.ts +59 -0
  11. package/dist/declarations/src/transports/base-provider-transport.d.ts +40 -0
  12. package/dist/declarations/src/transports/base-wallet-transport.d.ts +38 -0
  13. package/dist/declarations/src/transports/extension-transport/base-injected-transport.d.ts +18 -0
  14. package/dist/declarations/src/transports/extension-transport/extension-message-handler.d.ts +12 -0
  15. package/dist/declarations/src/transports/extension-transport/extension-message-provider.d.ts +11 -0
  16. package/dist/declarations/src/transports/extension-transport/index.d.ts +3 -0
  17. package/dist/declarations/src/transports/index.d.ts +7 -0
  18. package/dist/declarations/src/transports/mux-transport/index.d.ts +1 -0
  19. package/dist/declarations/src/transports/mux-transport/mux-message-provider.d.ts +22 -0
  20. package/dist/declarations/src/transports/proxy-transport/index.d.ts +3 -0
  21. package/dist/declarations/src/transports/proxy-transport/proxy-message-channel.d.ts +15 -0
  22. package/dist/declarations/src/transports/proxy-transport/proxy-message-handler.d.ts +12 -0
  23. package/dist/declarations/src/transports/proxy-transport/proxy-message-provider.d.ts +12 -0
  24. package/dist/declarations/src/transports/wallet-request-handler.d.ts +51 -0
  25. package/dist/declarations/src/transports/window-transport/index.d.ts +2 -0
  26. package/dist/declarations/src/transports/window-transport/window-message-handler.d.ts +15 -0
  27. package/dist/declarations/src/transports/window-transport/window-message-provider.d.ts +13 -0
  28. package/dist/declarations/src/types.d.ts +170 -0
  29. package/dist/declarations/src/utils.d.ts +14 -0
  30. package/dist/declarations/src/wallet.d.ts +99 -0
  31. package/package.json +37 -0
  32. package/src/commands/index.ts +146 -0
  33. package/src/index.ts +5 -0
  34. package/src/provider.ts +494 -0
  35. package/src/transports/base-provider-transport.ts +411 -0
  36. package/src/transports/base-wallet-transport.ts +441 -0
  37. package/src/transports/extension-transport/base-injected-transport.ts +100 -0
  38. package/src/transports/extension-transport/extension-message-handler.ts +25 -0
  39. package/src/transports/extension-transport/extension-message-provider.ts +41 -0
  40. package/src/transports/extension-transport/index.ts +3 -0
  41. package/src/transports/index.ts +7 -0
  42. package/src/transports/mux-transport/index.ts +1 -0
  43. package/src/transports/mux-transport/mux-message-provider.ts +160 -0
  44. package/src/transports/proxy-transport/index.ts +3 -0
  45. package/src/transports/proxy-transport/proxy-message-channel.ts +58 -0
  46. package/src/transports/proxy-transport/proxy-message-handler.ts +46 -0
  47. package/src/transports/proxy-transport/proxy-message-provider.ts +81 -0
  48. package/src/transports/wallet-request-handler.ts +702 -0
  49. package/src/transports/window-transport/index.ts +2 -0
  50. package/src/transports/window-transport/window-message-handler.ts +166 -0
  51. package/src/transports/window-transport/window-message-provider.ts +183 -0
  52. package/src/types.ts +252 -0
  53. package/src/utils.ts +76 -0
  54. package/src/wallet.ts +692 -0
@@ -0,0 +1,166 @@
1
+ import {
2
+ ProviderMessageRequest,
3
+ ProviderMessage,
4
+ EventType,
5
+ InitState,
6
+ WindowSessionParams,
7
+ OpenWalletIntent,
8
+ ProviderRpcError,
9
+ TransportSession
10
+ } from '../../types'
11
+ import { WalletRequestHandler } from '../wallet-request-handler'
12
+ import { BaseWalletTransport } from '../base-wallet-transport'
13
+ import { logger, sanitizeNumberString, base64DecodeObject } from '@0xsequence/utils'
14
+
15
+ export class WindowMessageHandler extends BaseWalletTransport {
16
+ protected parentWindow: Window
17
+
18
+ private _isPopup: boolean = false
19
+
20
+ constructor(walletRequestHandler: WalletRequestHandler) {
21
+ super(walletRequestHandler)
22
+ this._init = InitState.NIL
23
+ }
24
+
25
+ register() {
26
+ const isPopup = parent.window.opener !== null
27
+ this._isPopup = isPopup
28
+ if (isPopup !== true) {
29
+ return
30
+ }
31
+
32
+ // record open details (sessionId + default network) from the window url
33
+ const { pathname, search: rawParams } = new URL(window.location.href)
34
+
35
+ let session: TransportSession | null = this.getWindowTransportSession(rawParams)
36
+
37
+ // provider should always include sid when opening a new window
38
+ const isNewWindowSession = !!session.sessionId
39
+
40
+ // attempt to restore previous session in the case of a redirect or window reload
41
+ if (!isNewWindowSession) {
42
+ session = this.getCachedTransportSession()
43
+ }
44
+
45
+ if (!session) {
46
+ logger.error('window session is undefined')
47
+ return
48
+ }
49
+
50
+ if (session.intent && isNewWindowSession) {
51
+ window.history.replaceState({ openWalletIntent: true }, document.title, pathname)
52
+ }
53
+
54
+ // record parent window instance for communication
55
+ this.parentWindow = parent.window.opener
56
+
57
+ // listen for window-transport requests
58
+ window.addEventListener('message', this.onWindowEvent, false)
59
+ this._registered = true
60
+
61
+ // send open event to the app which opened us
62
+ this.open(session)
63
+ .then(opened => {
64
+ if (!opened) {
65
+ const err = `failed to open to network ${session?.networkId}`
66
+ logger.error(err)
67
+ this.notifyClose({ message: err } as ProviderRpcError)
68
+ window.close()
69
+ }
70
+ })
71
+ .catch(e => {
72
+ const err = `failed to open to network ${session?.networkId}, due to: ${e}`
73
+ logger.error(err)
74
+ this.notifyClose({ message: err } as ProviderRpcError)
75
+ window.close()
76
+ })
77
+ }
78
+
79
+ unregister() {
80
+ window.removeEventListener('message', this.onWindowEvent)
81
+ this._registered = false
82
+ }
83
+
84
+ // onmessage is called when (the wallet) receives request messages from the dapp
85
+ // over the window post-messaging transport
86
+ private onWindowEvent = async (event: MessageEvent) => {
87
+ if (!event.origin || event.origin === '') {
88
+ // skip same-origin or when event.origin is empty/undefined
89
+ return
90
+ }
91
+ if (this.appOrigin && event.origin !== this.appOrigin) {
92
+ // skip message as not from expected app origin
93
+ return
94
+ }
95
+
96
+ // Wallet always expects json-rpc request messages from a dapp
97
+ let request: ProviderMessageRequest
98
+ try {
99
+ request = JSON.parse(event.data)
100
+ } catch (err) {
101
+ // event is not a ProviderMessage JSON object, skip
102
+ return
103
+ }
104
+
105
+ logger.debug('RECEIVED MESSAGE', request)
106
+
107
+ // Record event origin for valid init ack
108
+ if (this._init !== InitState.OK && this.isValidInitAck(request)) {
109
+ this.appOrigin = event.origin
110
+ }
111
+ if (this._init === InitState.OK && (!this.appOrigin || this.appOrigin.length < 8)) {
112
+ // impossible state
113
+ logger.error('impossible state, init.OK and appOrigin required')
114
+ return
115
+ }
116
+
117
+ // Handle message via the base transport
118
+ this.handleMessage(request)
119
+ }
120
+
121
+ // postMessage sends message to the dapp window
122
+ sendMessage(message: ProviderMessage<any>) {
123
+ // prepare payload
124
+ const payload = JSON.stringify(message)
125
+
126
+ // post-message to app.
127
+ // only for init requests, we send to '*' origin
128
+ if (message.type === EventType.INIT) {
129
+ this.postMessage(payload, true)
130
+ } else {
131
+ this.postMessage(payload)
132
+ }
133
+ }
134
+
135
+ get isPopup(): boolean {
136
+ return this._isPopup
137
+ }
138
+
139
+ private postMessage(message: any, init = false) {
140
+ if (init !== true && this._init !== InitState.OK) {
141
+ logger.error('impossible state, should not be calling postMessage until inited')
142
+ return
143
+ }
144
+
145
+ if (init) {
146
+ // init message transmission to global target -- for 'init' payloads only
147
+ this.parentWindow.postMessage(message, '*')
148
+ } else {
149
+ // open message transmission
150
+ if (this.appOrigin && this.appOrigin.length > 4) { // just above '.com'
151
+ this.parentWindow.postMessage(message, this.appOrigin)
152
+ } else {
153
+ logger.error('unable to postMessage as parentOrigin is invalid')
154
+ }
155
+ }
156
+ }
157
+
158
+ private getWindowTransportSession = (windowParams: string | undefined): TransportSession => {
159
+ const params = new WindowSessionParams(windowParams)
160
+ return {
161
+ sessionId: params.get('sid'),
162
+ networkId: params.get('net'),
163
+ intent: base64DecodeObject<OpenWalletIntent>(params.get('intent'))
164
+ }
165
+ }
166
+ }
@@ -0,0 +1,183 @@
1
+ import { OpenWalletIntent, ProviderMessage, InitState, EventType, WindowSessionParams } from '../../types'
2
+ import { BaseProviderTransport } from '../base-provider-transport'
3
+ import { logger, base64EncodeObject } from '@0xsequence/utils'
4
+ import { isBrowserExtension } from '../../utils'
5
+
6
+ // ..
7
+ let registeredWindowMessageProvider: WindowMessageProvider | undefined
8
+
9
+ export class WindowMessageProvider extends BaseProviderTransport {
10
+ private walletURL: URL
11
+ private walletWindow: Window | null
12
+
13
+ constructor(walletAppURL: string) {
14
+ super()
15
+ this.walletURL = new URL(walletAppURL)
16
+ }
17
+
18
+ register = () => {
19
+ if (registeredWindowMessageProvider) {
20
+ throw new Error('A WindowMessageProvider is already registered. There can only be one.')
21
+ }
22
+
23
+ // listen for incoming messages from wallet
24
+ window.addEventListener('message', this.onWindowEvent)
25
+ registeredWindowMessageProvider = this
26
+
27
+ // open heartbeat
28
+ this.on('open', () => {
29
+ // Heartbeat to track if window closed
30
+ const popup = this.walletWindow
31
+ const interval = setInterval(() => {
32
+ if (popup && popup.closed) {
33
+ clearInterval(interval)
34
+ this.close()
35
+ }
36
+ }, 500)
37
+ })
38
+
39
+ // close clean up
40
+ this.on('close', () => {
41
+ if (this.walletWindow) {
42
+ this.walletWindow.close()
43
+ this.walletWindow = null
44
+ }
45
+ })
46
+
47
+ this._registered = true
48
+ }
49
+
50
+ unregister = () => {
51
+ this._registered = false
52
+ this.closeWallet()
53
+
54
+ // disable message listener
55
+ if (registeredWindowMessageProvider === this) {
56
+ registeredWindowMessageProvider = undefined
57
+ }
58
+ window.removeEventListener('message', this.onWindowEvent)
59
+
60
+ // clear event listeners
61
+ this.events.removeAllListeners()
62
+ }
63
+
64
+ openWallet = (path?: string, intent?: OpenWalletIntent, networkId?: string | number): void => {
65
+ if (this.walletWindow && this.isOpened()) {
66
+ // TODO: update the location of window to path
67
+ this.walletWindow.focus()
68
+ return
69
+ }
70
+
71
+ // Instantiate new walletURL for this call
72
+ const walletURL = new URL(this.walletURL.href)
73
+ const windowSessionParams = new WindowSessionParams()
74
+
75
+ if (path && path !== '') {
76
+ walletURL.pathname = path.toLowerCase()
77
+ }
78
+
79
+ // Set session, intent and network id on walletURL
80
+ this._init = InitState.NIL
81
+ this._sessionId = `${performance.now()}`
82
+ windowSessionParams.set('sid', this._sessionId)
83
+ if (intent) {
84
+ // for the window-transport, we eagerly/optimistically set the origin host
85
+ // when connecting to the wallet, however, this will be verified and enforced
86
+ // on the wallet-side, so if a dapp provides the wrong origin, it will be dropped.
87
+ if (intent.type === 'connect') {
88
+ if (!intent.options) intent.options = {}
89
+
90
+ // skip setting origin host if we're in an browser extension execution context
91
+ // allow origin that is passed in
92
+ if (!isBrowserExtension()) {
93
+ intent.options.origin = window.location.origin
94
+ }
95
+ }
96
+ // encode intent as base6 url-encoded param
97
+ windowSessionParams.set('intent', base64EncodeObject(intent))
98
+ }
99
+ if (networkId) {
100
+ windowSessionParams.set('net', `${networkId}`)
101
+ }
102
+
103
+ // Open popup window on center of the app window
104
+ const windowSize = [450, 750]
105
+ const windowPos = [
106
+ Math.abs(window.screenX + window.innerWidth / 2 - windowSize[0] / 2),
107
+ Math.abs(window.screenY + window.innerHeight / 2 - windowSize[1] / 2)
108
+ ]
109
+
110
+ const windowFeatures =
111
+ `toolbar=0,location=0,menubar=0,scrollbars=yes,status=yes` +
112
+ `,width=${windowSize[0]},height=${windowSize[1]}` +
113
+ `,left=${windowPos[0]},top=${windowPos[1]}`
114
+
115
+ // serialize params
116
+ walletURL.search = windowSessionParams.toString()
117
+
118
+ this.walletWindow = window.open(walletURL.href, 'sequence.app', windowFeatures)
119
+
120
+ // TODO: move this somewhere else
121
+ // TODO: perhaps we trigger a .on('openTimeout') event..? maybe.. could help.
122
+
123
+ // Popup blocking detection and notice
124
+ // let warned = false
125
+ // const warnPopupBlocked = () => {
126
+ // if (warned) return
127
+ // warned = true
128
+ // // alert('popup is blocked! hey yo') // NOTE: for debug purposes only
129
+ // throw new Error('popup is blocked')
130
+ // }
131
+
132
+ // const popupCheck = setTimeout(() => {
133
+ // if (!popup || popup.closed || typeof popup.closed === 'undefined') {
134
+ // // popup is definitely blocked if we reach here.
135
+ // warnPopupBlocked()
136
+ // }
137
+ // }, 1000)
138
+
139
+ // const popupBlocked = popup === null || popup === undefined
140
+ // if (popupBlocked) {
141
+ // warnPopupBlocked()
142
+ // return
143
+ // }
144
+ }
145
+
146
+ closeWallet() {
147
+ this.close()
148
+ this.walletWindow?.close()
149
+ }
150
+
151
+ // onmessage, receives ProviderMessageResponse from the wallet post-message transport
152
+ private onWindowEvent = (event: MessageEvent) => {
153
+ // Security check, ensure message is coming from wallet origin url
154
+ if (event.origin !== this.walletURL.origin) {
155
+ // Safetly can skip events not from the wallet
156
+ return
157
+ }
158
+
159
+ let message: ProviderMessage<any>
160
+ try {
161
+ message = JSON.parse(event.data)
162
+ } catch (err) {
163
+ // event is not a ProviderMessage JSON object, skip
164
+ return
165
+ }
166
+
167
+ if (!message) {
168
+ throw new Error('ProviderMessage object is empty')
169
+ }
170
+
171
+ // handle message with base message provider
172
+ this.handleMessage(message)
173
+ }
174
+
175
+ sendMessage(message: ProviderMessage<any>) {
176
+ if (!this.walletWindow) {
177
+ logger.warn('WindowMessageProvider: sendMessage failed as walletWindow is unavailable')
178
+ return
179
+ }
180
+ const postedMessage = typeof message !== 'string' ? JSON.stringify(message) : message
181
+ this.walletWindow.postMessage(postedMessage, this.walletURL.origin)
182
+ }
183
+ }
package/src/types.ts ADDED
@@ -0,0 +1,252 @@
1
+ import { NetworkConfig, WalletContext, JsonRpcRequest, JsonRpcResponse, JsonRpcHandler } from '@0xsequence/network'
2
+ import { TypedData } from '@0xsequence/utils'
3
+
4
+ export interface ProviderTransport extends JsonRpcHandler, ProviderMessageTransport, ProviderMessageRequestHandler {
5
+ register(): void
6
+ unregister(): void
7
+
8
+ openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number): void
9
+ closeWallet(): void
10
+
11
+ isOpened(): boolean
12
+ isConnected(): boolean
13
+
14
+ on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void
15
+ once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void
16
+ emit<K extends keyof ProviderEventTypes>(event: K, ...args: Parameters<ProviderEventTypes[K]>): boolean
17
+
18
+ waitUntilOpened(): Promise<WalletSession | undefined>
19
+ waitUntilConnected(): Promise<ConnectDetails>
20
+ }
21
+
22
+ export interface WalletTransport extends JsonRpcHandler, ProviderMessageTransport, ProviderMessageRequestHandler {
23
+ register(): void
24
+ unregister(): void
25
+
26
+ notifyOpen(openInfo: { chainId?: string, sessionId?: string, session?: WalletSession, error?: string }): void
27
+ notifyClose(error?: ProviderRpcError): void
28
+
29
+ notifyConnect(connectDetails: ConnectDetails): void
30
+ notifyAccountsChanged(accounts: string[]): void
31
+ notifyChainChanged(chainIdHex: string): void
32
+ notifyNetworks(networks: NetworkConfig[]): void
33
+ }
34
+
35
+ export interface ProviderMessage<T> {
36
+ idx: number // message id number
37
+ type: string // message type
38
+ data: T // the ethereum json-rpc payload
39
+ chainId?: number // chain id which the message is intended
40
+ }
41
+
42
+ export type ProviderMessageRequest = ProviderMessage<JsonRpcRequest>
43
+
44
+ export type ProviderMessageResponse = ProviderMessage<JsonRpcResponse>
45
+
46
+ // ProviderMessageCallback is used to respond to ProviderMessage requests. The error
47
+ // argument is for exceptions during the execution, and response is the response payload
48
+ // which may contain the result or an error payload from the wallet.
49
+ export type ProviderMessageResponseCallback = (error?: ProviderRpcError, response?: ProviderMessageResponse) => void
50
+
51
+ export interface ProviderRpcError extends Error {
52
+ message: string
53
+ code?: number
54
+ data?: { [key: string]: any }
55
+ }
56
+
57
+ export interface ProviderMessageRequestHandler {
58
+ // sendMessageRequest sends a ProviderMessageRequest over the wire to the wallet.
59
+ // This method is similar to `sendMessage`, but it expects a response to this message.
60
+ sendMessageRequest(message: ProviderMessageRequest): Promise<ProviderMessageResponse>
61
+ }
62
+
63
+ export interface ProviderMessageTransport {
64
+ // handleMessage will handle a message received from the remote wallet
65
+ handleMessage(message: ProviderMessage<any>): void
66
+
67
+ // sendMessage will send the provider message over the wire
68
+ sendMessage(message: ProviderMessage<any>): void
69
+ }
70
+
71
+ export type WindowSessionParam = 'sid' | 'net' | 'intent'
72
+
73
+ export interface WindowSessionParams extends URLSearchParams {
74
+ get(name: WindowSessionParam): string | null
75
+ set(name: WindowSessionParam, value: string): void
76
+ }
77
+
78
+ export class WindowSessionParams extends URLSearchParams {
79
+ static new(init?: Record<WindowSessionParam, string> | string) {
80
+ return new URLSearchParams(init) as WindowSessionParams
81
+ }
82
+ }
83
+
84
+ export interface TransportSession {
85
+ sessionId?: string | null
86
+ networkId?: string | number | null,
87
+ intent?: OpenWalletIntent,
88
+ }
89
+
90
+ export enum EventType {
91
+ OPEN = 'open',
92
+ CLOSE = 'close',
93
+
94
+ MESSAGE = 'message',
95
+ CONNECT = 'connect',
96
+ DISCONNECT = 'disconnect',
97
+ ACCOUNTS_CHANGED = 'accountsChanged',
98
+ CHAIN_CHANGED = 'chainChanged',
99
+
100
+ NETWORKS = 'networks',
101
+ WALLET_CONTEXT = 'walletContext',
102
+
103
+ INIT = 'init',
104
+ DEBUG = '_debug'
105
+ }
106
+
107
+ export interface WalletEventTypes {
108
+ 'open': (openInfo: { chainId?: string, sessionId?: string, session?: WalletSession, error?: string }) => void
109
+ 'close': (error?: ProviderRpcError) => void
110
+
111
+ 'connect': (connectDetails: ConnectDetails) => void
112
+ 'disconnect': (error?: ProviderRpcError) => void
113
+
114
+ 'accountsChanged': (accounts: string[]) => void
115
+ 'chainChanged': (chainIdHex: string) => void
116
+
117
+ 'networks': (networks: NetworkConfig[]) => void
118
+ 'walletContext': (walletContext: WalletContext) => void
119
+ }
120
+
121
+ export interface ProviderEventTypes extends WalletEventTypes {
122
+ 'message': (message: ProviderMessageResponse) => void
123
+ }
124
+
125
+ export enum OpenState {
126
+ CLOSED = 0,
127
+ OPENING = 1,
128
+ OPENED = 2
129
+ }
130
+
131
+ export enum InitState {
132
+ NIL = 0,
133
+ SENT_NONCE = 1,
134
+ OK = 2
135
+ }
136
+
137
+ export interface ConnectOptions {
138
+ // networkId specifics the default network a dapp would like to connect to. This field
139
+ // is optional as it can be provided a number of different ways.
140
+ networkId?: string | number
141
+
142
+ // app name of the dapp which will be announced to user on connect screen
143
+ app?: string
144
+
145
+ // origin hint of the dapp's host opening the wallet. This value will automatically
146
+ // be determined and verified for integrity, and can be omitted.
147
+ origin?: string
148
+
149
+ // expiry number (in seconds) to expire connect session. default is 1 week of seconds.
150
+ expiry?: number
151
+
152
+ // authorize will perform an ETHAuth eip712 signing and return the proof to the dapp.
153
+ authorize?: boolean
154
+
155
+ // askForEmail will prompt to give permission to the dapp to access email address
156
+ // TODO: this feature is currently not used as the wallet does not report emails yet
157
+ askForEmail?: boolean
158
+
159
+ // refresh flag will force a full re-connect (ie. disconnect then connect again)
160
+ refresh?: boolean
161
+
162
+ // keepWalletOpened will keep the wallet window opened after connecting. The default
163
+ // is to automatically close the wallet after connecting.
164
+ keepWalletOpened?: boolean
165
+ }
166
+
167
+ export interface ConnectDetails {
168
+ // chainId (in hex) and error are defined by EIP-1193 expected fields
169
+ chainId?: string
170
+ error?: string
171
+
172
+ // connected flag denotes user-accepted the connect request
173
+ connected: boolean
174
+
175
+ // session include account and network information needed by the dapp wallet provider.
176
+ session?: WalletSession
177
+
178
+ // proof is a signed typedData (EIP-712) payload using ETHAuth domain.
179
+ // NOTE: the proof is signed to the `authChainId`, as the canonical auth chain.
180
+ proof?: ETHAuthProof
181
+
182
+ // email address provided from wallet to the dapp, as request + accepted
183
+ // by a user during a connect request
184
+ email?: string
185
+ }
186
+
187
+ export type PromptConnectDetails = Pick<ConnectDetails, 'chainId' | 'error' | 'connected' | 'proof' | 'email'>
188
+
189
+ export type OpenWalletIntent =
190
+ { type: 'connect'; options?: ConnectOptions } |
191
+ { type: 'jsonRpcRequest'; method: string }
192
+
193
+ export interface MessageToSign {
194
+ message?: string
195
+ typedData?: TypedData
196
+ chainId?: number
197
+ }
198
+
199
+ export interface ETHAuthProof {
200
+ // eip712 typed-data payload for ETHAuth domain as input
201
+ typedData: TypedData
202
+
203
+ // signature encoded in an ETHAuth proof string
204
+ proofString: string
205
+ }
206
+
207
+ export interface WalletSession {
208
+ // Wallet context
209
+ walletContext?: WalletContext
210
+
211
+ // Account address of the wallet
212
+ accountAddress?: string
213
+
214
+ // Networks in use for the session. The default/dapp network will show
215
+ // up as the first one in the list as the "main chain"
216
+ networks?: NetworkConfig[]
217
+
218
+ // Caching provider responses for things such as account and chainId
219
+ providerCache?: { [key: string]: any }
220
+ }
221
+
222
+ export class ProviderError extends Error {
223
+ constructor(message?: string) {
224
+ super(message)
225
+ this.name = 'ProviderError'
226
+ }
227
+ }
228
+
229
+ export const ErrSignedInRequired = new ProviderError('Wallet is not signed in. Connect a wallet and try again.')
230
+
231
+ // TODO: lets build some nice error handling tools, prob in /utils ...
232
+
233
+ export interface TypedEventEmitter<Events>{
234
+ addListener<E extends keyof Events> (event: E, listener: Events[E]): this
235
+ on<E extends keyof Events> (event: E, listener: Events[E]): this
236
+ once<E extends keyof Events> (event: E, listener: Events[E]): this
237
+ prependListener<E extends keyof Events> (event: E, listener: Events[E]): this
238
+ prependOnceListener<E extends keyof Events> (event: E, listener: Events[E]): this
239
+
240
+ off<E extends keyof Events>(event: E, listener: Events[E]): this
241
+ removeAllListeners<E extends keyof Events> (event?: E): this
242
+ removeListener<E extends keyof Events> (event: E, listener: Events[E]): this
243
+
244
+ emit<E extends keyof Events> (event: E, ...args: Arguments<Events[E]>): boolean
245
+ eventNames (): (keyof Events | string | symbol)[]
246
+ listeners<E extends keyof Events> (event: E): Function[]
247
+ listenerCount<E extends keyof Events> (event: E): number
248
+ }
249
+
250
+ type Arguments<T> = [T] extends [(...args: infer U) => any]
251
+ ? U
252
+ : [T] extends [void] ? [] : [T]
package/src/utils.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { BigNumberish, BytesLike } from 'ethers'
2
+ import { WalletContext } from '@0xsequence/network'
3
+ import { WalletConfig, addressOf, DecodedSignature } from '@0xsequence/config'
4
+ import { packMessageData } from '@0xsequence/utils'
5
+ import { Web3Provider } from './provider'
6
+ import { isValidSignature as _isValidSignature, recoverConfig } from '@0xsequence/wallet'
7
+
8
+ export const isValidSignature = async (
9
+ address: string,
10
+ digest: Uint8Array,
11
+ sig: string,
12
+ provider: Web3Provider,
13
+ chainId?: number,
14
+ walletContext?: WalletContext
15
+ ): Promise<boolean | undefined> => {
16
+ chainId = chainId || await provider.getChainId()
17
+ walletContext = walletContext || await provider.getSigner().getWalletContext()
18
+ return _isValidSignature(address, digest, sig, provider, walletContext, chainId)
19
+ }
20
+
21
+ export const recoverWalletConfig = async (
22
+ address: string,
23
+ digest: BytesLike,
24
+ signature: string | DecodedSignature,
25
+ chainId: BigNumberish,
26
+ walletContext?: WalletContext
27
+ ): Promise<WalletConfig> => {
28
+ const subDigest = packMessageData(address, chainId, digest)
29
+ const config = await recoverConfig(subDigest, signature)
30
+
31
+ if (walletContext) {
32
+ const recoveredWalletAddress = addressOf(config, walletContext)
33
+ if (config.address && config.address !== recoveredWalletAddress) {
34
+ throw new Error('recovered address does not match the WalletConfig address, check the WalletContext')
35
+ } else {
36
+ config.address = recoveredWalletAddress
37
+ }
38
+ }
39
+
40
+ return config
41
+ }
42
+
43
+ export const isBrowserExtension = (): boolean => window.location.protocol === 'chrome-extension:' || window.location.protocol === 'moz-extension:'
44
+
45
+ // window.localstorage helper
46
+ export class LocalStore<T extends Object = string> {
47
+ readonly key: string
48
+
49
+ constructor(key: string, public def?: T) {
50
+ this.key = key
51
+ }
52
+
53
+ get(): T | undefined {
54
+ const val = window.localStorage.getItem(this.key)
55
+
56
+ if (val === null) {
57
+ return this.def
58
+ }
59
+
60
+ try {
61
+ return JSON.parse(val)
62
+ } catch (err) {
63
+ console.error(err)
64
+ }
65
+
66
+ return
67
+ }
68
+
69
+ set(val: T | undefined) {
70
+ val ? window.localStorage.setItem(this.key, JSON.stringify(val)) : window.localStorage.removeItem(this.key)
71
+ }
72
+
73
+ del() {
74
+ window.localStorage.removeItem(this.key)
75
+ }
76
+ }