@0xsequence/provider 1.0.4 → 1.1.0

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 (53) hide show
  1. package/dist/0xsequence-provider.cjs.d.ts +1 -0
  2. package/dist/0xsequence-provider.cjs.d.ts.map +1 -0
  3. package/dist/0xsequence-provider.cjs.dev.js +1742 -1678
  4. package/dist/0xsequence-provider.cjs.prod.js +1742 -1678
  5. package/dist/0xsequence-provider.esm.js +1735 -1676
  6. package/dist/declarations/src/client.d.ts +74 -0
  7. package/dist/declarations/src/index.d.ts +7 -5
  8. package/dist/declarations/src/init.d.ts +25 -0
  9. package/dist/declarations/src/provider.d.ts +127 -63
  10. package/dist/declarations/src/signer.d.ts +87 -0
  11. package/dist/declarations/src/transports/base-provider-transport.d.ts +1 -1
  12. package/dist/declarations/src/transports/base-wallet-transport.d.ts +2 -2
  13. package/dist/declarations/src/transports/extension-transport/base-injected-transport.d.ts +1 -1
  14. package/dist/declarations/src/transports/extension-transport/extension-message-handler.d.ts +3 -3
  15. package/dist/declarations/src/transports/extension-transport/extension-message-provider.d.ts +2 -2
  16. package/dist/declarations/src/transports/extension-transport/index.d.ts +3 -3
  17. package/dist/declarations/src/transports/index.d.ts +8 -8
  18. package/dist/declarations/src/transports/mux-transport/index.d.ts +1 -1
  19. package/dist/declarations/src/transports/mux-transport/mux-message-provider.d.ts +22 -1
  20. package/dist/declarations/src/transports/proxy-transport/index.d.ts +3 -3
  21. package/dist/declarations/src/transports/proxy-transport/proxy-message-channel.d.ts +1 -1
  22. package/dist/declarations/src/transports/proxy-transport/proxy-message-handler.d.ts +4 -4
  23. package/dist/declarations/src/transports/proxy-transport/proxy-message-provider.d.ts +3 -3
  24. package/dist/declarations/src/transports/unreal-transport/index.d.ts +2 -2
  25. package/dist/declarations/src/transports/unreal-transport/unreal-message-handler.d.ts +3 -3
  26. package/dist/declarations/src/transports/unreal-transport/unreal-message-provider.d.ts +2 -2
  27. package/dist/declarations/src/transports/wallet-request-handler.d.ts +1 -2
  28. package/dist/declarations/src/transports/window-transport/index.d.ts +2 -2
  29. package/dist/declarations/src/transports/window-transport/window-message-handler.d.ts +3 -3
  30. package/dist/declarations/src/transports/window-transport/window-message-provider.d.ts +2 -2
  31. package/dist/declarations/src/types.d.ts +25 -9
  32. package/dist/declarations/src/utils/index.d.ts +10 -7
  33. package/dist/declarations/src/utils.d.ts +21 -15
  34. package/package.json +19 -12
  35. package/src/client.ts +423 -0
  36. package/src/extended.ts +4 -0
  37. package/src/index.ts +3 -1
  38. package/src/init.ts +134 -0
  39. package/src/provider.ts +447 -424
  40. package/src/signer.ts +316 -0
  41. package/src/transports/base-wallet-transport.ts +3 -8
  42. package/src/transports/mux-transport/mux-message-provider.ts +86 -2
  43. package/src/transports/proxy-transport/proxy-message-provider.ts +1 -1
  44. package/src/transports/wallet-request-handler.ts +3 -33
  45. package/src/transports/window-transport/window-message-provider.ts +4 -2
  46. package/src/types.ts +41 -10
  47. package/src/utils/index.ts +16 -76
  48. package/src/utils.ts +81 -51
  49. package/dist/declarations/src/eip191exceptions.d.ts +0 -3
  50. package/dist/declarations/src/extended.d.ts +0 -6
  51. package/dist/declarations/src/transports/unreal-transport/overridelogs.d.ts +0 -4
  52. package/dist/declarations/src/wallet.d.ts +0 -105
  53. package/src/wallet.ts +0 -770
package/src/provider.ts CHANGED
@@ -1,531 +1,554 @@
1
- import { ethers, BytesLike, Bytes, providers, TypedDataDomain, TypedDataField } from 'ethers'
2
1
 
3
- import {
4
- NetworkConfig,
5
- ChainIdLike,
6
- JsonRpcHandler,
7
- JsonRpcFetchFunc,
8
- JsonRpcRequest,
9
- JsonRpcResponseCallback,
10
- JsonRpcSender,
11
- maybeChainId
12
- } from '@0xsequence/network'
2
+ import { ethers } from "ethers"
3
+ import { SequenceClient } from "./client"
4
+ import { ChainIdLike, NetworkConfig, allNetworks, findNetworkConfig } from "@0xsequence/network"
5
+ import { ConnectDetails, ConnectOptions, EIP1193Provider, OpenWalletIntent, OptionalChainIdLike, WalletSession } from "./types"
6
+ import { commons } from "@0xsequence/core"
7
+ import { WalletUtils } from "./utils/index"
8
+ import { SequenceSigner, SingleNetworkSequenceSigner } from "./signer"
13
9
 
