@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.
- package/CHANGELOG.md +909 -0
- package/README.md +4 -0
- package/dist/0xsequence-provider.cjs.d.ts +1 -0
- package/dist/0xsequence-provider.cjs.dev.js +3540 -0
- package/dist/0xsequence-provider.cjs.js +7 -0
- package/dist/0xsequence-provider.cjs.prod.js +3540 -0
- package/dist/0xsequence-provider.esm.js +3508 -0
- package/dist/declarations/src/commands/index.d.ts +20 -0
- package/dist/declarations/src/index.d.ts +5 -0
- package/dist/declarations/src/provider.d.ts +59 -0
- package/dist/declarations/src/transports/base-provider-transport.d.ts +40 -0
- package/dist/declarations/src/transports/base-wallet-transport.d.ts +38 -0
- package/dist/declarations/src/transports/extension-transport/base-injected-transport.d.ts +18 -0
- package/dist/declarations/src/transports/extension-transport/extension-message-handler.d.ts +12 -0
- package/dist/declarations/src/transports/extension-transport/extension-message-provider.d.ts +11 -0
- package/dist/declarations/src/transports/extension-transport/index.d.ts +3 -0
- package/dist/declarations/src/transports/index.d.ts +7 -0
- package/dist/declarations/src/transports/mux-transport/index.d.ts +1 -0
- package/dist/declarations/src/transports/mux-transport/mux-message-provider.d.ts +22 -0
- package/dist/declarations/src/transports/proxy-transport/index.d.ts +3 -0
- package/dist/declarations/src/transports/proxy-transport/proxy-message-channel.d.ts +15 -0
- package/dist/declarations/src/transports/proxy-transport/proxy-message-handler.d.ts +12 -0
- package/dist/declarations/src/transports/proxy-transport/proxy-message-provider.d.ts +12 -0
- package/dist/declarations/src/transports/wallet-request-handler.d.ts +51 -0
- package/dist/declarations/src/transports/window-transport/index.d.ts +2 -0
- package/dist/declarations/src/transports/window-transport/window-message-handler.d.ts +15 -0
- package/dist/declarations/src/transports/window-transport/window-message-provider.d.ts +13 -0
- package/dist/declarations/src/types.d.ts +170 -0
- package/dist/declarations/src/utils.d.ts +14 -0
- package/dist/declarations/src/wallet.d.ts +99 -0
- package/package.json +37 -0
- package/src/commands/index.ts +146 -0
- package/src/index.ts +5 -0
- package/src/provider.ts +494 -0
- package/src/transports/base-provider-transport.ts +411 -0
- package/src/transports/base-wallet-transport.ts +441 -0
- package/src/transports/extension-transport/base-injected-transport.ts +100 -0
- package/src/transports/extension-transport/extension-message-handler.ts +25 -0
- package/src/transports/extension-transport/extension-message-provider.ts +41 -0
- package/src/transports/extension-transport/index.ts +3 -0
- package/src/transports/index.ts +7 -0
- package/src/transports/mux-transport/index.ts +1 -0
- package/src/transports/mux-transport/mux-message-provider.ts +160 -0
- package/src/transports/proxy-transport/index.ts +3 -0
- package/src/transports/proxy-transport/proxy-message-channel.ts +58 -0
- package/src/transports/proxy-transport/proxy-message-handler.ts +46 -0
- package/src/transports/proxy-transport/proxy-message-provider.ts +81 -0
- package/src/transports/wallet-request-handler.ts +702 -0
- package/src/transports/window-transport/index.ts +2 -0
- package/src/transports/window-transport/window-message-handler.ts +166 -0
- package/src/transports/window-transport/window-message-provider.ts +183 -0
- package/src/types.ts +252 -0
- package/src/utils.ts +76 -0
- 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
|
+
}
|