@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,702 @@
|
|
|
1
|
+
import { EventEmitter2 as EventEmitter } from 'eventemitter2'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ProviderMessage,
|
|
5
|
+
ProviderMessageRequest,
|
|
6
|
+
ProviderMessageResponse,
|
|
7
|
+
ProviderMessageResponseCallback,
|
|
8
|
+
ProviderMessageRequestHandler,
|
|
9
|
+
MessageToSign,
|
|
10
|
+
ProviderRpcError,
|
|
11
|
+
ConnectOptions,
|
|
12
|
+
ConnectDetails,
|
|
13
|
+
PromptConnectDetails,
|
|
14
|
+
WalletSession,
|
|
15
|
+
ErrSignedInRequired,
|
|
16
|
+
ProviderEventTypes,
|
|
17
|
+
TypedEventEmitter
|
|
18
|
+
} from '../types'
|
|
19
|
+
|
|
20
|
+
import { BigNumber, ethers } from 'ethers'
|
|
21
|
+
import { JsonRpcProvider, ExternalProvider } from '@ethersproject/providers'
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
Networks,
|
|
25
|
+
NetworkConfig,
|
|
26
|
+
JsonRpcHandler,
|
|
27
|
+
JsonRpcRequest,
|
|
28
|
+
JsonRpcResponseCallback,
|
|
29
|
+
JsonRpcResponse
|
|
30
|
+
} from '@0xsequence/network'
|
|
31
|
+
|
|
32
|
+
import { Signer, Account } from '@0xsequence/wallet'
|
|
33
|
+
import { isSignedTransactions, SignedTransactions, TransactionRequest } from '@0xsequence/transactions'
|
|
34
|
+
|
|
35
|
+
import { signAuthorization, AuthorizationOptions } from '@0xsequence/auth'
|
|
36
|
+
|
|
37
|
+
import { logger, TypedData } from '@0xsequence/utils'
|
|
38
|
+
|
|
39
|
+
export interface WalletSignInOptions {
|
|
40
|
+
connect?: boolean
|
|
41
|
+
mainnetNetworks?: Networks
|
|
42
|
+
testnetNetworks?: Networks
|
|
43
|
+
defaultNetworkId?: string | number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class WalletRequestHandler implements ExternalProvider, JsonRpcHandler, ProviderMessageRequestHandler {
|
|
47
|
+
private signer: Signer | null
|
|
48
|
+
private prompter: WalletUserPrompter | null
|
|
49
|
+
private mainnetNetworks: NetworkConfig[]
|
|
50
|
+
private testnetNetworks: NetworkConfig[]
|
|
51
|
+
|
|
52
|
+
private _connectOptions?: ConnectOptions
|
|
53
|
+
private _defaultNetworkId?: string | number
|
|
54
|
+
private _chainId?: number
|
|
55
|
+
|
|
56
|
+
private events: TypedEventEmitter<ProviderEventTypes> = new EventEmitter() as TypedEventEmitter<ProviderEventTypes>
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
signer: Signer | null,
|
|
60
|
+
prompter: WalletUserPrompter | null,
|
|
61
|
+
mainnetNetworks: Networks,
|
|
62
|
+
testnetNetworks: Networks = []
|
|
63
|
+
) {
|
|
64
|
+
this.signer = signer
|
|
65
|
+
this.prompter = prompter
|
|
66
|
+
this.mainnetNetworks = mainnetNetworks
|
|
67
|
+
this.testnetNetworks = testnetNetworks
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async signIn(signer: Signer | null, options: WalletSignInOptions = {}) {
|
|
71
|
+
this.signer = signer
|
|
72
|
+
|
|
73
|
+
const { connect, mainnetNetworks, testnetNetworks, defaultNetworkId } = options
|
|
74
|
+
|
|
75
|
+
if (mainnetNetworks && mainnetNetworks.length > 0) {
|
|
76
|
+
this.mainnetNetworks = mainnetNetworks
|
|
77
|
+
}
|
|
78
|
+
if (testnetNetworks && testnetNetworks.length > 0) {
|
|
79
|
+
this.testnetNetworks = testnetNetworks
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
(!this.mainnetNetworks || this.mainnetNetworks.length === 0) &&
|
|
83
|
+
(!this.testnetNetworks || this.testnetNetworks.length === 0)
|
|
84
|
+
) {
|
|
85
|
+
throw new Error('signIn failed as network configuration is empty')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const networkId = defaultNetworkId || this._defaultNetworkId
|
|
89
|
+
if (networkId) {
|
|
90
|
+
if (!(await this.setDefaultNetwork(networkId, false))) {
|
|
91
|
+
throw new Error(`WalletRequestHandler setup unable to set defaultNetworkId ${networkId}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Optionally, connect the dapp and wallet. In case connectOptions are provided, we will perform
|
|
96
|
+
// necessary auth request, and then notify the dapp of the 'connect' details.
|
|
97
|
+
//
|
|
98
|
+
// NOTE: if a user is signing into a dapp from a fresh state, and and auth request is made
|
|
99
|
+
// we don't trigger the promptConnect flow, as we consider the user just authenticated
|
|
100
|
+
// for this dapp, so its safe to authorize in the connect() method without the prompt.
|
|
101
|
+
//
|
|
102
|
+
// NOTE: signIn can optionally connect and notify dapp at this time for new signIn flows
|
|
103
|
+
if (connect) {
|
|
104
|
+
const connectOptions = this._connectOptions
|
|
105
|
+
|
|
106
|
+
const connectDetails = await this.connect(connectOptions)
|
|
107
|
+
this.notifyConnect(connectDetails)
|
|
108
|
+
|
|
109
|
+
if (!connectOptions || connectOptions.keepWalletOpened !== true) {
|
|
110
|
+
this.notifyClose()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async connect(options?: ConnectOptions): Promise<ConnectDetails> {
|
|
116
|
+
if (!this.signer) {
|
|
117
|
+
return {
|
|
118
|
+
connected: false,
|
|
119
|
+
chainId: '0x0',
|
|
120
|
+
error: 'unable to connect without signed in account'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const connectDetails: ConnectDetails = {
|
|
125
|
+
connected: true,
|
|
126
|
+
chainId: ethers.utils.hexlify(await this.getChainId())
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (options && options.authorize) {
|
|
130
|
+
// Perform ethauth eip712 request and construct the ConnectDetails response
|
|
131
|
+
// including the auth proof
|
|
132
|
+
const authOptions: AuthorizationOptions = {
|
|
133
|
+
app: options.app,
|
|
134
|
+
origin: options.origin,
|
|
135
|
+
expiry: options.expiry
|
|
136
|
+
}
|
|
137
|
+
// if (typeof(options.authorize) === 'object') {
|
|
138
|
+
// authOptions = { ...authOptions, ...options.authorize }
|
|
139
|
+
// }
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
connectDetails.proof = await signAuthorization(this.signer, authOptions)
|
|
143
|
+
} catch (err) {
|
|
144
|
+
logger.warn(`connect, signAuthorization failed for options: ${JSON.stringify(options)}, due to: ${err.message}`)
|
|
145
|
+
return {
|
|
146
|
+
connected: false,
|
|
147
|
+
chainId: '0x0',
|
|
148
|
+
error: `signAuthorization failed: ${err.message}`
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Build session response for connect details
|
|
154
|
+
connectDetails.session = await this.walletSession()
|
|
155
|
+
|
|
156
|
+
return connectDetails
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
promptConnect = async (options?: ConnectOptions): Promise<ConnectDetails> => {
|
|
160
|
+
if (!options && !this._connectOptions) {
|
|
161
|
+
// this is an unexpected state and should not happen
|
|
162
|
+
throw new Error('prompter connect options are empty')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!this.prompter) {
|
|
166
|
+
// if prompter is null, we'll auto connect
|
|
167
|
+
return this.connect(options)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const promptConnectDetails = await this.prompter.promptConnect(options || this._connectOptions).catch(_ => {
|
|
171
|
+
return { connected: false } as ConnectDetails
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const connectDetails: ConnectDetails = promptConnectDetails
|
|
175
|
+
if (connectDetails.connected && !connectDetails.session) {
|
|
176
|
+
connectDetails.session = await this.walletSession()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return promptConnectDetails
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// sendMessageRequest will unwrap the ProviderMessageRequest and send it to the JsonRpcHandler
|
|
183
|
+
// (aka, the signer in this instance) and then responds with a wrapped response of
|
|
184
|
+
// ProviderMessageResponse to be sent over the transport
|
|
185
|
+
sendMessageRequest(message: ProviderMessageRequest): Promise<ProviderMessageResponse> {
|
|
186
|
+
return new Promise(resolve => {
|
|
187
|
+
this.sendAsync(
|
|
188
|
+
message.data,
|
|
189
|
+
(error: any, response?: JsonRpcResponse) => {
|
|
190
|
+
// TODO: if response includes data.error, why do we need a separate error argument here?
|
|
191
|
+
|
|
192
|
+
const responseMessage: ProviderMessageResponse = {
|
|
193
|
+
...message,
|
|
194
|
+
data: response!
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// NOTE: we always resolve here, are the sendAsync call will wrap any exceptions
|
|
198
|
+
// in the error field of the response to ensure we send back to the user
|
|
199
|
+
resolve(responseMessage)
|
|
200
|
+
},
|
|
201
|
+
message.chainId
|
|
202
|
+
)
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// sendAsync implements the JsonRpcHandler interface for sending JsonRpcRequests to the wallet
|
|
207
|
+
sendAsync = async (request: JsonRpcRequest, callback: JsonRpcResponseCallback, chainId?: number) => {
|
|
208
|
+
const response: JsonRpcResponse = {
|
|
209
|
+
jsonrpc: '2.0',
|
|
210
|
+
id: request.id!,
|
|
211
|
+
result: null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
// only allow public json rpc method to the provider when user is not logged in, aka signer is not set
|
|
216
|
+
if ((!this.signer || this.signer === null) && !permittedJsonRpcMethods.includes(request.method)) {
|
|
217
|
+
// throw new Error(`not logged in. ${request.method} is unavailable`)
|
|
218
|
+
throw ErrSignedInRequired
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// wallet signer
|
|
222
|
+
const signer = this.signer
|
|
223
|
+
if (!signer) throw new Error('WalletRequestHandler: wallet signer is not configured')
|
|
224
|
+
|
|
225
|
+
// fetch the provider for the specific chain, or undefined will select defaultChain
|
|
226
|
+
const provider = await signer.getProvider(chainId)
|
|
227
|
+
if (!provider) throw new Error(`WalletRequestHandler: wallet provider is not configured for chainId ${chainId}`)
|
|
228
|
+
|
|
229
|
+
switch (request.method) {
|
|
230
|
+
case 'net_version': {
|
|
231
|
+
const result = await provider.send('net_version', [])
|
|
232
|
+
response.result = result
|
|
233
|
+
break
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'eth_chainId': {
|
|
237
|
+
const result = await provider.send('eth_chainId', [])
|
|
238
|
+
response.result = result
|
|
239
|
+
break
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
case 'eth_accounts': {
|
|
243
|
+
const walletAddress = await signer.getAddress()
|
|
244
|
+
response.result = [walletAddress]
|
|
245
|
+
break
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
case 'eth_getBalance': {
|
|
249
|
+
const [accountAddress, blockTag] = request.params!
|
|
250
|
+
const walletBalance = await provider.getBalance(accountAddress, blockTag)
|
|
251
|
+
response.result = walletBalance.toHexString()
|
|
252
|
+
break
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case 'eth_sign': {
|
|
256
|
+
// note: message from json-rpc input is in hex format
|
|
257
|
+
const [signingAddress, message] = request.params!
|
|
258
|
+
|
|
259
|
+
let sig = ''
|
|
260
|
+
// TODO:
|
|
261
|
+
// if (process.env.TEST_MODE === 'true' && this.prompter === null) {
|
|
262
|
+
if (this.prompter === null) {
|
|
263
|
+
// prompter is null, so we'll sign from here
|
|
264
|
+
sig = await signer.signMessage(ethers.utils.arrayify(message), chainId)
|
|
265
|
+
} else {
|
|
266
|
+
// prompt user to provide the response
|
|
267
|
+
sig = await this.prompter.promptSignMessage({ chainId: chainId, message: message })
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (sig && sig.length > 0) {
|
|
271
|
+
response.result = sig
|
|
272
|
+
} else {
|
|
273
|
+
// The user has declined the request when value is null
|
|
274
|
+
throw new Error('declined by user')
|
|
275
|
+
}
|
|
276
|
+
break
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
case 'eth_signTypedData':
|
|
280
|
+
case 'eth_signTypedData_v4': {
|
|
281
|
+
// note: signingAddress from json-rpc input is in hex format, and typedDataObject
|
|
282
|
+
// should be an object, but in some instances may be double string encoded
|
|
283
|
+
const [signingAddress, typedDataObject] = request.params!
|
|
284
|
+
|
|
285
|
+
let typedData: TypedData | undefined = undefined
|
|
286
|
+
if (typeof typedDataObject === 'string') {
|
|
287
|
+
try {
|
|
288
|
+
typedData = JSON.parse(typedDataObject)
|
|
289
|
+
} catch (e) {}
|
|
290
|
+
} else {
|
|
291
|
+
typedData = typedDataObject
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!typedData || !typedData.domain || !typedData.types || !typedData.message) {
|
|
295
|
+
throw new Error('invalid typedData object')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let sig = ''
|
|
299
|
+
if (this.prompter === null) {
|
|
300
|
+
// prompter is null, so we'll sign from here
|
|
301
|
+
sig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainId)
|
|
302
|
+
} else {
|
|
303
|
+
// prompt user to provide the response
|
|
304
|
+
sig = await this.prompter.promptSignMessage({ chainId: chainId, typedData: typedData })
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (sig && sig.length > 0) {
|
|
308
|
+
response.result = sig
|
|
309
|
+
} else {
|
|
310
|
+
// The user has declined the request when value is null
|
|
311
|
+
throw new Error('declined by user')
|
|
312
|
+
}
|
|
313
|
+
break
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'eth_sendTransaction': {
|
|
317
|
+
// https://eth.wiki/json-rpc/API#eth_sendtransaction
|
|
318
|
+
const [transactionParams] = request.params!
|
|
319
|
+
|
|
320
|
+
let txnHash = ''
|
|
321
|
+
if (this.prompter === null) {
|
|
322
|
+
// prompter is null, so we'll send from here
|
|
323
|
+
const txnResponse = await signer.sendTransaction(transactionParams, chainId)
|
|
324
|
+
txnHash = txnResponse.hash
|
|
325
|
+
} else {
|
|
326
|
+
// prompt user to provide the response
|
|
327
|
+
txnHash = await this.prompter.promptSendTransaction(transactionParams, chainId)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (txnHash) {
|
|
331
|
+
response.result = txnHash
|
|
332
|
+
} else {
|
|
333
|
+
// The user has declined the request when value is null
|
|
334
|
+
throw new Error('declined by user')
|
|
335
|
+
}
|
|
336
|
+
break
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
case 'eth_signTransaction': {
|
|
340
|
+
// https://eth.wiki/json-rpc/API#eth_signTransaction
|
|
341
|
+
const [transaction] = request.params!
|
|
342
|
+
const sender = ethers.utils.getAddress(transaction.from)
|
|
343
|
+
|
|
344
|
+
if (sender !== (await signer.getAddress())) {
|
|
345
|
+
throw new Error('sender address does not match wallet')
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (this.prompter === null) {
|
|
349
|
+
// The eth_signTransaction method expects a `string` return value we instead return a `SignedTransactions` object,
|
|
350
|
+
// this can only be broadcasted using an RPC provider with support for signed Sequence transactions, like this one.
|
|
351
|
+
//
|
|
352
|
+
// TODO: verify serializing / transporting the SignedTransaction object works as expected, most likely however
|
|
353
|
+
// we will want to resolveProperties the bignumber values to hex strings
|
|
354
|
+
response.result = await signer.signTransactions(transaction, chainId)
|
|
355
|
+
} else {
|
|
356
|
+
response.result = await this.prompter.promptSignTransaction(transaction, chainId)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
break
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
case 'eth_sendRawTransaction': {
|
|
363
|
+
// NOTE: we're not using a prompter here as the transaction is already signed
|
|
364
|
+
// and would have prompted the user upon signing.
|
|
365
|
+
|
|
366
|
+
// https://eth.wiki/json-rpc/API#eth_sendRawTransaction
|
|
367
|
+
if (isSignedTransactions(request.params![0])) {
|
|
368
|
+
const txChainId = BigNumber.from(request.params![0].chainId).toNumber()
|
|
369
|
+
const tx = await (await signer.getRelayer(txChainId))!.relay(request.params![0])
|
|
370
|
+
response.result = (await tx).hash
|
|
371
|
+
} else {
|
|
372
|
+
const tx = await provider.sendTransaction(request.params![0])
|
|
373
|
+
response.result = tx.hash
|
|
374
|
+
}
|
|
375
|
+
break
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
case 'eth_getTransactionCount': {
|
|
379
|
+
const address = ethers.utils.getAddress(request.params![0] as string)
|
|
380
|
+
const tag = request.params![1]
|
|
381
|
+
|
|
382
|
+
const walletAddress = ethers.utils.getAddress(await signer.getAddress())
|
|
383
|
+
|
|
384
|
+
if (address === walletAddress) {
|
|
385
|
+
const count = await signer.getTransactionCount(tag)
|
|
386
|
+
response.result = ethers.BigNumber.from(count).toHexString()
|
|
387
|
+
} else {
|
|
388
|
+
const count = await provider.getTransactionCount(address, tag)
|
|
389
|
+
response.result = ethers.BigNumber.from(count).toHexString()
|
|
390
|
+
}
|
|
391
|
+
break
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
case 'eth_blockNumber': {
|
|
395
|
+
response.result = await provider.getBlockNumber()
|
|
396
|
+
break
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
case 'eth_getBlockByNumber': {
|
|
400
|
+
response.result = await provider.getBlock(request.params![0] /* , jsonRpcRequest.params[1] */)
|
|
401
|
+
break
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
case 'eth_getBlockByHash': {
|
|
405
|
+
response.result = await provider.getBlock(request.params![0] /* , jsonRpcRequest.params[1] */)
|
|
406
|
+
break
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
case 'eth_getTransactionByHash': {
|
|
410
|
+
response.result = await provider.getTransaction(request.params![0])
|
|
411
|
+
break
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case 'eth_call': {
|
|
415
|
+
const [transactionObject, blockTag] = request.params!
|
|
416
|
+
response.result = await provider.call(transactionObject, blockTag)
|
|
417
|
+
break
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
case 'eth_getCode': {
|
|
421
|
+
const [contractAddress, blockTag] = request.params!
|
|
422
|
+
response.result = await provider.getCode(contractAddress, blockTag)
|
|
423
|
+
break
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
case 'eth_estimateGas': {
|
|
427
|
+
const [transactionObject] = request.params!
|
|
428
|
+
response.result = await provider.estimateGas(transactionObject)
|
|
429
|
+
break
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
case 'eth_gasPrice': {
|
|
433
|
+
const gasPrice = await provider.getGasPrice()
|
|
434
|
+
response.result = gasPrice.toHexString()
|
|
435
|
+
break
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// smart wallet method
|
|
439
|
+
case 'sequence_getWalletContext': {
|
|
440
|
+
response.result = await signer.getWalletContext()
|
|
441
|
+
break
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// smart wallet method
|
|
445
|
+
case 'sequence_getWalletConfig': {
|
|
446
|
+
const [chainId] = request.params!
|
|
447
|
+
response.result = await signer.getWalletConfig(chainId)
|
|
448
|
+
break
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// smart wallet method
|
|
452
|
+
case 'sequence_getWalletState': {
|
|
453
|
+
const [chainId] = request.params!
|
|
454
|
+
response.result = await signer.getWalletState(chainId)
|
|
455
|
+
break
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// smart wallet method
|
|
459
|
+
case 'sequence_getNetworks': {
|
|
460
|
+
// NOTE: must ensure that the response result below returns clean serialized data, which is to omit
|
|
461
|
+
// the provider and relayer objects and only return the urls so can be reinstantiated on dapp side.
|
|
462
|
+
// This is handled by this.getNetworks() but noted here for future readers.
|
|
463
|
+
response.result = await this.getNetworks(true)
|
|
464
|
+
break
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// smart wallet method
|
|
468
|
+
case 'sequence_updateConfig': {
|
|
469
|
+
throw new Error('sequence_updateConfig method is not allowed from a dapp')
|
|
470
|
+
// NOTE: method is disabled as we don't need a dapp to request to update a config.
|
|
471
|
+
// However, if we ever want this, we can enable it but must also use the prompter
|
|
472
|
+
// for confirmation.
|
|
473
|
+
//
|
|
474
|
+
// const [newConfig] = request.params
|
|
475
|
+
// response.result = await signer.updateConfig(newConfig)
|
|
476
|
+
break
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// smart wallet method
|
|
480
|
+
case 'sequence_publishConfig': {
|
|
481
|
+
throw new Error('sequence_publishConfig method is not allowed from a dapp')
|
|
482
|
+
break
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// relayer method
|
|
486
|
+
case 'sequence_estimateGasLimits': {
|
|
487
|
+
// TODO
|
|
488
|
+
break
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// relayer method
|
|
492
|
+
case 'sequence_gasRefundOptions': {
|
|
493
|
+
// TODO
|
|
494
|
+
break
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// relayer method
|
|
498
|
+
case 'sequence_getNonce': {
|
|
499
|
+
// TODO
|
|
500
|
+
break
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// relayer method
|
|
504
|
+
case 'sequence_relay': {
|
|
505
|
+
// TODO
|
|
506
|
+
break
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// set default network of wallet
|
|
510
|
+
case 'sequence_setDefaultNetwork': {
|
|
511
|
+
const [defaultNetworkId] = request.params!
|
|
512
|
+
|
|
513
|
+
if (!defaultNetworkId) {
|
|
514
|
+
throw new Error('invalid request, method argument defaultNetworkId cannot be empty')
|
|
515
|
+
}
|
|
516
|
+
const ok = await this.setDefaultNetwork(defaultNetworkId)
|
|
517
|
+
if (!ok) {
|
|
518
|
+
throw new Error(`unable to set default network ${defaultNetworkId}`)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
response.result = await this.getNetworks(true)
|
|
522
|
+
break
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
default: {
|
|
526
|
+
// NOTE: provider here will be chain-bound if chainId is provided
|
|
527
|
+
const providerResponse = await provider.send(request.method, request.params!)
|
|
528
|
+
response.result = providerResponse
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
} catch (err) {
|
|
532
|
+
logger.error(err)
|
|
533
|
+
|
|
534
|
+
// See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#rpc-errors
|
|
535
|
+
response.result = null
|
|
536
|
+
response.error = {
|
|
537
|
+
...new Error(err),
|
|
538
|
+
code: 4001
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
callback(undefined, response)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
|
|
546
|
+
this.events.on(event, fn as any)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
|
|
550
|
+
this.events.once(event, fn as any)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async getAddress(): Promise<string> {
|
|
554
|
+
if (!this.signer) {
|
|
555
|
+
return ''
|
|
556
|
+
} else {
|
|
557
|
+
return this.signer.getAddress()
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async getChainId(): Promise<number> {
|
|
562
|
+
if (!this.signer) {
|
|
563
|
+
return 0
|
|
564
|
+
} else {
|
|
565
|
+
if (this._chainId) return this._chainId // memoized
|
|
566
|
+
this._chainId = await this.signer.getChainId()
|
|
567
|
+
return this._chainId
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
get connectOptions(): ConnectOptions | undefined {
|
|
572
|
+
return this._connectOptions
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
setConnectOptions(options: ConnectOptions | undefined) {
|
|
576
|
+
this._connectOptions = options
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
get defaultNetworkId(): string | number | undefined {
|
|
580
|
+
return this._defaultNetworkId
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async setDefaultNetwork(chainId: string | number, notifyNetworks: boolean = true): Promise<number | undefined> {
|
|
584
|
+
if (!chainId) return undefined
|
|
585
|
+
this._defaultNetworkId = chainId
|
|
586
|
+
this._chainId = undefined
|
|
587
|
+
|
|
588
|
+
if (this.signer && (<any>this.signer).setNetworks) {
|
|
589
|
+
const defaultChainId: number = (<any>this.signer).setNetworks(this.mainnetNetworks, this.testnetNetworks, chainId)
|
|
590
|
+
if (defaultChainId && notifyNetworks) {
|
|
591
|
+
await this.notifyNetworks()
|
|
592
|
+
}
|
|
593
|
+
return defaultChainId
|
|
594
|
+
} else {
|
|
595
|
+
return undefined
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async getNetworks(jsonRpcResponse?: boolean): Promise<NetworkConfig[]> {
|
|
600
|
+
if (!this.signer) {
|
|
601
|
+
logger.warn('signer not set: getNetworks is returning an empty list')
|
|
602
|
+
return []
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const networks = await this.signer.getNetworks()
|
|
606
|
+
|
|
607
|
+
if (jsonRpcResponse) {
|
|
608
|
+
// omit provider and relayer objects as they are not serializable
|
|
609
|
+
return networks.map(n => {
|
|
610
|
+
const network: NetworkConfig = { ...n }
|
|
611
|
+
network.provider = undefined
|
|
612
|
+
network.relayer = undefined
|
|
613
|
+
return network
|
|
614
|
+
})
|
|
615
|
+
} else {
|
|
616
|
+
return networks
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async walletSession(): Promise<WalletSession | undefined> {
|
|
621
|
+
return !this.signer
|
|
622
|
+
? undefined
|
|
623
|
+
: {
|
|
624
|
+
walletContext: await this.signer.getWalletContext(),
|
|
625
|
+
accountAddress: await this.signer.getAddress(),
|
|
626
|
+
networks: await this.getNetworks(true)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
notifyConnect(connectDetails: ConnectDetails) {
|
|
631
|
+
this.events.emit('connect', connectDetails)
|
|
632
|
+
if (connectDetails.session?.accountAddress) {
|
|
633
|
+
this.events.emit('accountsChanged', [connectDetails.session?.accountAddress])
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
notifyDisconnect() {
|
|
638
|
+
this.events.emit('accountsChanged', [])
|
|
639
|
+
this.events.emit('networks', [])
|
|
640
|
+
this.events.emit('disconnect')
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async notifyNetworks(networks?: NetworkConfig[]) {
|
|
644
|
+
const n = networks || (await this.getNetworks(true))
|
|
645
|
+
this.events.emit('networks', n)
|
|
646
|
+
if (n && n.length > 0) {
|
|
647
|
+
this.events.emit('chainChanged', ethers.utils.hexlify(n[0].chainId))
|
|
648
|
+
} else {
|
|
649
|
+
this.events.emit('chainChanged', '0x0')
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async notifyWalletContext() {
|
|
654
|
+
if (!this.signer) {
|
|
655
|
+
logger.warn('signer not set: skipping to notify wallet context')
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
const walletContext = await this.signer.getWalletContext()
|
|
659
|
+
this.events.emit('walletContext', walletContext)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
notifyClose(error?: ProviderRpcError) {
|
|
663
|
+
this.events.emit('close', error)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
isSignedIn(): boolean {
|
|
667
|
+
return !!this.signer
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
getSigner(): Signer | null {
|
|
671
|
+
return this.signer
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
setSigner(signer: Signer | null) {
|
|
675
|
+
this.signer = signer
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export interface WalletUserPrompter {
|
|
680
|
+
promptConnect(options?: ConnectOptions): Promise<PromptConnectDetails>
|
|
681
|
+
promptSignMessage(message: MessageToSign): Promise<string>
|
|
682
|
+
promptSignTransaction(txn: TransactionRequest, chaindId?: number): Promise<string>
|
|
683
|
+
promptSendTransaction(txn: TransactionRequest, chaindId?: number): Promise<string>
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const permittedJsonRpcMethods = [
|
|
687
|
+
'net_version',
|
|
688
|
+
'eth_chainId',
|
|
689
|
+
'eth_getBalance',
|
|
690
|
+
'eth_getTransactionCount',
|
|
691
|
+
'eth_blockNumber',
|
|
692
|
+
'eth_getBlockByNumber',
|
|
693
|
+
'eth_getBlockByHash',
|
|
694
|
+
'eth_getTransactionByHash',
|
|
695
|
+
'eth_getCode',
|
|
696
|
+
'eth_estimateGas',
|
|
697
|
+
'eth_gasPrice',
|
|
698
|
+
|
|
699
|
+
'sequence_getWalletContext',
|
|
700
|
+
'sequence_getNetworks',
|
|
701
|
+
'sequence_setDefaultNetwork'
|
|
702
|
+
]
|