@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,494 @@
1
+ import { ethers } from 'ethers'
2
+ import { BytesLike } from '@ethersproject/bytes'
3
+ import { Web3Provider as EthersWeb3Provider, ExternalProvider, JsonRpcProvider, Networkish } from '@ethersproject/providers'
4
+ import { TypedDataDomain, TypedDataField, TypedDataSigner } from '@ethersproject/abstract-signer'
5
+ import {
6
+ NetworkConfig,
7
+ Networks,
8
+ WalletContext,
9
+ ChainIdLike,
10
+ JsonRpcHandler,
11
+ JsonRpcHandlerFunc,
12
+ JsonRpcFetchFunc,
13
+ JsonRpcRequest,
14
+ JsonRpcResponseCallback,
15
+ JsonRpcResponse,
16
+ maybeChainId,
17
+ JsonRpcVersion,
18
+ JsonRpcSender,
19
+ isJsonRpcProvider
20
+ } from '@0xsequence/network'
21
+ import { resolveArrayProperties, Signer } from '@0xsequence/wallet'
22
+ import { WalletConfig, WalletState } from '@0xsequence/config'
23
+ import { Relayer } from '@0xsequence/relayer'
24
+ import { Deferrable, shallowCopy, resolveProperties, Forbid } from '@0xsequence/utils'
25
+ import {
26
+ Transaction,
27
+ TransactionRequest,
28
+ TransactionResponse,
29
+ Transactionish,
30
+ SignedTransactions
31
+ } from '@0xsequence/transactions'
32
+ import { WalletRequestHandler } from './transports/wallet-request-handler'
33
+
34
+ export class Web3Provider extends EthersWeb3Provider implements JsonRpcHandler {
35
+ static isSequenceProvider(cand: any): cand is Web3Provider {
36
+ return isSequenceProvider(cand)
37
+ }
38
+
39
+ readonly _sender: JsonRpcSender
40
+
41
+ readonly _isSequenceProvider: boolean
42
+
43
+ // defaultChainId is the default chainId to use with requests, but may be
44
+ // overridden by passing chainId argument to a specific request
45
+ readonly _defaultChainId?: number
46
+
47
+ constructor(provider: JsonRpcProvider | JsonRpcHandler | JsonRpcFetchFunc, defaultChainId?: ChainIdLike) {
48
+ const sender = new JsonRpcSender(provider, maybeChainId(defaultChainId))
49
+ provider = sender
50
+
51
+ super(provider, 'any')
52
+
53
+ this._sender = sender
54
+ this._isSequenceProvider = true
55
+ this._defaultChainId = maybeChainId(defaultChainId)
56
+ }
57
+
58
+ sendAsync(
59
+ request: JsonRpcRequest,
60
+ callback: JsonRpcResponseCallback | ((error: any, response: any) => void),
61
+ chainId?: number
62
+ ) {
63
+ this._sender.sendAsync(request, callback, chainId)
64
+ }
65
+
66
+ send(method: string, params: Array<any>, chainId?: number): Promise<any> {
67
+ return this._sender.send(method, params, chainId)
68
+ }
69
+
70
+ getSigner(): Web3Signer {
71
+ return new Web3Signer(this, this._defaultChainId)
72
+ }
73
+
74
+ async getChainId(): Promise<number> {
75
+ const result = await this.send('eth_chainId', [])
76
+ const chainId = ethers.BigNumber.from(result).toNumber()
77
+
78
+ if (this._defaultChainId && this._defaultChainId !== chainId) {
79
+ throw new Error(`provider chainId (${chainId}) does not match provider-bound chainId ${this._defaultChainId}`)
80
+ }
81
+
82
+ return chainId
83
+ }
84
+ }
85
+
86
+ export function isSequenceProvider(provider: any): provider is Web3Provider {
87
+ const cand = provider as Web3Provider
88
+ return cand && cand.send !== undefined && cand._isSequenceProvider === true
89
+ }
90
+
91
+ export class LocalWeb3Provider extends Web3Provider {
92
+ constructor(signer: Signer, networks?: Networks) {
93
+ const walletRequestHandler = new WalletRequestHandler(signer, null, networks || [])
94
+ super(walletRequestHandler)
95
+ }
96
+ }
97
+
98
+ export class Web3Signer extends Signer implements TypedDataSigner {
99
+ readonly provider: Web3Provider
100
+ readonly defaultChainId?: number
101
+
102
+ constructor(provider: Web3Provider, defaultChainId?: number) {
103
+ super()
104
+ this.provider = provider
105
+ this.defaultChainId = defaultChainId
106
+ }
107
+
108
+ // memoized
109
+ _address: string
110
+ _index: number
111
+ _context: WalletContext
112
+ _networks: NetworkConfig[]
113
+ private _providers: { [key: number]: Web3Provider } = {}
114
+
115
+ //
116
+ // ethers AbstractSigner methods
117
+ //
118
+
119
+ async getAddress(): Promise<string> {
120
+ if (this._address) return this._address
121
+ const accounts = await this.provider.send('eth_accounts', [])
122
+ this._address = accounts[0]
123
+ this._index = 0
124
+ return ethers.utils.getAddress(this._address)
125
+ }
126
+
127
+ signTransaction(transaction: Deferrable<TransactionRequest>): Promise<string> {
128
+ // TODO .. since ethers isn't using this method, perhaps we will?
129
+ throw new Error('signTransaction is unsupported, use signTransactions instead')
130
+ }
131
+
132
+ connect(provider: ethers.providers.Provider): ethers.providers.JsonRpcSigner {
133
+ throw new Error('unsupported: cannot alter JSON-RPC Signer connection')
134
+ }
135
+
136
+ //
137
+ // Sequence Signer methods
138
+ //
139
+
140
+ // getProvider returns a Web3Provider instance for the current chain. Note that this method
141
+ // and signer is bound to a particular chain to prevent misuse. If you'd like a provider
142
+ // for a specific chain, try getSender(chainId), or wallet.getProvider(chainId).
143
+ async getProvider(chainId?: number): Promise<Web3Provider | undefined> {
144
+ if (chainId) {
145
+ const currentChainId = await this.getChainId()
146
+ if (currentChainId !== chainId) {
147
+ throw new Error(`signer is attempting to access chain ${chainId}, but is already bound to chain ${currentChainId}`)
148
+ }
149
+ }
150
+ return this.provider
151
+ }
152
+
153
+ // getSender returns a Web3Provider instance via the signer transport. Note: for our case
154
+ // the of sequence wallet, this will bring up the wallet window whenever using it, as the json-rpc
155
+ // requests are sent to the window transport. Therefore, for anything non-signing related
156
+ // you can write a higher-order JsonRpcRouter sender to route to the public provider endpoints
157
+ // as we do in the WalletProvider.
158
+ //
159
+ // This method is primarily utilized internally when routing requests to a particular chainId.
160
+ async getSender(chainId?: number): Promise<Web3Provider | undefined> {
161
+ if (!chainId || (chainId && chainId === this.defaultChainId)) {
162
+ return this.provider
163
+ }
164
+ if (!this._providers[chainId]) {
165
+ this._providers[chainId] = new Web3Provider(new JsonRpcSender(this.provider, chainId), chainId)
166
+ }
167
+ return this._providers[chainId]
168
+ }
169
+
170
+ getRelayer(chainId?: number): Promise<Relayer | undefined> {
171
+ // TODO: JsonRpcRelayer ......? or, Web3Relayer.. or SequenceRelayer?
172
+ // sequence_estimateGasLimits
173
+ // sequence_gasRefundOptions
174
+ // sequence_getNonce
175
+ // sequence_relay
176
+ throw new Error('TODO')
177
+ }
178
+
179
+ async getWalletContext(): Promise<WalletContext> {
180
+ if (!this._context) {
181
+ this._context = await this.provider.send('sequence_getWalletContext', [])
182
+ }
183
+ return this._context
184
+ }
185
+
186
+ async getWalletConfig(chainId?: ChainIdLike): Promise<WalletConfig[]> {
187
+ return await this.provider.send(
188
+ 'sequence_getWalletConfig',
189
+ [maybeChainId(chainId)],
190
+ maybeChainId(chainId) || this.defaultChainId
191
+ )
192
+ }
193
+
194
+ async getWalletState(chainId?: ChainIdLike): Promise<WalletState[]> {
195
+ return await this.provider.send(
196
+ 'sequence_getWalletState',
197
+ [maybeChainId(chainId)],
198
+ maybeChainId(chainId) || this.defaultChainId
199
+ )
200
+ }
201
+
202
+ async getNetworks(): Promise<NetworkConfig[]> {
203
+ if (!this._networks) {
204
+ this._networks = await this.provider.send('sequence_getNetworks', [])
205
+ }
206
+ return this._networks
207
+ }
208
+
209
+ async getSigners(): Promise<string[]> {
210
+ const networks = await this.getNetworks()
211
+
212
+ const authChainId = networks.find(n => n.isAuthChain)
213
+ if (!authChainId) {
214
+ throw new Error('authChainId could not be determined from network list')
215
+ }
216
+
217
+ const walletConfig = await this.getWalletConfig(authChainId)
218
+ if (!walletConfig || walletConfig.length === 0) {
219
+ throw new Error(`walletConfig returned zero results for authChainId {authChainId}`)
220
+ }
221
+
222
+ return walletConfig[0].signers.map(s => s.address)
223
+ }
224
+
225
+ // signMessage matches implementation from ethers JsonRpcSigner for compatibility, but with
226
+ // multi-chain support.
227
+ async signMessage(message: BytesLike, chainId?: ChainIdLike, allSigners?: boolean): Promise<string> {
228
+ const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId)
229
+
230
+ const data = typeof message === 'string' ? ethers.utils.toUtf8Bytes(message) : message
231
+ const address = await this.getAddress()
232
+
233
+ // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
234
+ return await provider!.send('eth_sign', [address, ethers.utils.hexlify(data)])
235
+ }
236
+
237
+ // signTypedData matches implementation from ethers JsonRpcSigner for compatibility, but with
238
+ // multi-chain support.
239
+ async signTypedData(
240
+ domain: TypedDataDomain,
241
+ types: Record<string, Array<TypedDataField>>,
242
+ message: Record<string, any>,
243
+ chainId?: ChainIdLike,
244
+ allSigners?: boolean
245
+ ): Promise<string> {
246
+ // Populate any ENS names (in-place)
247
+ // const populated = await ethers.utils._TypedDataEncoder.resolveNames(domain, types, message, (name: string) => {
248
+ // return this.provider.resolveName(name)
249
+ // })
250
+
251
+ return await this.provider.send(
252
+ 'eth_signTypedData_v4',
253
+ [await this.getAddress(), ethers.utils._TypedDataEncoder.getPayload(domain, types, message)],
254
+ maybeChainId(chainId) || this.defaultChainId
255
+ )
256
+ }
257
+
258
+ // sendTransaction matches implementation from ethers JsonRpcSigner for compatibility, but with
259
+ // multi-chain support.
260
+ async sendTransaction(
261
+ transaction: Deferrable<TransactionRequest>,
262
+ chainId?: ChainIdLike,
263
+ allSigners?: boolean
264
+ ): Promise<TransactionResponse> {
265
+ const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId)
266
+
267
+ const tx = this.sendUncheckedTransaction(transaction, chainId).then(hash => {
268
+ return ethers.utils
269
+ .poll(
270
+ () => {
271
+ return provider!.getTransaction(hash).then((tx: TransactionResponse) => {
272
+ if (tx === null) {
273
+ return undefined
274
+ }
275
+ return provider!._wrapTransaction(tx, hash)
276
+ })
277
+ },
278
+ { onceBlock: this.provider! }
279
+ )
280
+ .catch((error: Error) => {
281
+ ;(<any>error).transactionHash = hash
282
+ throw error
283
+ })
284
+ })
285
+
286
+ // @ts-ignore
287
+ return tx
288
+ }
289
+
290
+ // sendTransactionBatch is a convenience method to call sendTransaction in a batch format, allowing you to
291
+ // send multiple transaction as a single payload and just one on-chain transaction.
292
+ async sendTransactionBatch(
293
+ transactions: Deferrable<Forbid<TransactionRequest, "wait">[]>,
294
+ chainId?: ChainIdLike,
295
+ allSigners?: boolean
296
+ ): Promise<TransactionResponse> {
297
+ const batch = await resolveArrayProperties<Forbid<TransactionRequest, "wait">[]>(transactions)
298
+ if (!batch || batch.length === 0) {
299
+ throw new Error('cannot send empty batch')
300
+ }
301
+
302
+ // sendTransactionBatch only accepts TransactionRequest, not TransactionResponses
303
+ if (batch.find((v) => v.wait !== undefined && v.wait !== null)) {
304
+ throw new Error('transaction request expected for sendTransactionBatch, transaction response found')
305
+ }
306
+
307
+ const tx: TransactionRequest = { ...batch[0] }
308
+ if (batch.length > 1) {
309
+ tx.auxiliary = batch.splice(1)
310
+ }
311
+
312
+ return this.sendTransaction(tx, chainId, allSigners)
313
+ }
314
+
315
+ signTransactions(
316
+ transaction: Deferrable<TransactionRequest>,
317
+ chainId?: ChainIdLike,
318
+ allSigners?: boolean
319
+ ): Promise<SignedTransactions> {
320
+ transaction = shallowCopy(transaction)
321
+ // TODO: transaction argument..? make sure to resolve any properties and serialize property before sending over
322
+ // the wire.. see sendUncheckedTransaction and resolveProperties
323
+ return this.provider.send('eth_signTransaction', [transaction], maybeChainId(chainId) || this.defaultChainId)
324
+ }
325
+
326
+ sendSignedTransactions(signedTxs: SignedTransactions, chainId?: ChainIdLike): Promise<TransactionResponse> {
327
+ // sequence_relay
328
+ throw new Error('TODO')
329
+ }
330
+
331
+ // updateConfig..
332
+ // NOTE: this is not supported by the remote wallet by default.
333
+ async updateConfig(newConfig?: WalletConfig): Promise<[WalletConfig, TransactionResponse | undefined]> {
334
+ // sequence_updateConfig
335
+ const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId)
336
+ if (tx === null) {
337
+ return [config, undefined]
338
+ }
339
+
340
+ const provider = await this.getSender(this.defaultChainId)
341
+ return [config, provider!._wrapTransaction(tx, tx.hash)]
342
+ }
343
+
344
+ // publishConfig..
345
+ // NOTE: this is not supported by the remote wallet by default.
346
+ async publishConfig(): Promise<TransactionResponse | undefined> {
347
+ const provider = await this.getSender(this.defaultChainId)
348
+
349
+ const tx = await provider!.send('sequence_publishConfig', [])
350
+ if (tx === null) {
351
+ return undefined
352
+ }
353
+ return provider!._wrapTransaction(tx, tx.hash)
354
+ }
355
+
356
+ async isDeployed(chainId?: ChainIdLike): Promise<boolean> {
357
+ const provider = await this.getSender(maybeChainId(chainId))
358
+ const walletCode = await provider!.getCode(await this.getAddress())
359
+ return !!walletCode && walletCode !== '0x'
360
+ }
361
+
362
+ //
363
+ // ethers JsonRpcSigner methods
364
+ //
365
+
366
+ async _signTypedData(
367
+ domain: TypedDataDomain,
368
+ types: Record<string, Array<TypedDataField>>,
369
+ message: Record<string, any>,
370
+ chainId?: ChainIdLike,
371
+ allSigners?: boolean
372
+ ): Promise<string> {
373
+ return this.signTypedData(domain, types, message, chainId, allSigners)
374
+ }
375
+
376
+ async sendUncheckedTransaction(transaction: Deferrable<TransactionRequest>, chainId?: ChainIdLike): Promise<string> {
377
+ transaction = shallowCopy(transaction)
378
+
379
+ const fromAddress = this.getAddress()
380
+
381
+ // NOTE: we do not use provider estimation, and instead rely on our relayer to determine the gasLimit and gasPrice
382
+ //
383
+ // TODO: alternatively/one day, we could write a provider middleware to eth_estimateGas
384
+ // and send it to our relayer url instead for estimation..
385
+ //
386
+ // if (!transaction.gasLimit) {
387
+ // const estimate = shallowCopy(transaction)
388
+ // estimate.from = fromAddress
389
+ // transaction.gasLimit = this.provider.estimateGas(estimate)
390
+ // }
391
+
392
+ const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId)
393
+
394
+ return resolveProperties({
395
+ tx: resolveProperties(transaction),
396
+ sender: await fromAddress
397
+ }).then(({ tx, sender }) => {
398
+ if (tx.from != null) {
399
+ if (ethers.utils.getAddress(tx.from) !== sender) {
400
+ // logger.throwArgumentError("from address mismatch", "transaction", transaction)
401
+ throw new Error(`from address mismatch for transaction ${transaction}`)
402
+ }
403
+ } else {
404
+ tx.from = sender
405
+ }
406
+
407
+ const hexTx = hexlifyTransaction(tx)
408
+
409
+ return provider!.send('eth_sendTransaction', [hexTx]).then(
410
+ hash => {
411
+ return hash
412
+ },
413
+ error => {
414
+ // return checkError("sendTransaction", error, hexTx)
415
+ throw new Error(`sendTransaction ${error}`)
416
+ }
417
+ )
418
+ })
419
+ }
420
+
421
+ connectUnchecked(): ethers.providers.JsonRpcSigner {
422
+ throw new Error('connectUnchecked is unsupported')
423
+ }
424
+
425
+ async unlock(password: string): Promise<boolean> {
426
+ const address = await this.getAddress()
427
+ return this.provider.send('personal_unlockAccount', [address, password, null])
428
+ }
429
+ }
430
+
431
+ // NOTE: method has been copied + modified from ethers.js JsonRpcProvider
432
+ // Convert an ethers.js transaction into a JSON-RPC transaction
433
+
434
+ const allowedTransactionKeys: { [key: string]: boolean } = {
435
+ chainId: true,
436
+ data: true,
437
+ gasLimit: true,
438
+ gasPrice: true,
439
+ nonce: true,
440
+ to: true,
441
+ value: true,
442
+ from: true,
443
+ auxiliary: true,
444
+ expiration: true,
445
+ afterNonce: true,
446
+ delegateCall: true,
447
+ revertOnError: true
448
+ }
449
+
450
+ const hexlifyTransaction = (
451
+ transaction: TransactionRequest,
452
+ allowExtra?: { [key: string]: boolean }
453
+ ): { [key: string]: string } => {
454
+ // Check only allowed properties are given
455
+ const allowed = shallowCopy(allowedTransactionKeys)
456
+ if (allowExtra) {
457
+ for (const key in allowExtra) {
458
+ if (allowExtra[key]) {
459
+ allowed[key] = true
460
+ }
461
+ }
462
+ }
463
+ ethers.utils.checkProperties(transaction, allowed)
464
+
465
+ const result: { [key: string]: any } = {}
466
+
467
+ // Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
468
+ ;['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(key => {
469
+ if (!(<any>transaction)[key]) {
470
+ return
471
+ }
472
+ const value = ethers.utils.hexValue((<any>transaction)[key])
473
+ if (key === 'gasLimit') {
474
+ key = 'gas'
475
+ }
476
+ result[key] = value
477
+ })
478
+ ;['from', 'to', 'data'].forEach(key => {
479
+ if (!(<any>transaction)[key]) {
480
+ return
481
+ }
482
+ result[key] = ethers.utils.hexlify((<any>transaction)[key])
483
+ })
484
+
485
+ const auxiliary = <any>transaction['auxiliary']
486
+ if (auxiliary && auxiliary.length > 0) {
487
+ result['auxiliary'] = []
488
+ auxiliary.forEach((a: any) => {
489
+ result['auxiliary'].push(hexlifyTransaction(a))
490
+ })
491
+ }
492
+
493
+ return result
494
+ }