14
- import { resolveArrayProperties, Signer } from '@0xsequence/wallet'
15
- import { Relayer } from '@0xsequence/relayer'
16
- import { Deferrable, shallowCopy, resolveProperties, Forbid } from '@0xsequence/utils'
17
- import { WalletRequestHandler } from './transports/wallet-request-handler'
18
- import { commons, universal } from '@0xsequence/core'
19
- import { Account, AccountStatus } from '@0xsequence/account'
20
- import { ExtendedTransactionRequest, toExtended } from './extended'
10
+ export interface ISequenceProvider {
11
+ readonly _isSequenceProvider: true
21
12
 
22
- export class Web3Provider extends providers.Web3Provider implements JsonRpcHandler {
23
- static isSequenceProvider(cand: any): cand is Web3Provider {
24
- return isSequenceProvider(cand)
25
- }
13
+ connect(options?: ConnectOptions): Promise<ConnectDetails>
14
+ disconnect(): void
26
15
 
27
- readonly _sender: JsonRpcSender
16
+ isConnected(): boolean
17
+ getSession(): WalletSession | undefined
28
18
 
29
- readonly _isSequenceProvider: boolean
19
+ listAccounts(): string[]
30
20
 
31
- // defaultChainId is the default chainId to use with requests, but may be
32
- // overridden by passing chainId argument to a specific request
33
- readonly _defaultChainId?: number
21
+ // @deprecated use getSigner().getAddress() instead
22
+ getAddress(): string
34
23
 
35
- constructor(provider: providers.JsonRpcProvider | JsonRpcHandler | JsonRpcFetchFunc, defaultChainId?: ChainIdLike) {
36
- const sender = new JsonRpcSender(provider, maybeChainId(defaultChainId))
37
- provider = sender
24
+ getNetworks(): Promise<NetworkConfig[]>
25
+ getChainId(): number
38
26
 
39
- super(provider, 'any')
27
+ setDefaultChainId(chainId: ChainIdLike): void
40
28
 
41
- this._sender = sender
42
- this._isSequenceProvider = true
43
- this._defaultChainId = maybeChainId(defaultChainId)
44
- }
29
+ isOpened(): boolean
30
+ openWallet(path?: string, intent?: OpenWalletIntent): Promise<boolean>
31
+ closeWallet(): void
32
+
33
+ getProvider(): SequenceProvider
34
+ getProvider(chainId: ChainIdLike): SingleNetworkSequenceProvider
35
+ getProvider(chainId?: ChainIdLike): SequenceProvider | SingleNetworkSequenceProvider
36
+
37
+ getSigner(): SequenceSigner
38
+ getSigner(chainId: ChainIdLike): SingleNetworkSequenceSigner
39
+ getSigner(chainId?: ChainIdLike): SequenceSigner | SingleNetworkSequenceSigner
40
+
41
+ // @deprecated use getSigner().getWalletContext() instead
42
+ getWalletContext(): Promise<commons.context.VersionedContext>
43
+
44
+ // @deprecated use getSigner().getWalletConfig() instead
45
+ getWalletConfig(chainId?: ChainIdLike): Promise<commons.config.Config>
46
+
47
+ utils: WalletUtils
48
+ }
45
49
 
46
- sendAsync(
47
- request: JsonRpcRequest,
48
- callback: JsonRpcResponseCallback | ((error: any, response: any) => void),
49
- chainId?: number
50
+ export class SequenceProvider extends ethers.providers.BaseProvider implements ISequenceProvider, EIP1193Provider {
51
+ private readonly singleNetworkProviders: { [chainId: number]: SingleNetworkSequenceProvider } = {}
52
+
53
+ readonly _isSequenceProvider = true
54
+ readonly utils: WalletUtils
55
+
56
+ readonly signer: SequenceSigner
57
+
58
+ constructor (
59
+ public readonly client: SequenceClient,
60
+ private readonly providerFor: (networkId: number) => ethers.providers.JsonRpcProvider,
61
+ public readonly networks: NetworkConfig[] = allNetworks
50
62
  ) {
51
- this._sender.sendAsync(request, callback, chainId)
63
+ // We support a lot of networks
64
+ // but we start with the default one
65
+ super(client.getChainId())
66
+
67
+ // Emit events as defined by EIP-1193
68
+ client.onConnect((details) => {
69
+ this.emit('connect', details)
70
+ })
71
+
72
+ client.onDisconnect((error) => {
73
+ this.emit('disconnect', error)
74
+ })
75
+
76
+ client.onDefaultChainIdChanged((chainId) => {
77
+ this.emit('chainChanged', chainId)
78
+ })
79
+
80
+ client.onAccountsChanged((accounts) => {
81
+ this.emit('accountsChanged', accounts)
82
+ })
83
+
84
+ // NOTICE: We don't emit 'open' and 'close' events
85
+ // because these are handled by the library, and they
86
+ // are not part of EIP-1193
87
+
88
+ // devs can still access them using
89
+ // client.onOpen()
90
+ // client.onClose()
91
+
92
+ // Create a Sequence signer too
93
+ this.signer = new SequenceSigner(this.client, this)
94
+
95
+ // Create a utils instance
96
+ this.utils = new WalletUtils(this.signer)
52
97
  }
53
98
 
54
- send(method: string, params: Array<any>, chainId?: number): Promise<any> {
55
- return this._sender.send(method, params, chainId)
99
+ getSigner(): SequenceSigner
100
+ getSigner(chainId: ChainIdLike): SingleNetworkSequenceSigner
101
+ getSigner(chainId?: ChainIdLike): SequenceSigner | SingleNetworkSequenceSigner
102
+
103
+ getSigner(chainId?: ChainIdLike) {
104
+ return this.signer.getSigner(chainId)
56
105
  }
57
106
 
58
- request(request: { method: string; params?: Array<any>; chainId?: number }): Promise<any> {
59
- return this.send(request.method, request.params || [], request.chainId)
107
+ connect(options: ConnectOptions) {
108
+ return this.client.connect(options)
60
109
  }
61
110
 
62
- getSigner(): Web3Signer {
63
- return new Web3Signer(this, this._defaultChainId)
111
+ disconnect() {
112
+ return this.client.disconnect()
64
113
  }
65
114
 
66
- async getChainId(): Promise<number> {
67
- // If we already have a default chainId, then we can just return it
68
- const defaultChainId = this._defaultChainId
69
- if (defaultChainId) return defaultChainId
115
+ isConnected() {
116
+ return this.client.isConnected()
117
+ }
70
118
 
71
- // If there is no default chain, then we can just return the chainId of the provider
72
- return this.send('eth_chainId', [])
119
+ getSession() {
120
+ return this.client.getSession()
73
121
  }
74
122
 
75
- getNetworks(): Promise<NetworkConfig[]> {
76
- return this.send('sequence_getNetworks', [])
123
+ listAccounts(): string[] {
124
+ return [this.client.getAddress()]
77
125
  }
78
- }
79
126
 
80
- export function isSequenceProvider(provider: any): provider is Web3Provider {
81
- const cand = provider as Web3Provider
82
- return cand && cand.send !== undefined && cand._isSequenceProvider === true
83
- }
127
+ // @deprecated use getSigner() instead
128
+ getAddress() {
129
+ return this.client.getAddress()
130
+ }
84
131
 
85
- export class LocalWeb3Provider extends Web3Provider {
86
- constructor(account: Account, networks?: NetworkConfig[]) {
87
- const walletRequestHandler = new WalletRequestHandler(account, null, networks || [])
88
- super(walletRequestHandler)
132
+ getNetworks(): Promise<NetworkConfig[]> {
133
+ return this.client.getNetworks()
89
134
  }
90
- }
91
135
 
92
- // TODO: in the future with ethers v6 we can remove/change this type name
93
- interface TypedDataSigner {
94
- _signTypedData(
95
- domain: TypedDataDomain,
96
- types: Record<string, Array<TypedDataField>>,
97
- value: Record<string, any>
98
- ): Promise<string>
99
- }
136
+ getChainId(): number {
137
+ return this.client.getChainId()
138
+ }
100
139
 
101
- export class Web3Signer extends Signer implements TypedDataSigner {
102
- readonly provider: Web3Provider
103
- readonly defaultChainId?: number
140
+ setDefaultChainId(chainId: ChainIdLike) {
141
+ return this.client.setDefaultChainId(this.toChainId(chainId))
142
+ }
104
143
 
105
- constructor(provider: Web3Provider, defaultChainId?: number) {
106
- super()
107
- this.provider = provider
108
- this.defaultChainId = defaultChainId
144
+ isOpened(): boolean {
145
+ return this.client.isOpened()
109
146
  }
110
147
 
111
- // memoized
112
- _address: string
113
- _index: number
114
- _context: commons.context.VersionedContext
115
- _networks: NetworkConfig[]
116
- private _providers: { [key: number]: Web3Provider } = {}
148
+ closeWallet(): void {
149
+ return this.client.closeWallet()
150
+ }
117
151
 
118
- //
119
- // ethers AbstractSigner methods
120
- //
152
+ getWalletContext(): Promise<commons.context.VersionedContext> {
153
+ return this.client.getWalletContext()
154
+ }
121
155
 
122
- async getAddress(): Promise<string> {
123
- if (this._address) return this._address
124
- const accounts = await this.provider.send('eth_accounts', [])
125
- this._address = accounts[0]
126
- this._index = 0
127
- return ethers.utils.getAddress(this._address)
156
+ // @deprecated use getSigner() instead
157
+ async getWalletConfig(chainId?: ChainIdLike): Promise<commons.config.Config> {
158
+ const useChainId = await this.useChainId(chainId)
159
+ return this.client.getOnchainWalletConfig({ chainId: useChainId })
128
160
  }
129
161
 
130
- signTransaction(transaction: Deferrable<ethers.providers.TransactionRequest>): Promise<string> {
131
- // TODO .. since ethers isn't using this method, perhaps we will?
132
- throw new Error('signTransaction is unsupported, use signTransactions instead')
162
+ authorize(options: ConnectOptions) {
163
+ // Just an alias for connect with authorize: true
164
+ return this.client.connect({ ...options, authorize: true })
133
165
  }
134
166
 
135
- connect(provider: ethers.providers.Provider): ethers.providers.JsonRpcSigner {
136
- throw new Error('unsupported: cannot alter JSON-RPC Signer connection')
167
+ async openWallet(
168
+ path?: string,
169
+ intent?: OpenWalletIntent
170
+ ) {
171
+ await this.client.openWallet(path, intent)
172
+ return true
137
173
  }
138
174
 
139
- //
140
- // Sequence Signer methods
141
- //
175
+ toChainId(chainId: ChainIdLike): number
176
+ toChainId(chainId?: ChainIdLike): number | undefined
142
177
 
143
- // getProvider returns a Web3Provider instance for the current chain. Note that this method
144
- // and signer is bound to a particular chain to prevent misuse. If you'd like a provider
145
- // for a specific chain, try getSender(chainId), or wallet.getProvider(chainId).
146
- async getProvider(chainId?: number): Promise<Web3Provider | undefined> {
147
- if (chainId) {
148
- const currentChainId = await this.getChainId()
149
- if (currentChainId !== chainId) {
150
- throw new Error(`signer is attempting to access chain ${chainId}, but is already bound to chain ${currentChainId}`)
151
- }
178
+ toChainId(chainId?: ChainIdLike) {
179
+ if (chainId === undefined) {
180
+ return undefined
152
181
  }
153
- return this.provider
154
- }
155
-
156
- // getSender returns a Web3Provider instance via the signer transport. Note: for our case
157
- // the of sequence wallet, this will bring up the wallet window whenever using it, as the json-rpc
158
- // requests are sent to the window transport. Therefore, for anything non-signing related
159
- // you can write a higher-order JsonRpcRouter sender to route to the public provider endpoints
160
- // as we do in the WalletProvider.
161
- //
162
- // This method is primarily utilized internally when routing requests to a particular chainId.
163
- async getSender(chainId?: number): Promise<Web3Provider | undefined> {
164
- if (!chainId || (chainId && chainId === this.defaultChainId)) {
165
- return this.provider
182
+
183
+ const resolved = findNetworkConfig(this.networks, chainId as ChainIdLike)
184
+
185
+ if (!resolved) {
186
+ throw new Error(`Unsupported network ${chainId}`)
166
187
  }
167
- if (!this._providers[chainId]) {
168
- this._providers[chainId] = new Web3Provider(new JsonRpcSender(this.provider, chainId), chainId)
188
+
189
+ return resolved.chainId
190
+ }
191
+
192
+ /**
193
+ * Resolves the chainId to use for the given request. If no chainId is provided,
194
+ * it uses the chainId defined by the client (default chainId). This can be
195
+ * overriden to build a single-network SequenceProvider.
196
+ */
197
+ protected async useChainId(chainId?: ChainIdLike): Promise<number> {
198
+ return this.toChainId(chainId) || this.client.getChainId()
199
+ }
200
+
201
+ /**
202
+ * This generates a provider that ONLY works for the given chainId.
203
+ * the generated provider can't switch networks, and can't handle requests
204
+ * for other networks.
205
+ */
206
+ getProvider(): SequenceProvider
207
+ getProvider(chainId: ChainIdLike): SingleNetworkSequenceProvider
208
+ getProvider(chainId?: ChainIdLike): SequenceProvider | SingleNetworkSequenceProvider
209
+
210
+ getProvider(chainId?: ChainIdLike) {
211
+ // The provider without a chainId is... this one
212
+ if (!chainId) {
213
+ return this
169
214
  }
170
- return this._providers[chainId]
171
- }
172
215
 
173
- getRelayer(chainId?: number): Promise<Relayer | undefined> {
174
- // TODO: JsonRpcRelayer ......? or, Web3Relayer.. or SequenceRelayer?
175
- // sequence_gasRefundOptions
176
- // sequence_getNonce
177
- // sequence_relay
178
- throw new Error('TODO')
216
+ const useChainId = this.toChainId(chainId)
217
+
218
+ if (!this.singleNetworkProviders[useChainId]) {
219
+ this.singleNetworkProviders[useChainId] = new SingleNetworkSequenceProvider(
220
+ this.client,
221
+ this.providerFor,
222
+ useChainId
223
+ )
224
+ }
225
+
226
+ return this.singleNetworkProviders[useChainId]
179
227
  }
180
228
 
181
- async getWalletContext(): Promise<commons.context.VersionedContext> {
182
- if (!this._context) {
183
- this._context = await this.provider.send('sequence_getWalletContext', [])
229
+ /**
230
+ * This returns a subprovider, this is a regular non-sequence provider that
231
+ * can be used to fulfill read only requests on a given network.
232
+ */
233
+ async _getSubprovider(chainId?: ChainIdLike): Promise<ethers.providers.JsonRpcProvider> {
234
+ const useChainId = await this.useChainId(chainId)
235
+
236
+ // Whoever implements providerFrom should memoize the generated provider
237
+ // otherwise every instance of SequenceProvider will create a new subprovider
238
+ const provider = this.providerFor(useChainId)
239
+
240
+ if (!provider) {
241
+ throw new Error(`Unsupported network ${useChainId}`)
184
242
  }
185
- return this._context
243
+
244
+ return provider
186
245
  }
187
246
 
188
- async getWalletConfig(chainId?: ChainIdLike): Promise<commons.config.Config> {
189
- const reqChainId = maybeChainId(chainId) || this.defaultChainId
190
- if (!reqChainId) throw new Error('chainId is required')
191
- return (await this.provider.send(
192
- 'sequence_getWalletConfig',
193
- [reqChainId],
194
- reqChainId
195
- ))[0]
247
+ async perform(method: string, params: any): Promise<any> {
248
+ // First we check if the method should be handled by the client
249
+ if (method === 'eth_chainId') {
250
+ return this.useChainId()
251
+ }
252
+
253
+ if (method === 'eth_accounts') {
254
+ return [this.client.getAddress()]
255
+ }
256
+
257
+ if (method === 'wallet_switchEthereumChain') {
258
+ const args = params[0] as { chainId: string } | number | string
259
+ const chainId = normalizeChainId(args)
260
+ return this.setDefaultChainId(chainId)
261
+ }
262
+
263
+ // Usually these methods aren't used by calling the provider
264
+ // but to maximize compatibility we support them too.
265
+ // The correct way of accessing these methods is by using .getSigner()
266
+ if (
267
+ method === 'eth_sendTransaction' ||
268
+ method === 'eth_sign' ||
269
+ method === 'eth_signTypedData' ||
270
+ method === 'eth_signTypedData_v4' ||
271
+ method === 'personal_sign' ||
272
+ // These methods will use EIP-6492
273
+ // but this is handled directly by the wallet
274
+ method === 'sequence_sign' ||
275
+ method === 'sequence_signTypedData_v4'
276
+ ) {
277
+ // We pass the chainId to the client, if we don't pass one
278
+ // the client will use its own default chainId
279
+ return this.client.send({ method, params }, this.getChainId())
280
+ }
281
+
282
+ // Forward call to the corresponding provider
283
+ // we use the provided chainId, or the default one provided by the client
284
+ const provider = await this._getSubprovider()
285
+ const prepared = provider.prepareRequest(method, params) ?? [method, params]
286
+ return provider.send(prepared[0], prepared[1])
196
287
  }
197
288
 
198
- async getWalletState(chainId?: ChainIdLike): Promise<AccountStatus> {
199
- const reqChainId = maybeChainId(chainId) || this.defaultChainId
200
- if (!reqChainId) throw new Error('chainId is required')
201
- return (await this.provider.send(
202
- 'sequence_getWalletState',
203
- [reqChainId],
204
- reqChainId
205
- ))[0].status
289
+ send (method: string, params: any): Promise<any> {
290
+ return this.perform(method, params)
206
291
  }
207
292
 
208
- async getNetworks(): Promise<NetworkConfig[]> {
209
- if (!this._networks) this._networks = await this.provider.getNetworks()
210
- return this._networks
293
+ request (request: { method: string; params?: any[] | undefined }) {
294
+ return this.perform(request.method, request.params)
211
295
  }
212
296
 
213
- async getSigners(): Promise<string[]> {
214
- const networks = await this.getNetworks()
297
+ async detectNetwork(): Promise<ethers.providers.Network> {
298
+ const chainId = this.client.getChainId()
299
+ const network = findNetworkConfig(this.networks, chainId)
215
300
 
216
- // TODO: Replace this with a method that aggregates signer addresses from all chains
217
- const config = await this.getWalletConfig(networks[0].chainId)
218
- if (!config) {
219
- throw new Error(`walletConfig returned zero results for authChainId {authChainId}`)
301
+ if (!network) {
302
+ throw new Error(`Unknown network ${chainId}`)
220
303
  }
221
304
 
222
- return universal.genericCoderFor(config.version).config.signersOf(config).map((s) => s.address)
305
+ return network
223
306
  }
224
307
 
225
- // signMessage matches implementation from ethers JsonRpcSigner for compatibility, but with
226
- // multi-chain support.
227
- async signMessage(message: BytesLike, chainId?: ChainIdLike, sequenceVerified: boolean = true): Promise<string> {
228
- const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId)
308
+ // Override most of the methods, so we add support for an optional chainId
309
+ // argument, which is used to select the provider to use.
310
+ //
311
+ // NOTICE: We could use generics to avoid repeating the same code
312
+ // but this would make the code harder to read, and it's not worth it
313
+ // since we only have a few methods to override.
314
+
315
+ async waitForTransaction(
316
+ transactionHash: string,
317
+ confirmations?: number,
318
+ timeout?: number,
319
+ optionals?: OptionalChainIdLike
320
+ ) {
321
+ const provider = await this._getSubprovider(optionals?.chainId)
322
+ return provider.waitForTransaction(transactionHash, confirmations, timeout)
323
+ }
229
324
 
230
- const data = typeof message === 'string' ? ethers.utils.toUtf8Bytes(message) : message
231
- const address = await this.getAddress()
325
+ async getBlockNumber(optionals?: OptionalChainIdLike) {
326
+ const provider = await this._getSubprovider(optionals?.chainId)
327
+ return provider.getBlockNumber()
328
+ }
232
329
 
233
- // NOTE: as of ethers v5.5, it switched to using personal_sign, see
234
- // https://github.com/ethers-io/ethers.js/pull/1542 and see
235
- // https://github.com/WalletConnect/walletconnect-docs/issues/32 for additional info.
236
- return provider!.send(
237
- sequenceVerified ? 'sequence_sign' : 'personal_sign',
238
- [ethers.utils.hexlify(data), address]
239
- )
330
+ async getGasPrice(optionals?: OptionalChainIdLike) {
331
+ const provider = await this._getSubprovider(optionals?.chainId)
332
+ return provider.getGasPrice()
240
333
  }
241
334
 
242
- // signTypedData matches implementation from ethers JsonRpcSigner for compatibility, but with
243
- // multi-chain support.
244
- async signTypedData(
245
- domain: TypedDataDomain,
246
- types: Record<string, Array<TypedDataField>>,
247
- message: Record<string, any>,
248
- chainId?: ChainIdLike,
249
- sequenceVerified: boolean = true
250
- ): Promise<string> {
251
- // Populate any ENS names (in-place)
252
- // const populated = await ethers.utils._TypedDataEncoder.resolveNames(domain, types, message, (name: string) => {
253
- // return this.provider.resolveName(name)
254
- // })
255
-
256
- return this.provider.send(
257
- sequenceVerified ? 'sequence_signTypedData_v4' : 'eth_signTypedData_v4',
258
- [await this.getAddress(), ethers.utils._TypedDataEncoder.getPayload(domain, types, message)],
259
- maybeChainId(chainId) || this.defaultChainId
260
- )
335
+ async getBalance(
336
+ addressOrName: string | Promise<string>,
337
+ blockTag?: ethers.providers.BlockTag | Promise<ethers.providers.BlockTag>,
338
+ optionals?: OptionalChainIdLike
339
+ ) {
340
+ const provider = await this._getSubprovider(optionals?.chainId)
341
+ return provider.getBalance(addressOrName, blockTag)
261
342
  }
262
343
 
263
- // sendTransaction matches implementation from ethers JsonRpcSigner for compatibility, but with
264
- // multi-chain support.
265
- async sendTransaction(
266
- transaction: Deferrable<ExtendedTransactionRequest>,
267
- chainId?: ChainIdLike
268
- ): Promise<commons.transaction.TransactionResponse> {
269
- const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId)
270
-
271
- const tx = this.sendUncheckedTransaction(transaction, chainId).then(hash => {
272
- return ethers.utils
273
- .poll(
274
- () => {
275
- return provider!.getTransaction(hash).then((tx: ethers.providers.TransactionResponse) => {
276
- if (tx === null) {
277
- return undefined
278
- }
279
- return provider!._wrapTransaction(tx, hash)
280
- })
281
- },
282
- { onceBlock: this.provider! }
283
- )
284
- .catch((error: Error) => {
285
- ;(<any>error).transactionHash = hash
286
- throw error
287
- })
288
- })
344
+ async getTransactionCount(
345
+ addressOrName: string | Promise<string>,
346
+ blockTag?: ethers.providers.BlockTag | Promise<ethers.providers.BlockTag>,
347
+ optionals?: OptionalChainIdLike
348
+ ) {
349
+ const provider = await this._getSubprovider(optionals?.chainId)
350
+ return provider.getTransactionCount(addressOrName, blockTag)
351
+ }
289
352
 
290
- // @ts-ignore
291
- return tx
353
+ async getCode(
354
+ addressOrName: string | Promise<string>,
355
+ blockTag?: ethers.providers.BlockTag | Promise<ethers.providers.BlockTag>,
356
+ optionals?: OptionalChainIdLike
357
+ ) {
358
+ const provider = await this._getSubprovider(optionals?.chainId)
359
+ return provider.getCode(addressOrName, blockTag)
292
360
  }
293
361
 
294
- // sendTransactionBatch is a convenience method to call sendTransaction in a batch format, allowing you to
295
- // send multiple transaction as a single payload and just one on-chain transaction.
296
- async sendTransactionBatch(
297
- transactions: Deferrable<ethers.providers.TransactionRequest[]>,
298
- chainId?: ChainIdLike
299
- ): Promise<ethers.providers.TransactionResponse> {
300
- const batch = await resolveArrayProperties<Forbid<ethers.providers.TransactionRequest, 'wait'>[]>(transactions)
301
- if (!batch || batch.length === 0) {
302
- throw new Error('cannot send empty batch')
303
- }
362
+ async getStorageAt(
363
+ addressOrName: string | Promise<string>,
364
+ position: ethers.BigNumberish | Promise<ethers.BigNumberish>,
365
+ blockTag?: ethers.providers.BlockTag | Promise<ethers.providers.BlockTag>,
366
+ optionals?: OptionalChainIdLike
367
+ ) {
368
+ const provider = await this._getSubprovider(optionals?.chainId)
369
+ return provider.getStorageAt(addressOrName, position, blockTag)
370
+ }
304
371
 
305
- // sendTransactionBatch only accepts TransactionRequest, not TransactionResponses
306
- if (batch.find(v => v.wait !== undefined && v.wait !== null)) {
307
- throw new Error('transaction request expected for sendTransactionBatch, transaction response found')
308
- }
372
+ async call(
373
+ transaction: ethers.utils.Deferrable<ethers.providers.TransactionRequest>,
374
+ blockTag?: ethers.providers.BlockTag | Promise<ethers.providers.BlockTag>,
375
+ optionals?: OptionalChainIdLike
376
+ ) {
377
+ const provider = await this._getSubprovider(optionals?.chainId)
378
+ return provider.call(transaction, blockTag)
379
+ }
309
380
 
310
- const asExtended = toExtended(batch)
311
- return this.sendTransaction(asExtended, chainId)
381
+ async estimateGas(
382
+ transaction: ethers.utils.Deferrable<ethers.providers.TransactionRequest>,
383
+ optionals?: OptionalChainIdLike
384
+ ) {
385
+ const provider = await this._getSubprovider(optionals?.chainId)
386
+ return provider.estimateGas(transaction)
312
387
  }
313
388
 
314
- signTransactions(
315
- transaction: Deferrable<ethers.providers.TransactionRequest>,
316
- chainId?: ChainIdLike
317
- ): Promise<commons.transaction.SignedTransactionBundle> {
318
- transaction = shallowCopy(transaction)
319
- // TODO: transaction argument..? make sure to resolve any properties and serialize property before sending over
320
- // the wire.. see sendUncheckedTransaction and resolveProperties
321
- return this.provider.send('eth_signTransaction', [transaction], maybeChainId(chainId) || this.defaultChainId)
389
+ async getBlock(
390
+ blockHashOrBlockTag: ethers.providers.BlockTag | string | Promise<ethers.providers.BlockTag | string>,
391
+ optionals?: OptionalChainIdLike
392
+ ) {
393
+ const provider = await this._getSubprovider(optionals?.chainId)
394
+ return provider.getBlock(blockHashOrBlockTag)
322
395
  }
323
396
 
324
- sendSignedTransactions(
325
- signedTxs: commons.transaction.SignedTransactionBundle,
326
- chainId?: ChainIdLike
327
- ): Promise<ethers.providers.TransactionResponse> {
328
- // sequence_relay
329
- throw new Error('TODO')
397
+ async getTransaction(
398
+ transactionHash: string | Promise<string>,
399
+ optionals?: OptionalChainIdLike
400
+ ) {
401
+ const provider = await this._getSubprovider(optionals?.chainId)
402
+ return provider.getTransaction(transactionHash)
330
403
  }
331
404
 
332
- // updateConfig..
333
- // NOTE: this is not supported by the remote wallet by default.
334
- async updateConfig(newConfig?: commons.config.Config): Promise<[commons.config.Config, ethers.providers.TransactionResponse | undefined]> {
335
- // sequence_updateConfig
336
- const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId)
337
- if (tx === null) {
338
- return [config, undefined]
405
+ async getLogs(
406
+ filter: ethers.providers.Filter | Promise<ethers.providers.Filter>,
407
+ optionals?: OptionalChainIdLike
408
+ ) {
409
+ const provider = await this._getSubprovider(optionals?.chainId)
410
+ return provider.getLogs(filter)
411
+ }
412
+
413
+ // ENS methods
414
+
415
+ async supportsENS(): Promise<boolean> {
416
+ const networks = await this.getNetworks()
417
+ return networks.some(n => n.chainId === 1)
418
+ }
419
+
420
+ async getResolver(name: string) {
421
+ if (!await this.supportsENS()) {
422
+ return null
339
423
  }
340
424
 
341
- const provider = await this.getSender(this.defaultChainId)
342
- return [config, provider!._wrapTransaction(tx, tx.hash)]
425
+ // Resolver is always on the chainId 1
426
+ const provider = await this._getSubprovider(1)
427
+ return provider.getResolver(name)
343
428
  }
344
429
 
345
- // publishConfig..
346
- // NOTE: this is not supported by the remote wallet by default.
347
- async publishConfig(): Promise<ethers.providers.TransactionResponse | undefined> {
348
- const provider = await this.getSender(this.defaultChainId)
430
+ async resolveName(name: string | Promise<string>) {
431
+ if (ethers.utils.isAddress(await name)) {
432
+ return name
433
+ }
349
434
 
350
- const tx = await provider!.send('sequence_publishConfig', [])
351
- if (tx === null) {
352
- return undefined
435
+ if (!await this.supportsENS()) {
436
+ return null
353
437
  }
354
- return provider!._wrapTransaction(tx, tx.hash)
355
- }
356
438
 
357
- async isDeployed(chainId?: ChainIdLike): Promise<boolean> {
358
- const provider = await this.getSender(maybeChainId(chainId))
359
- const walletCode = await provider!.getCode(await this.getAddress())
360
- return !!walletCode && walletCode !== '0x'
439
+ // Resolver is always on the chainId 1
440
+ const provider = await this._getSubprovider(1)
441
+ return provider.resolveName(name)
361
442
  }
362
443
 
363
- //
364
- // ethers JsonRpcSigner methods
365
- //
444
+ async lookupAddress(address: string | Promise<string>) {
445
+ if (!await this.supportsENS()) {
446
+ return null
447
+ }
366
448
 
367
- async _legacySignMessage(message: Bytes | string, chainId?: ChainIdLike): Promise<string> {
368
- const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId)
369
-
370
- const data = typeof message === 'string' ? ethers.utils.toUtf8Bytes(message) : message
371
- const address = await this.getAddress()
372
-
373
- // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
374
- // NOTE: ethers since 5.5 has switched to using personal_sign, we should review, etc.
375
- return await provider!.send('eth_sign', [address, ethers.utils.hexlify(data)])
376
- }
377
-
378
- async _signTypedData(
379
- domain: TypedDataDomain,
380
- types: Record<string, Array<TypedDataField>>,
381
- message: Record<string, any>,
382
- chainId?: ChainIdLike
383
- ): Promise<string> {
384
- return this.signTypedData(domain, types, message, chainId)
385
- }
386
-
387
- async sendUncheckedTransaction(transaction: Deferrable<ExtendedTransactionRequest>, chainId?: ChainIdLike): Promise<string> {
388
- transaction = shallowCopy(transaction)
389
-
390
- const fromAddress = this.getAddress()
391
-
392
- // NOTE: we do not use provider estimation, and instead rely on our relayer to determine the gasLimit and gasPrice
393
- //
394
- // TODO: alternatively/one day, we could write a provider middleware to eth_estimateGas
395
- // and send it to our relayer url instead for estimation..
396
- //
397
- // if (!transaction.gasLimit) {
398
- // const estimate = shallowCopy(transaction)
399
- // estimate.from = fromAddress
400
- // transaction.gasLimit = this.provider.estimateGas(estimate)
401
- // }
402
-
403
- const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId)
404
-
405
- return resolveProperties({
406
- tx: resolveProperties(transaction),
407
- sender: await fromAddress
408
- }).then(({ tx, sender }) => {
409
- if (tx.from != null) {
410
- if (ethers.utils.getAddress(tx.from) !== sender) {
411
- // logger.throwArgumentError("from address mismatch", "transaction", transaction)
412
- throw new Error(`from address mismatch for transaction ${transaction}`)
413
- }
414
- } else {
415
- tx.from = sender
416
- }
417
-
418
- const hexTx = hexlifyTransaction(tx)
419
-
420
- return provider!.send('eth_sendTransaction', [hexTx]).then(
421
- hash => {
422
- return hash
423
- },
424
- error => {
425
- // return checkError("sendTransaction", error, hexTx)
426
- throw error
427
- }
428
- )
429
- })
449
+ // Resolver is always on the chainId 1
450
+ const provider = await this._getSubprovider(1)
451
+ return provider.lookupAddress(address)
430
452
  }
431
453
 
432
- connectUnchecked(): ethers.providers.JsonRpcSigner {
433
- return new UncheckedJsonRpcSigner(this.provider, this.defaultChainId)
454
+ async getAvatar(nameOrAddress: string) {
455
+ if (!await this.supportsENS()) {
456
+ return null
457
+ }
458
+
459
+ const provider = await this._getSubprovider(1)
460
+ return provider.getAvatar(nameOrAddress)
434
461
  }
435
462
 
436
- async unlock(password: string): Promise<boolean> {
437
- const address = await this.getAddress()
438
- return this.provider.send('personal_unlockAccount', [address, password, null])
463
+ static is = (provider: any): provider is SequenceProvider => {
464
+ return (
465
+ provider &&
466
+ typeof provider === 'object' &&
467
+ provider._isSequenceProvider === true
468
+ )
439
469
  }
440
470
  }
441
471
 
442
- // NOTE: method has been copied + modified from ethers.js JsonRpcProvider
443
- // Convert an ethers.js transaction into a JSON-RPC transaction
444
-
445
- const allowedTransactionKeys: { [key: string]: boolean } = {
446
- chainId: true,
447
- data: true,
448
- gasLimit: true,
449
- gasPrice: true,
450
- nonce: true,
451
- to: true,
452
- value: true,
453
- from: true,
454
- auxiliary: true,
455
- expiration: true,
456
- afterNonce: true,
457
- delegateCall: true,
458
- revertOnError: true
472
+ function normalizeChainId(chainId: string | number | bigint | { chainId: string }): number {
473
+ if (typeof chainId === 'object') return normalizeChainId(chainId.chainId)
474
+ return ethers.BigNumber.from(chainId).toNumber()
459
475
  }
460
476
 
461
- const hexlifyTransaction = (
462
- transaction: ExtendedTransactionRequest,
463
- allowExtra?: { [key: string]: boolean }
464
- ): { [key: string]: string } => {
465
- // Check only allowed properties are given
466
- const allowed = shallowCopy(allowedTransactionKeys)
467
- if (allowExtra) {
468
- for (const key in allowExtra) {
469
- if (allowExtra[key]) {
470
- allowed[key] = true
471
- }
472
- }
477
+ /**
478
+ * This is the same provider, but it only allows a single network at a time.
479
+ * the network defined by the constructor is the only one that can be used.
480
+ *
481
+ * Attempting to call any method with a different network will throw an error.
482
+ * Attempting to change the network of this provider will throw an error.
483
+ *
484
+ * NOTICE: These networks won't support ENS unless they are the mainnet.
485
+ */
486
+ export class SingleNetworkSequenceProvider extends SequenceProvider {
487
+ readonly _isSingleNetworkSequenceProvider = true
488
+
489
+ constructor(
490
+ client: SequenceClient,
491
+ providerFor: (networkId: number) => ethers.providers.JsonRpcProvider,
492
+ public readonly chainId: ChainIdLike
493
+ ) {
494
+ super(client, providerFor)
473
495
  }
474
- ethers.utils.checkProperties(transaction, allowed)
475
496
 
476
- const result: { [key: string]: any } = {}
497
+ private _useChainId(chainId?: ChainIdLike): number {
498
+ const provided = this.toChainId(chainId)
477
499
 
478
- // Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
479
- ;['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(key => {
480
- const value = (transaction as any)[key]
481
- if (value === null || value === undefined) {
482
- return
483
- }
484
- const hexValue = ethers.utils.hexValue(value)
485
- if (key === 'gasLimit') {
486
- key = 'gas'
500
+ if (provided && provided !== this.chainId) {
501
+ throw new Error(`This provider only supports the network ${this.chainId}, but ${provided} was requested.`)
487
502
  }
488
- result[key] = hexValue
489
- })
490
- ;['from', 'to', 'data'].forEach(key => {
491
- if (!(<any>transaction)[key]) {
492
- return
493
- }
494
- result[key] = ethers.utils.hexlify((<any>transaction)[key])
495
- })
496
- ;['delegateCall', 'revertOnError'].forEach(key => {
497
- const value = (transaction as any)[key]
498
- if (value !== undefined && value !== null) {
499
- result[key] = value
503
+
504
+ return provided || super.toChainId(this.chainId)
505
+ }
506
+
507
+ protected useChainId(chainId?: ChainIdLike): Promise<number> {
508
+ return Promise.resolve(this._useChainId(chainId))
509
+ }
510
+
511
+ getChainId(): number {
512
+ return super.toChainId(this.chainId)
513
+ }
514
+
515
+ async getNetwork(): Promise<NetworkConfig> {
516
+ const networks = await this.client.getNetworks()
517
+ const res = findNetworkConfig(networks, this.chainId)
518
+
519
+ if (!res) {
520
+ throw new Error(`Unsupported network ${this.chainId}`)
500
521
  }
501
- })
502
522
 
503
- const auxiliary = <any>transaction['auxiliary']
504
- if (auxiliary && auxiliary.length > 0) {
505
- result['auxiliary'] = []
506
- auxiliary.forEach((a: any) => {
507
- result['auxiliary'].push(hexlifyTransaction(a))
508
- })
523
+ return res
509
524
  }
510
525
 
511
- return result
512
- }
526
+ /**
527
+ * Override getProvider and getSigner so they always use `useChainId`
528
+ * this way they can't return providers and signers that can switch networks,
529
+ * or that don't match the chainId of this signer.
530
+ */
531
+ getProvider(chainId?: ChainIdLike): SingleNetworkSequenceProvider {
532
+ if (this._useChainId(chainId) !== this.chainId) {
533
+ throw new Error(`Unreachable code`)
534
+ }
535
+
536
+ return this
537
+ }
513
538
 
514
- class UncheckedJsonRpcSigner extends Web3Signer {
515
- sendTransaction(transaction: Deferrable<ethers.providers.TransactionRequest>): Promise<commons.transaction.TransactionResponse> {
516
- return this.sendUncheckedTransaction(transaction).then(hash => {
517
- return <commons.transaction.TransactionResponse>{
518
- chainId: 0,
519
- confirmations: 0,
520
- data: '',
521
- from: '',
522
- gasLimit: ethers.constants.Zero,
523
- gasPrice: ethers.constants.Zero,
524
- hash,
525
- nonce: 0,
526
- value: ethers.constants.Zero,
527
- wait: (confirmations?: number) => this.provider.waitForTransaction(hash, confirmations)
528
- }
529
- })
539
+ getSigner(chainId?: ChainIdLike): SingleNetworkSequenceSigner {
540
+ return super.getSigner(this._useChainId(chainId))
541
+ }
542
+
543
+ setDefaultChainId(_chainId: ChainIdLike): void {
544
+ throw new Error(`This provider only supports the network ${this.chainId}; use the parent provider to switch networks.`)
545
+ }
546
+
547
+ static is(cand: any): cand is SingleNetworkSequenceProvider {
548
+ return (
549
+ cand &&
550
+ typeof cand === 'object' &&
551
+ cand._isSingleNetworkSequenceProvider === true
552
+ )
530
553
  }
531
554
  }