@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,3508 @@
|
|
|
1
|
+
import { EventEmitter2 } from 'eventemitter2';
|
|
2
|
+
import { logger, packMessageData, sanitizeNumberString, sanitizeHost, sanitizeAlphanumeric, base64EncodeObject, base64DecodeObject, shallowCopy, resolveProperties, encodeMessageDigest, encodeTypedDataDigest } from '@0xsequence/utils';
|
|
3
|
+
import { ethers, BigNumber } from 'ethers';
|
|
4
|
+
import { addressOf } from '@0xsequence/config';
|
|
5
|
+
import { isValidSignature as isValidSignature$1, recoverConfig, Signer, resolveArrayProperties } from '@0xsequence/wallet';
|
|
6
|
+
import { isSignedTransactions } from '@0xsequence/transactions';
|
|
7
|
+
import { signAuthorization } from '@0xsequence/auth';
|
|
8
|
+
import { JsonRpcSender, maybeChainId, allowProviderMiddleware, networkProviderMiddleware, CachedProvider, JsonRpcRouter, loggingProviderMiddleware, exceptionProviderMiddleware, findNetworkConfig, EagerProvider, SigningProvider, updateNetworkConfig, ensureValidNetworks } from '@0xsequence/network';
|
|
9
|
+
import { Web3Provider as Web3Provider$1, JsonRpcProvider } from '@ethersproject/providers';
|
|
10
|
+
|
|
11
|
+
function _extends() {
|
|
12
|
+
_extends = Object.assign || function (target) {
|
|
13
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
14
|
+
var source = arguments[i];
|
|
15
|
+
|
|
16
|
+
for (var key in source) {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
18
|
+
target[key] = source[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return target;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return _extends.apply(this, arguments);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ProviderMessageCallback is used to respond to ProviderMessage requests. The error
|
|
30
|
+
// argument is for exceptions during the execution, and response is the response payload
|
|
31
|
+
// which may contain the result or an error payload from the wallet.
|
|
32
|
+
class WindowSessionParams extends URLSearchParams {
|
|
33
|
+
static new(init) {
|
|
34
|
+
return new URLSearchParams(init);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
let EventType;
|
|
39
|
+
|
|
40
|
+
(function (EventType) {
|
|
41
|
+
EventType["OPEN"] = "open";
|
|
42
|
+
EventType["CLOSE"] = "close";
|
|
43
|
+
EventType["MESSAGE"] = "message";
|
|
44
|
+
EventType["CONNECT"] = "connect";
|
|
45
|
+
EventType["DISCONNECT"] = "disconnect";
|
|
46
|
+
EventType["ACCOUNTS_CHANGED"] = "accountsChanged";
|
|
47
|
+
EventType["CHAIN_CHANGED"] = "chainChanged";
|
|
48
|
+
EventType["NETWORKS"] = "networks";
|
|
49
|
+
EventType["WALLET_CONTEXT"] = "walletContext";
|
|
50
|
+
EventType["INIT"] = "init";
|
|
51
|
+
EventType["DEBUG"] = "_debug";
|
|
52
|
+
})(EventType || (EventType = {}));
|
|
53
|
+
|
|
54
|
+
let OpenState;
|
|
55
|
+
|
|
56
|
+
(function (OpenState) {
|
|
57
|
+
OpenState[OpenState["CLOSED"] = 0] = "CLOSED";
|
|
58
|
+
OpenState[OpenState["OPENING"] = 1] = "OPENING";
|
|
59
|
+
OpenState[OpenState["OPENED"] = 2] = "OPENED";
|
|
60
|
+
})(OpenState || (OpenState = {}));
|
|
61
|
+
|
|
62
|
+
let InitState;
|
|
63
|
+
|
|
64
|
+
(function (InitState) {
|
|
65
|
+
InitState[InitState["NIL"] = 0] = "NIL";
|
|
66
|
+
InitState[InitState["SENT_NONCE"] = 1] = "SENT_NONCE";
|
|
67
|
+
InitState[InitState["OK"] = 2] = "OK";
|
|
68
|
+
})(InitState || (InitState = {}));
|
|
69
|
+
|
|
70
|
+
class ProviderError extends Error {
|
|
71
|
+
constructor(message) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = 'ProviderError';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
const ErrSignedInRequired = new ProviderError('Wallet is not signed in. Connect a wallet and try again.'); // TODO: lets build some nice error handling tools, prob in /utils ...
|
|
78
|
+
|
|
79
|
+
const PROVIDER_OPEN_TIMEOUT = 30000; // in ms
|
|
80
|
+
|
|
81
|
+
let _messageIdx = 0;
|
|
82
|
+
const nextMessageIdx = () => ++_messageIdx;
|
|
83
|
+
class BaseProviderTransport {
|
|
84
|
+
constructor() {
|
|
85
|
+
var _this = this;
|
|
86
|
+
|
|
87
|
+
this.pendingMessageRequests = [];
|
|
88
|
+
this.responseCallbacks = new Map();
|
|
89
|
+
this.state = void 0;
|
|
90
|
+
this.confirmationOnly = false;
|
|
91
|
+
this.events = new EventEmitter2();
|
|
92
|
+
this.openPayload = void 0;
|
|
93
|
+
this.connectPayload = void 0;
|
|
94
|
+
this.accountsChangedPayload = void 0;
|
|
95
|
+
this.networksPayload = void 0;
|
|
96
|
+
this.walletContextPayload = void 0;
|
|
97
|
+
this._sessionId = void 0;
|
|
98
|
+
this._init = void 0;
|
|
99
|
+
this._registered = void 0;
|
|
100
|
+
|
|
101
|
+
this.sendAsync = async function (request, callback, chainId) {
|
|
102
|
+
// here, we receive the message from the dapp provider call
|
|
103
|
+
if (_this.state === OpenState.CLOSED) {
|
|
104
|
+
// flag the wallet to auto-close once user submits input. ie.
|
|
105
|
+
// prompting to sign a message or transaction
|
|
106
|
+
_this.confirmationOnly = true;
|
|
107
|
+
} // open/focus the wallet.
|
|
108
|
+
// automatically open the wallet when a provider request makes it here.
|
|
109
|
+
//
|
|
110
|
+
// NOTE: if we're not signed in, then the provider will fail, users must first connect+sign in.
|
|
111
|
+
//
|
|
112
|
+
// TODO: how does this behave with a session has expired?
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
_this.openWallet(undefined, {
|
|
116
|
+
type: 'jsonRpcRequest',
|
|
117
|
+
method: request.method
|
|
118
|
+
}, chainId); // send message request, await, and then execute callback after receiving the response
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
if (!_this.isOpened()) {
|
|
123
|
+
await _this.waitUntilOpened(); // will throw on timeout
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const response = await _this.sendMessageRequest({
|
|
127
|
+
idx: nextMessageIdx(),
|
|
128
|
+
type: EventType.MESSAGE,
|
|
129
|
+
data: request,
|
|
130
|
+
chainId: chainId
|
|
131
|
+
});
|
|
132
|
+
callback(undefined, response.data);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
callback(err);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
this.sendMessageRequest = async function (message) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
if ((!message.idx || message.idx <= 0) && message.type !== 'init') {
|
|
141
|
+
reject(new Error('message idx not set'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const responseCallback = (error, response) => {
|
|
145
|
+
if (error) {
|
|
146
|
+
reject(error);
|
|
147
|
+
} else if (response) {
|
|
148
|
+
resolve(response);
|
|
149
|
+
} else {
|
|
150
|
+
throw new Error('no valid response to return');
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const idx = message.idx;
|
|
155
|
+
|
|
156
|
+
if (!_this.responseCallbacks.get(idx)) {
|
|
157
|
+
_this.responseCallbacks.set(idx, responseCallback);
|
|
158
|
+
} else {
|
|
159
|
+
reject(new Error('duplicate message idx, should never happen'));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!_this.isOpened()) {
|
|
163
|
+
logger.debug('pushing to pending requests', message);
|
|
164
|
+
|
|
165
|
+
_this.pendingMessageRequests.push(message);
|
|
166
|
+
} else {
|
|
167
|
+
_this.sendMessage(message);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
this.waitUntilOpened = async function (openTimeout = PROVIDER_OPEN_TIMEOUT) {
|
|
173
|
+
let opened = false;
|
|
174
|
+
return Promise.race([new Promise((_, reject) => {
|
|
175
|
+
const timeout = setTimeout(() => {
|
|
176
|
+
clearTimeout(timeout); // only emit close if the timeout wins the race
|
|
177
|
+
|
|
178
|
+
if (!opened) {
|
|
179
|
+
_this.state = OpenState.CLOSED;
|
|
180
|
+
|
|
181
|
+
_this.events.emit('close', {
|
|
182
|
+
code: 1005,
|
|
183
|
+
message: 'opening wallet timed out'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
reject(new Error('opening wallet timed out'));
|
|
188
|
+
}, openTimeout);
|
|
189
|
+
}), new Promise(resolve => {
|
|
190
|
+
if (_this.isOpened()) {
|
|
191
|
+
var _this$openPayload;
|
|
192
|
+
|
|
193
|
+
opened = true;
|
|
194
|
+
resolve((_this$openPayload = _this.openPayload) == null ? void 0 : _this$openPayload.session);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_this.events.once('open', openInfo => {
|
|
199
|
+
_this.openPayload = openInfo;
|
|
200
|
+
opened = true;
|
|
201
|
+
resolve(openInfo.session);
|
|
202
|
+
});
|
|
203
|
+
})]);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
this.waitUntilConnected = async function () {
|
|
207
|
+
await _this.waitUntilOpened();
|
|
208
|
+
const connect = new Promise(resolve => {
|
|
209
|
+
if (_this.connectPayload) {
|
|
210
|
+
resolve(_this.connectPayload);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_this.events.once('connect', connectDetails => {
|
|
215
|
+
_this.connectPayload = connectDetails;
|
|
216
|
+
resolve(connectDetails);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
const closeWallet = new Promise((_, reject) => {
|
|
220
|
+
_this.events.once('close', error => {
|
|
221
|
+
if (error) {
|
|
222
|
+
reject(new Error(`wallet closed due to ${JSON.stringify(error)}`));
|
|
223
|
+
} else {
|
|
224
|
+
reject(new Error(`user closed the wallet`));
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
return Promise.race([connect, closeWallet]);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
this.state = OpenState.CLOSED;
|
|
232
|
+
this._registered = false;
|
|
233
|
+
this._init = InitState.NIL;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
get registered() {
|
|
237
|
+
return this._registered;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
register() {
|
|
241
|
+
throw new Error('abstract method');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
unregister() {
|
|
245
|
+
throw new Error('abstract method');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
openWallet(path, intent, networkId) {
|
|
249
|
+
throw new Error('abstract method');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
closeWallet() {
|
|
253
|
+
throw new Error('abstract method');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
isOpened() {
|
|
257
|
+
return this.registered && this.state === OpenState.OPENED;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
isConnected() {
|
|
261
|
+
var _this$openPayload2;
|
|
262
|
+
|
|
263
|
+
// if we're registered, and we have the account details, then we are connected
|
|
264
|
+
const session = (_this$openPayload2 = this.openPayload) == null ? void 0 : _this$openPayload2.session;
|
|
265
|
+
return this.registered && session !== undefined && !!session.accountAddress && session.accountAddress.length === 42 && !!session.networks && session.networks.length > 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// handleMessage will handle message received from the remote wallet
|
|
269
|
+
handleMessage(message) {
|
|
270
|
+
var _this2 = this;
|
|
271
|
+
|
|
272
|
+
// init incoming for initial handshake with transport.
|
|
273
|
+
// always respond to INIT messages, e.g. on popup window reload
|
|
274
|
+
if (message.type === EventType.INIT) {
|
|
275
|
+
logger.debug('MessageProvider, received INIT message', message);
|
|
276
|
+
const {
|
|
277
|
+
nonce
|
|
278
|
+
} = message.data;
|
|
279
|
+
|
|
280
|
+
if (!nonce || nonce.length == 0) {
|
|
281
|
+
logger.error('invalid init nonce');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this._init = InitState.OK;
|
|
286
|
+
this.sendMessage({
|
|
287
|
+
idx: -1,
|
|
288
|
+
type: EventType.INIT,
|
|
289
|
+
data: {
|
|
290
|
+
sessionId: this._sessionId,
|
|
291
|
+
nonce: nonce
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (this._init !== InitState.OK) {
|
|
297
|
+
// if provider is not init'd, then we drop any received messages. the only
|
|
298
|
+
// message we will process is of event type 'init', as our acknowledgement
|
|
299
|
+
return;
|
|
300
|
+
} // message is either a notification, or its a ProviderMessageResponse
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
logger.debug('RECEIVED MESSAGE FROM WALLET', message.idx, message);
|
|
304
|
+
const requestIdx = message.idx;
|
|
305
|
+
const responseCallback = this.responseCallbacks.get(requestIdx);
|
|
306
|
+
|
|
307
|
+
if (requestIdx) {
|
|
308
|
+
this.responseCallbacks.delete(requestIdx);
|
|
309
|
+
} // OPEN response
|
|
310
|
+
//
|
|
311
|
+
// Flip opened flag, and flush the pending queue
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
if (message.type === EventType.OPEN && !this.isOpened()) {
|
|
315
|
+
var _message$data, _message$data2;
|
|
316
|
+
|
|
317
|
+
if (this._sessionId && this._sessionId !== ((_message$data = message.data) == null ? void 0 : _message$data.sessionId)) {
|
|
318
|
+
logger.debug('open event received from wallet, but does not match sessionId', this._sessionId);
|
|
319
|
+
return;
|
|
320
|
+
} // check if open error occured due to invalid defaultNetworkId
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if ((_message$data2 = message.data) != null && _message$data2.error) {
|
|
324
|
+
var _message$data3;
|
|
325
|
+
|
|
326
|
+
const err = new Error(`opening wallet failed: received ${(_message$data3 = message.data) == null ? void 0 : _message$data3.error}`);
|
|
327
|
+
logger.error(err);
|
|
328
|
+
this.close();
|
|
329
|
+
throw err;
|
|
330
|
+
} // success!
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
this.state = OpenState.OPENED;
|
|
334
|
+
this.openPayload = message.data;
|
|
335
|
+
this.events.emit('open', this.openPayload); // flush pending requests when connected
|
|
336
|
+
|
|
337
|
+
if (this.pendingMessageRequests.length !== 0) {
|
|
338
|
+
const pendingMessageRequests = this.pendingMessageRequests.splice(0, this.pendingMessageRequests.length);
|
|
339
|
+
pendingMessageRequests.forEach(async function (pendingMessageRequest) {
|
|
340
|
+
_this2.sendMessage(pendingMessageRequest);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return;
|
|
345
|
+
} // MESSAGE resposne
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
if (message.type === EventType.MESSAGE) {
|
|
349
|
+
// Require user confirmation, bring up wallet to prompt for input then close
|
|
350
|
+
// TODO: perhaps apply technique like in multicall to queue messages within
|
|
351
|
+
// a period of time, then close the window if responseCallbacks is empty, this is better.
|
|
352
|
+
if (this.confirmationOnly) {
|
|
353
|
+
setTimeout(() => {
|
|
354
|
+
if (this.responseCallbacks.size === 0) {
|
|
355
|
+
this.closeWallet();
|
|
356
|
+
}
|
|
357
|
+
}, 500); // TODO: be smarter about timer as we're processing the response callbacks..
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!responseCallback) {
|
|
361
|
+
// NOTE: this would occur if 'idx' isn't set, which should never happen
|
|
362
|
+
// or when we register two handler, or duplicate messages with the same idx are sent,
|
|
363
|
+
// all of which should be prevented prior to getting to this point
|
|
364
|
+
throw new Error('impossible state');
|
|
365
|
+
} // Callback to original caller
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
if (responseCallback) {
|
|
369
|
+
this.events.emit('message', message);
|
|
370
|
+
responseCallback(message.data.error, message);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
} // ACCOUNTS_CHANGED -- when a user logs in or out
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
if (message.type === EventType.ACCOUNTS_CHANGED) {
|
|
377
|
+
this.accountsChangedPayload = undefined;
|
|
378
|
+
|
|
379
|
+
if (message.data && message.data.length > 0) {
|
|
380
|
+
this.accountsChangedPayload = ethers.utils.getAddress(message.data[0]);
|
|
381
|
+
this.events.emit('accountsChanged', [this.accountsChangedPayload]);
|
|
382
|
+
} else {
|
|
383
|
+
this.events.emit('accountsChanged', []);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return;
|
|
387
|
+
} // CHAIN_CHANGED -- when a user changes their default chain
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if (message.type === EventType.CHAIN_CHANGED) {
|
|
391
|
+
this.events.emit('chainChanged', message.data);
|
|
392
|
+
return;
|
|
393
|
+
} // NOTIFY NETWORKS -- when a user connects or logs in
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if (message.type === EventType.NETWORKS) {
|
|
397
|
+
this.networksPayload = message.data;
|
|
398
|
+
this.events.emit('networks', this.networksPayload);
|
|
399
|
+
return;
|
|
400
|
+
} // NOTIFY WALLET_CONTEXT -- when a user connects or logs in
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
if (message.type === EventType.WALLET_CONTEXT) {
|
|
404
|
+
this.walletContextPayload = message.data;
|
|
405
|
+
this.events.emit('walletContext', this.walletContextPayload);
|
|
406
|
+
return;
|
|
407
|
+
} // NOTIFY CLOSE -- when wallet instructs to close
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
if (message.type === EventType.CLOSE) {
|
|
411
|
+
if (this.state !== OpenState.CLOSED) {
|
|
412
|
+
this.close(message.data);
|
|
413
|
+
}
|
|
414
|
+
} // NOTIFY CONNECT -- when wallet instructs we've connected
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
if (message.type === EventType.CONNECT) {
|
|
418
|
+
this.connectPayload = message.data;
|
|
419
|
+
this.events.emit('connect', this.connectPayload);
|
|
420
|
+
} // NOTIFY DISCONNECT -- when wallet instructs to disconnect
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
if (message.type === EventType.DISCONNECT) {
|
|
424
|
+
if (this.isConnected()) {
|
|
425
|
+
this.events.emit('disconnect', message.data);
|
|
426
|
+
this.close();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} // sendMessageRequest sends a ProviderMessageRequest over the wire to the wallet
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
sendMessage(message) {
|
|
433
|
+
throw new Error('abstract method');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
on(event, fn) {
|
|
437
|
+
this.events.on(event, fn);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
once(event, fn) {
|
|
441
|
+
this.events.once(event, fn);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
emit(event, ...args) {
|
|
445
|
+
return this.events.emit(event, ...args);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
close(error) {
|
|
449
|
+
if (this.state === OpenState.CLOSED) return;
|
|
450
|
+
this.state = OpenState.CLOSED;
|
|
451
|
+
this.confirmationOnly = false;
|
|
452
|
+
this._sessionId = undefined;
|
|
453
|
+
logger.info('closing wallet and flushing!'); // flush pending requests and return error to all callbacks
|
|
454
|
+
|
|
455
|
+
this.pendingMessageRequests.length = 0;
|
|
456
|
+
this.responseCallbacks.forEach(responseCallback => {
|
|
457
|
+
responseCallback(_extends({}, new Error('wallet closed'), {
|
|
458
|
+
code: 4001
|
|
459
|
+
}));
|
|
460
|
+
});
|
|
461
|
+
this.responseCallbacks.clear();
|
|
462
|
+
this.connectPayload = undefined;
|
|
463
|
+
this.openPayload = undefined;
|
|
464
|
+
this.accountsChangedPayload = undefined;
|
|
465
|
+
this.networksPayload = undefined;
|
|
466
|
+
this.walletContextPayload = undefined;
|
|
467
|
+
this.events.emit('close', error);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const isValidSignature = async (address, digest, sig, provider, chainId, walletContext) => {
|
|
473
|
+
chainId = chainId || (await provider.getChainId());
|
|
474
|
+
walletContext = walletContext || (await provider.getSigner().getWalletContext());
|
|
475
|
+
return isValidSignature$1(address, digest, sig, provider, walletContext, chainId);
|
|
476
|
+
};
|
|
477
|
+
const recoverWalletConfig = async (address, digest, signature, chainId, walletContext) => {
|
|
478
|
+
const subDigest = packMessageData(address, chainId, digest);
|
|
479
|
+
const config = await recoverConfig(subDigest, signature);
|
|
480
|
+
|
|
481
|
+
if (walletContext) {
|
|
482
|
+
const recoveredWalletAddress = addressOf(config, walletContext);
|
|
483
|
+
|
|
484
|
+
if (config.address && config.address !== recoveredWalletAddress) {
|
|
485
|
+
throw new Error('recovered address does not match the WalletConfig address, check the WalletContext');
|
|
486
|
+
} else {
|
|
487
|
+
config.address = recoveredWalletAddress;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return config;
|
|
492
|
+
};
|
|
493
|
+
const isBrowserExtension = () => window.location.protocol === 'chrome-extension:' || window.location.protocol === 'moz-extension:'; // window.localstorage helper
|
|
494
|
+
|
|
495
|
+
class LocalStore {
|
|
496
|
+
constructor(key, def) {
|
|
497
|
+
this.def = def;
|
|
498
|
+
this.key = void 0;
|
|
499
|
+
this.key = key;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
get() {
|
|
503
|
+
const val = window.localStorage.getItem(this.key);
|
|
504
|
+
|
|
505
|
+
if (val === null) {
|
|
506
|
+
return this.def;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
return JSON.parse(val);
|
|
511
|
+
} catch (err) {
|
|
512
|
+
console.error(err);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
set(val) {
|
|
519
|
+
val ? window.localStorage.setItem(this.key, JSON.stringify(val)) : window.localStorage.removeItem(this.key);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
del() {
|
|
523
|
+
window.localStorage.removeItem(this.key);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const TRANSPORT_SESSION_LS_KEY = '@sequence.transportSession';
|
|
529
|
+
class BaseWalletTransport {
|
|
530
|
+
// appOrigin identifies the dapp's origin which opened the app. A transport
|
|
531
|
+
// will auto-detect and set this value if it can. This is determined
|
|
532
|
+
// as the parent app/window which opened the wallet.
|
|
533
|
+
constructor(walletRequestHandler) {
|
|
534
|
+
var _this = this;
|
|
535
|
+
|
|
536
|
+
this.walletRequestHandler = void 0;
|
|
537
|
+
this._sessionId = void 0;
|
|
538
|
+
this._registered = void 0;
|
|
539
|
+
this._init = void 0;
|
|
540
|
+
this._initNonce = void 0;
|
|
541
|
+
this._initCallback = void 0;
|
|
542
|
+
this.appOrigin = void 0;
|
|
543
|
+
|
|
544
|
+
this.sendAsync = async function (request, callback, chainId) {
|
|
545
|
+
throw new Error('abstract method');
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
this.handleMessage = async function (message) {
|
|
549
|
+
const request = message; // ensure initial handshake is complete before accepting
|
|
550
|
+
// other kinds of messages.
|
|
551
|
+
|
|
552
|
+
if (_this._init !== InitState.OK) {
|
|
553
|
+
if (request.type === EventType.INIT) {
|
|
554
|
+
if (_this.isValidInitAck(message)) {
|
|
555
|
+
// successful init
|
|
556
|
+
if (_this._initCallback) _this._initCallback();
|
|
557
|
+
} else {
|
|
558
|
+
// failed init
|
|
559
|
+
if (_this._initCallback) _this._initCallback('invalid init');
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return;
|
|
565
|
+
} // handle request
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
switch (request.type) {
|
|
569
|
+
case EventType.OPEN:
|
|
570
|
+
{
|
|
571
|
+
if (_this._init !== InitState.OK) return;
|
|
572
|
+
const session = {
|
|
573
|
+
sessionId: request.data.sessionId,
|
|
574
|
+
intent: request.data.intent,
|
|
575
|
+
networkId: request.data.networkId
|
|
576
|
+
};
|
|
577
|
+
await _this.open(session);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
case EventType.CLOSE:
|
|
582
|
+
{
|
|
583
|
+
if (_this._init !== InitState.OK) return; // noop. just here to capture the message so event emitters may be notified
|
|
584
|
+
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
case EventType.MESSAGE:
|
|
589
|
+
{
|
|
590
|
+
const response = await _this.walletRequestHandler.sendMessageRequest(request);
|
|
591
|
+
|
|
592
|
+
_this.sendMessage(response);
|
|
593
|
+
|
|
594
|
+
if (response.data.error) ;
|
|
595
|
+
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
default:
|
|
600
|
+
{
|
|
601
|
+
logger.error(`unexpected payload type ${request.type}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
this.sendMessageRequest = async function (message) {
|
|
607
|
+
return _this.walletRequestHandler.sendMessageRequest(message);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
this.open = async function ({
|
|
611
|
+
sessionId,
|
|
612
|
+
intent,
|
|
613
|
+
networkId
|
|
614
|
+
}) {
|
|
615
|
+
if (sessionId) {
|
|
616
|
+
_this._sessionId = sanitizeNumberString(sessionId); // persist transport session in localstorage for restoring after redirect/reload
|
|
617
|
+
|
|
618
|
+
_this.saveTransportSession({
|
|
619
|
+
sessionId,
|
|
620
|
+
intent,
|
|
621
|
+
networkId
|
|
622
|
+
});
|
|
623
|
+
} // init handshake for certain transports, before we can open the communication.
|
|
624
|
+
//
|
|
625
|
+
// for example, with the window-transport, we have to exchange messages to determine the
|
|
626
|
+
// origin host of the dapp.
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
await _this.init(); // Prepare connect options from intent
|
|
630
|
+
|
|
631
|
+
if (intent && intent.type === 'connect' && intent.options) {
|
|
632
|
+
const connectOptions = intent.options;
|
|
633
|
+
const authorizeOptions = connectOptions; // overlapping types
|
|
634
|
+
// Sanity/integrity check the intent payload, and set authorization origin
|
|
635
|
+
// if its been determined as part of the init handshake from earlier.
|
|
636
|
+
|
|
637
|
+
if (_this.appOrigin && authorizeOptions != null && authorizeOptions.origin) {
|
|
638
|
+
if (!isBrowserExtension()) {
|
|
639
|
+
if (authorizeOptions.origin !== _this.appOrigin) {
|
|
640
|
+
throw new Error('origin is invalid');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} else if (!_this.appOrigin && authorizeOptions != null && authorizeOptions.origin) {
|
|
644
|
+
// ie. when we can't determine the origin in our transport, but dapp provides it to us.
|
|
645
|
+
// we just sanitize the origin host.
|
|
646
|
+
connectOptions.origin = sanitizeHost(authorizeOptions.origin);
|
|
647
|
+
} else if (_this.appOrigin) {
|
|
648
|
+
// ie. when we auto-determine the origin such as in window-transport
|
|
649
|
+
connectOptions.origin = _this.appOrigin;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (connectOptions.app) {
|
|
653
|
+
connectOptions.app = sanitizeAlphanumeric(connectOptions.app);
|
|
654
|
+
} // Set connect options on the walletRequestHandler as our primary
|
|
655
|
+
// wallet controller
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
_this.walletRequestHandler.setConnectOptions(connectOptions);
|
|
659
|
+
|
|
660
|
+
if (connectOptions.networkId) {
|
|
661
|
+
networkId = connectOptions.networkId;
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
_this.walletRequestHandler.setConnectOptions(undefined);
|
|
665
|
+
} // Notify open and proceed to prompt for connection if intended
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
if (!_this.walletRequestHandler.isSignedIn()) {
|
|
669
|
+
// open wallet without a specific connected chainId, as the user is not signed in
|
|
670
|
+
_this.notifyOpen({
|
|
671
|
+
sessionId: _this._sessionId
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
return true;
|
|
675
|
+
} else {
|
|
676
|
+
// Set default network, in case of error chainId will be undefined or 0
|
|
677
|
+
let chainId = undefined;
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
if (networkId) {
|
|
681
|
+
chainId = await _this.walletRequestHandler.setDefaultNetwork(networkId, false);
|
|
682
|
+
} else {
|
|
683
|
+
chainId = await _this.walletRequestHandler.getChainId();
|
|
684
|
+
}
|
|
685
|
+
} catch (err) {
|
|
686
|
+
console.error(err);
|
|
687
|
+
} // Failed to set default network on open -- quit + close
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
if (!chainId || chainId <= 0) {
|
|
691
|
+
_this.notifyOpen({
|
|
692
|
+
sessionId: _this._sessionId,
|
|
693
|
+
error: `failed to open wallet on network ${networkId}`
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
return false;
|
|
697
|
+
} // prompt user with a connect request. the options will be used as previously set above.
|
|
698
|
+
// upon success, the walletRequestHandler will notify the dapp with the ConnectDetails.
|
|
699
|
+
// upon cancellation by user, the walletRequestHandler will throw an error
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
if (intent && intent.type === 'connect') {
|
|
703
|
+
// notify wallet is opened, without session details
|
|
704
|
+
_this.notifyOpen({
|
|
705
|
+
sessionId: _this._sessionId
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
try {
|
|
709
|
+
const connectDetails = await _this.walletRequestHandler.promptConnect(intent.options);
|
|
710
|
+
|
|
711
|
+
if (connectDetails.connected) {
|
|
712
|
+
_this.walletRequestHandler.notifyConnect(connectDetails);
|
|
713
|
+
}
|
|
714
|
+
} catch (err) {
|
|
715
|
+
logger.warn('promptConnect not connected:', err);
|
|
716
|
+
} finally {
|
|
717
|
+
// auto-close by default, unless intent is to keep open
|
|
718
|
+
if (!intent.options || intent.options.keepWalletOpened !== true) {
|
|
719
|
+
_this.notifyClose();
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
} else {
|
|
723
|
+
// user is already connected, notify session details.
|
|
724
|
+
// TODO: in future, keep list if 'connected' dapps / sessions in the session
|
|
725
|
+
// controller, and only sync with allowed apps
|
|
726
|
+
_this.notifyOpen({
|
|
727
|
+
sessionId: _this._sessionId,
|
|
728
|
+
chainId: `${chainId}`,
|
|
729
|
+
session: await _this.walletRequestHandler.walletSession()
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return true;
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
this.saveTransportSession = session => {
|
|
738
|
+
window.localStorage.setItem(TRANSPORT_SESSION_LS_KEY, JSON.stringify(session));
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
this.getCachedTransportSession = () => {
|
|
742
|
+
const session = window.localStorage.getItem(TRANSPORT_SESSION_LS_KEY);
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
return session ? JSON.parse(session) : null;
|
|
746
|
+
} catch (err) {
|
|
747
|
+
console.error(`unable to parse transport session: ${session}`);
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
this.walletRequestHandler = walletRequestHandler;
|
|
753
|
+
this._init = InitState.NIL;
|
|
754
|
+
this.walletRequestHandler.on('connect', connectDetails => {
|
|
755
|
+
if (!this.registered) return; // means user has logged in and wallet is connected to the app
|
|
756
|
+
|
|
757
|
+
this.notifyConnect(connectDetails);
|
|
758
|
+
});
|
|
759
|
+
this.walletRequestHandler.on('disconnect', error => {
|
|
760
|
+
if (!this.registered) return; // means user has logged out the app / disconnected wallet from the app
|
|
761
|
+
|
|
762
|
+
this.notifyDisconnect(error);
|
|
763
|
+
});
|
|
764
|
+
this.walletRequestHandler.on('accountsChanged', accounts => {
|
|
765
|
+
if (!this.registered) return;
|
|
766
|
+
this.notifyAccountsChanged(accounts);
|
|
767
|
+
});
|
|
768
|
+
this.walletRequestHandler.on('networks', networks => {
|
|
769
|
+
if (!this.registered) return;
|
|
770
|
+
this.notifyNetworks(networks);
|
|
771
|
+
|
|
772
|
+
if (!networks || networks.length === 0) {
|
|
773
|
+
this.notifyChainChanged('0x0');
|
|
774
|
+
} else {
|
|
775
|
+
this.notifyChainChanged(ethers.utils.hexlify(networks[0].chainId));
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
this.walletRequestHandler.on('walletContext', walletContext => {
|
|
779
|
+
if (!this.registered || !walletContext) return;
|
|
780
|
+
this.notifyWalletContext(walletContext);
|
|
781
|
+
});
|
|
782
|
+
this.walletRequestHandler.on('close', error => {
|
|
783
|
+
if (!this.registered) return;
|
|
784
|
+
this.notifyClose(error);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
get registered() {
|
|
789
|
+
return this._registered;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
register() {
|
|
793
|
+
throw new Error('abstract method');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
unregister() {
|
|
797
|
+
throw new Error('abstract method');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
sendMessage(message) {
|
|
801
|
+
throw new Error('abstract method');
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
notifyOpen(openInfo) {
|
|
805
|
+
const {
|
|
806
|
+
chainId,
|
|
807
|
+
sessionId,
|
|
808
|
+
session,
|
|
809
|
+
error
|
|
810
|
+
} = openInfo;
|
|
811
|
+
this.sendMessage({
|
|
812
|
+
idx: -1,
|
|
813
|
+
type: EventType.OPEN,
|
|
814
|
+
data: {
|
|
815
|
+
chainId,
|
|
816
|
+
sessionId,
|
|
817
|
+
session,
|
|
818
|
+
error
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
notifyClose(error) {
|
|
824
|
+
this.sendMessage({
|
|
825
|
+
idx: -1,
|
|
826
|
+
type: EventType.CLOSE,
|
|
827
|
+
data: error ? {
|
|
828
|
+
error
|
|
829
|
+
} : null
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
notifyConnect(connectDetails) {
|
|
834
|
+
this.sendMessage({
|
|
835
|
+
idx: -1,
|
|
836
|
+
type: EventType.CONNECT,
|
|
837
|
+
data: connectDetails
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
notifyDisconnect(error) {
|
|
842
|
+
this.sendMessage({
|
|
843
|
+
idx: -1,
|
|
844
|
+
type: EventType.DISCONNECT,
|
|
845
|
+
data: error ? {
|
|
846
|
+
error
|
|
847
|
+
} : null
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
notifyAccountsChanged(accounts) {
|
|
852
|
+
this.sendMessage({
|
|
853
|
+
idx: -1,
|
|
854
|
+
type: EventType.ACCOUNTS_CHANGED,
|
|
855
|
+
data: accounts
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
notifyChainChanged(chainIdHex) {
|
|
860
|
+
this.sendMessage({
|
|
861
|
+
idx: -1,
|
|
862
|
+
type: EventType.CHAIN_CHANGED,
|
|
863
|
+
data: chainIdHex
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
notifyNetworks(networks) {
|
|
868
|
+
this.sendMessage({
|
|
869
|
+
idx: -1,
|
|
870
|
+
type: EventType.NETWORKS,
|
|
871
|
+
data: networks
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
notifyWalletContext(walletContext) {
|
|
876
|
+
this.sendMessage({
|
|
877
|
+
idx: -1,
|
|
878
|
+
type: EventType.WALLET_CONTEXT,
|
|
879
|
+
data: walletContext
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
isValidInitAck(message) {
|
|
884
|
+
if (this._init === InitState.OK) {
|
|
885
|
+
// we're already in init state, we shouldn't handle this message
|
|
886
|
+
logger.warn("isValidInitAck, already in init'd state, so inquiry is invalid.");
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (message.type !== EventType.INIT) {
|
|
891
|
+
logger.warn('isValidInitAck, invalid message type, expecting init');
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const {
|
|
896
|
+
sessionId,
|
|
897
|
+
nonce
|
|
898
|
+
} = message.data;
|
|
899
|
+
|
|
900
|
+
if (!sessionId || sessionId.length === 0 || !nonce || nonce.length === 0) {
|
|
901
|
+
logger.error('invalid init ack');
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (sessionId !== this._sessionId || nonce !== this._initNonce) {
|
|
906
|
+
logger.error('invalid init ack match');
|
|
907
|
+
return false;
|
|
908
|
+
} // all checks pass, its true
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
init() {
|
|
915
|
+
return new Promise((resolve, reject) => {
|
|
916
|
+
// avoid re-init`ing, or if there is a transport which doesn't require
|
|
917
|
+
// it, then it may set this._init to OK in its constructor.
|
|
918
|
+
if (this._init === InitState.OK) {
|
|
919
|
+
resolve();
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (this._init !== InitState.NIL || this._initCallback) {
|
|
924
|
+
reject('transport init is in progress');
|
|
925
|
+
return;
|
|
926
|
+
} // start init timeout, if we don't receive confirmation
|
|
927
|
+
// from provider within this amount of time, then we timeout
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
const initTimeout = setTimeout(() => {
|
|
931
|
+
logger.warn('transport init timed out');
|
|
932
|
+
|
|
933
|
+
if (this._initCallback) {
|
|
934
|
+
this._initCallback('transport init timed out');
|
|
935
|
+
}
|
|
936
|
+
}, PROVIDER_OPEN_TIMEOUT / 2); // setup callback as we receive the init message async in the handleMessage function
|
|
937
|
+
|
|
938
|
+
this._initCallback = error => {
|
|
939
|
+
this._initCallback = undefined; // reset
|
|
940
|
+
|
|
941
|
+
clearTimeout(initTimeout);
|
|
942
|
+
|
|
943
|
+
if (error) {
|
|
944
|
+
reject(error);
|
|
945
|
+
} else {
|
|
946
|
+
this._init = InitState.OK;
|
|
947
|
+
resolve();
|
|
948
|
+
}
|
|
949
|
+
}; // send init request with random nonce to the provider, where we expect
|
|
950
|
+
// for the provider to echo it back to us as complete handshake
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
this._initNonce = `${performance.now()}`;
|
|
954
|
+
this.sendMessage({
|
|
955
|
+
idx: -1,
|
|
956
|
+
type: EventType.INIT,
|
|
957
|
+
data: {
|
|
958
|
+
nonce: this._initNonce
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
this._init = InitState.SENT_NONCE; // NOTE: the promise will resolve in the _initCallback method
|
|
962
|
+
// which will be called from either handleMessage or the initTimeout
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
class ProxyMessageChannel {
|
|
969
|
+
constructor() {
|
|
970
|
+
this.app = void 0;
|
|
971
|
+
this.wallet = void 0;
|
|
972
|
+
const port1 = new ProxyMessageChannelPort();
|
|
973
|
+
const port2 = new ProxyMessageChannelPort();
|
|
974
|
+
port1.conn = port2;
|
|
975
|
+
port2.conn = port1;
|
|
976
|
+
this.app = port1;
|
|
977
|
+
this.wallet = port2;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
}
|
|
981
|
+
class ProxyMessageChannelPort {
|
|
982
|
+
constructor() {
|
|
983
|
+
this.conn = void 0;
|
|
984
|
+
this.events = new EventEmitter2();
|
|
985
|
+
|
|
986
|
+
this.handleMessage = message => {
|
|
987
|
+
throw new Error('ProxyMessageChannelPort is not registered');
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
this.sendMessage = message => {
|
|
991
|
+
this.conn.handleMessage(message); // trigger events
|
|
992
|
+
|
|
993
|
+
if (message.type === 'open') {
|
|
994
|
+
this.events.emit('open', message);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (message.type === 'close') {
|
|
998
|
+
this.events.emit('close', message);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (message.type === 'connect') {
|
|
1002
|
+
this.events.emit('connect', message);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (message.type === 'disconnect') {
|
|
1006
|
+
this.events.emit('disconnect', message);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
on(event, fn) {
|
|
1012
|
+
this.events.on(event, fn);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
once(event, fn) {
|
|
1016
|
+
this.events.once(event, fn);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
class ProxyMessageProvider extends BaseProviderTransport {
|
|
1022
|
+
constructor(port) {
|
|
1023
|
+
super();
|
|
1024
|
+
this.port = void 0;
|
|
1025
|
+
|
|
1026
|
+
this.register = () => {
|
|
1027
|
+
this.port.handleMessage = message => {
|
|
1028
|
+
this.handleMessage(message);
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
this.on('open', (...args) => {
|
|
1032
|
+
this.port.events.emit('open', ...args);
|
|
1033
|
+
});
|
|
1034
|
+
this.on('close', (...args) => {
|
|
1035
|
+
this.port.events.emit('close', ...args);
|
|
1036
|
+
});
|
|
1037
|
+
this.on('connect', (...args) => {
|
|
1038
|
+
this.port.events.emit('connect', ...args);
|
|
1039
|
+
});
|
|
1040
|
+
this.on('disconnect', (...args) => {
|
|
1041
|
+
this.port.events.emit('disconnect', ...args);
|
|
1042
|
+
});
|
|
1043
|
+
this._registered = true;
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
this.unregister = () => {
|
|
1047
|
+
this._registered = false;
|
|
1048
|
+
this.closeWallet();
|
|
1049
|
+
this.events.removeAllListeners(); // @ts-ignore
|
|
1050
|
+
|
|
1051
|
+
this.port.handleMessage = undefined;
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
this.openWallet = (path, intent, networkId) => {
|
|
1055
|
+
if (this.state === OpenState.CLOSED) {
|
|
1056
|
+
this.state = OpenState.OPENING;
|
|
1057
|
+
const sessionId = `${performance.now()}`;
|
|
1058
|
+
this._sessionId = sessionId;
|
|
1059
|
+
this.sendMessage({
|
|
1060
|
+
idx: -1,
|
|
1061
|
+
type: EventType.OPEN,
|
|
1062
|
+
data: {
|
|
1063
|
+
path,
|
|
1064
|
+
intent,
|
|
1065
|
+
networkId,
|
|
1066
|
+
sessionId
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
this.state = OpenState.CLOSED;
|
|
1073
|
+
this.port = port;
|
|
1074
|
+
|
|
1075
|
+
if (!port) {
|
|
1076
|
+
throw new Error('port argument cannot be empty');
|
|
1077
|
+
} // disable init handshake for proxy-transport, we set it to OK, to
|
|
1078
|
+
// consider it in completed state.
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
this._init = InitState.OK;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
closeWallet() {
|
|
1085
|
+
this.sendMessage({
|
|
1086
|
+
idx: -1,
|
|
1087
|
+
type: EventType.CLOSE,
|
|
1088
|
+
data: null
|
|
1089
|
+
});
|
|
1090
|
+
this.close();
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
sendMessage(message) {
|
|
1094
|
+
if (!message.idx) {
|
|
1095
|
+
throw new Error('message idx is empty');
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
this.port.sendMessage(message);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
class ProxyMessageHandler extends BaseWalletTransport {
|
|
1104
|
+
constructor(walletRequestHandler, port) {
|
|
1105
|
+
super(walletRequestHandler);
|
|
1106
|
+
this.port = void 0;
|
|
1107
|
+
this.port = port;
|
|
1108
|
+
this._init = InitState.OK;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
register() {
|
|
1112
|
+
this.port.handleMessage = message => {
|
|
1113
|
+
this.handleMessage(message);
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
this._registered = true;
|
|
1117
|
+
} // note: we can't decide whether to restore the session within register(), because session info is
|
|
1118
|
+
// received asyncronously via EventType.OPEN after register() is executed.
|
|
1119
|
+
// And in the case of a redirect/reload, EventType.OPEN is not sent at all,
|
|
1120
|
+
// because the wallet is already open.
|
|
1121
|
+
//
|
|
1122
|
+
// call this method from wallet redirect hander when a session restore is needed
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
restoreSession() {
|
|
1126
|
+
const cachedSession = this.getCachedTransportSession();
|
|
1127
|
+
|
|
1128
|
+
if (cachedSession) {
|
|
1129
|
+
this.open(cachedSession);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
unregister() {
|
|
1134
|
+
// @ts-ignore
|
|
1135
|
+
this.port.handleMessage = undefined;
|
|
1136
|
+
this._registered = false;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
sendMessage(message) {
|
|
1140
|
+
this.port.sendMessage(message);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
class MuxMessageProvider {
|
|
1146
|
+
constructor(...messageProviders) {
|
|
1147
|
+
var _this = this;
|
|
1148
|
+
|
|
1149
|
+
this.messageProviders = void 0;
|
|
1150
|
+
this.provider = void 0;
|
|
1151
|
+
|
|
1152
|
+
this.register = () => {
|
|
1153
|
+
if (this.messageProviders.length === 1) {
|
|
1154
|
+
this.provider = this.messageProviders[0];
|
|
1155
|
+
this.provider.register();
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
this.messageProviders.forEach(m => {
|
|
1160
|
+
m.register();
|
|
1161
|
+
m.once('open', () => {
|
|
1162
|
+
// the first one to open is the winner, and others will be unregistered
|
|
1163
|
+
if (!this.provider) {
|
|
1164
|
+
this.provider = m; // unregister other providers
|
|
1165
|
+
|
|
1166
|
+
this.messageProviders.forEach(m => {
|
|
1167
|
+
if (this.provider !== m) {
|
|
1168
|
+
m.unregister();
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
this.unregister = () => {
|
|
1177
|
+
this.messageProviders.forEach(m => m.unregister());
|
|
1178
|
+
this.provider = undefined;
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
this.openWallet = (path, intent, networkId) => {
|
|
1182
|
+
if (this.provider) {
|
|
1183
|
+
this.provider.openWallet(path, intent, networkId);
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
this.messageProviders.forEach(m => m.openWallet(path, intent, networkId));
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
this.sendAsync = async function (request, callback, chainId) {
|
|
1191
|
+
if (_this.provider) {
|
|
1192
|
+
_this.provider.sendAsync(request, callback, chainId);
|
|
1193
|
+
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
throw new Error('impossible state, wallet must be opened first');
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
this.sendMessageRequest = async function (message) {
|
|
1201
|
+
if (_this.provider) {
|
|
1202
|
+
return _this.provider.sendMessageRequest(message);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
throw new Error('impossible state, wallet must be opened first');
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
this.waitUntilOpened = async function () {
|
|
1209
|
+
if (_this.provider) {
|
|
1210
|
+
return _this.provider.waitUntilOpened();
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
return Promise.race(_this.messageProviders.map(p => p.waitUntilOpened()));
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
this.waitUntilConnected = async function () {
|
|
1217
|
+
if (_this.provider) {
|
|
1218
|
+
return _this.provider.waitUntilConnected();
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
throw new Error('impossible state, wallet must be opened first');
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
this.messageProviders = messageProviders;
|
|
1225
|
+
this.provider = undefined;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
add(...messageProviders) {
|
|
1229
|
+
this.messageProviders.push(...messageProviders);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
closeWallet() {
|
|
1233
|
+
if (this.provider) {
|
|
1234
|
+
this.provider.closeWallet();
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
isOpened() {
|
|
1239
|
+
if (this.provider) {
|
|
1240
|
+
return this.provider.isOpened();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
return false;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
isConnected() {
|
|
1247
|
+
if (this.provider) {
|
|
1248
|
+
return this.provider.isConnected();
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
return false;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
on(event, fn) {
|
|
1255
|
+
if (this.provider) {
|
|
1256
|
+
this.provider.on(event, fn);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
this.messageProviders.forEach(m => {
|
|
1261
|
+
m.on(event, fn);
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
once(event, fn) {
|
|
1266
|
+
if (this.provider) {
|
|
1267
|
+
this.provider.once(event, fn);
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
this.messageProviders.forEach(m => {
|
|
1272
|
+
m.once(event, fn);
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
emit(event, ...args) {
|
|
1277
|
+
if (this.provider) {
|
|
1278
|
+
return this.provider.emit(event, ...args);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
for (let i = 0; i < this.messageProviders.length; i++) {
|
|
1282
|
+
this.messageProviders[i].emit(event, ...args);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
sendMessage(message) {
|
|
1289
|
+
if (!message.idx || message.idx <= 0) {
|
|
1290
|
+
throw new Error('message idx is empty');
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (this.provider) {
|
|
1294
|
+
this.provider.sendMessage(message);
|
|
1295
|
+
} else {
|
|
1296
|
+
throw new Error('impossible state, wallet must be opened first');
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
handleMessage(message) {
|
|
1301
|
+
if (this.provider) {
|
|
1302
|
+
this.provider.handleMessage(message);
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
throw new Error('impossible state, wallet must be opened first');
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
let registeredWindowMessageProvider;
|
|
1312
|
+
class WindowMessageProvider extends BaseProviderTransport {
|
|
1313
|
+
constructor(walletAppURL) {
|
|
1314
|
+
super();
|
|
1315
|
+
this.walletURL = void 0;
|
|
1316
|
+
this.walletWindow = void 0;
|
|
1317
|
+
|
|
1318
|
+
this.register = () => {
|
|
1319
|
+
if (registeredWindowMessageProvider) {
|
|
1320
|
+
throw new Error('A WindowMessageProvider is already registered. There can only be one.');
|
|
1321
|
+
} // listen for incoming messages from wallet
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
window.addEventListener('message', this.onWindowEvent);
|
|
1325
|
+
registeredWindowMessageProvider = this; // open heartbeat
|
|
1326
|
+
|
|
1327
|
+
this.on('open', () => {
|
|
1328
|
+
// Heartbeat to track if window closed
|
|
1329
|
+
const popup = this.walletWindow;
|
|
1330
|
+
const interval = setInterval(() => {
|
|
1331
|
+
if (popup && popup.closed) {
|
|
1332
|
+
clearInterval(interval);
|
|
1333
|
+
this.close();
|
|
1334
|
+
}
|
|
1335
|
+
}, 500);
|
|
1336
|
+
}); // close clean up
|
|
1337
|
+
|
|
1338
|
+
this.on('close', () => {
|
|
1339
|
+
if (this.walletWindow) {
|
|
1340
|
+
this.walletWindow.close();
|
|
1341
|
+
this.walletWindow = null;
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
this._registered = true;
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
this.unregister = () => {
|
|
1348
|
+
this._registered = false;
|
|
1349
|
+
this.closeWallet(); // disable message listener
|
|
1350
|
+
|
|
1351
|
+
if (registeredWindowMessageProvider === this) {
|
|
1352
|
+
registeredWindowMessageProvider = undefined;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
window.removeEventListener('message', this.onWindowEvent); // clear event listeners
|
|
1356
|
+
|
|
1357
|
+
this.events.removeAllListeners();
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
this.openWallet = (path, intent, networkId) => {
|
|
1361
|
+
if (this.walletWindow && this.isOpened()) {
|
|
1362
|
+
// TODO: update the location of window to path
|
|
1363
|
+
this.walletWindow.focus();
|
|
1364
|
+
return;
|
|
1365
|
+
} // Instantiate new walletURL for this call
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
const walletURL = new URL(this.walletURL.href);
|
|
1369
|
+
const windowSessionParams = new WindowSessionParams();
|
|
1370
|
+
|
|
1371
|
+
if (path && path !== '') {
|
|
1372
|
+
walletURL.pathname = path.toLowerCase();
|
|
1373
|
+
} // Set session, intent and network id on walletURL
|
|
1374
|
+
|
|
1375
|
+
|
|
1376
|
+
this._init = InitState.NIL;
|
|
1377
|
+
this._sessionId = `${performance.now()}`;
|
|
1378
|
+
windowSessionParams.set('sid', this._sessionId);
|
|
1379
|
+
|
|
1380
|
+
if (intent) {
|
|
1381
|
+
// for the window-transport, we eagerly/optimistically set the origin host
|
|
1382
|
+
// when connecting to the wallet, however, this will be verified and enforced
|
|
1383
|
+
// on the wallet-side, so if a dapp provides the wrong origin, it will be dropped.
|
|
1384
|
+
if (intent.type === 'connect') {
|
|
1385
|
+
if (!intent.options) intent.options = {}; // skip setting origin host if we're in an browser extension execution context
|
|
1386
|
+
// allow origin that is passed in
|
|
1387
|
+
|
|
1388
|
+
if (!isBrowserExtension()) {
|
|
1389
|
+
intent.options.origin = window.location.origin;
|
|
1390
|
+
}
|
|
1391
|
+
} // encode intent as base6 url-encoded param
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
windowSessionParams.set('intent', base64EncodeObject(intent));
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
if (networkId) {
|
|
1398
|
+
windowSessionParams.set('net', `${networkId}`);
|
|
1399
|
+
} // Open popup window on center of the app window
|
|
1400
|
+
|
|
1401
|
+
|
|
1402
|
+
const windowSize = [450, 750];
|
|
1403
|
+
const windowPos = [Math.abs(window.screenX + window.innerWidth / 2 - windowSize[0] / 2), Math.abs(window.screenY + window.innerHeight / 2 - windowSize[1] / 2)];
|
|
1404
|
+
const windowFeatures = `toolbar=0,location=0,menubar=0,scrollbars=yes,status=yes` + `,width=${windowSize[0]},height=${windowSize[1]}` + `,left=${windowPos[0]},top=${windowPos[1]}`; // serialize params
|
|
1405
|
+
|
|
1406
|
+
walletURL.search = windowSessionParams.toString();
|
|
1407
|
+
this.walletWindow = window.open(walletURL.href, 'sequence.app', windowFeatures); // TODO: move this somewhere else
|
|
1408
|
+
// TODO: perhaps we trigger a .on('openTimeout') event..? maybe.. could help.
|
|
1409
|
+
// Popup blocking detection and notice
|
|
1410
|
+
// let warned = false
|
|
1411
|
+
// const warnPopupBlocked = () => {
|
|
1412
|
+
// if (warned) return
|
|
1413
|
+
// warned = true
|
|
1414
|
+
// // alert('popup is blocked! hey yo') // NOTE: for debug purposes only
|
|
1415
|
+
// throw new Error('popup is blocked')
|
|
1416
|
+
// }
|
|
1417
|
+
// const popupCheck = setTimeout(() => {
|
|
1418
|
+
// if (!popup || popup.closed || typeof popup.closed === 'undefined') {
|
|
1419
|
+
// // popup is definitely blocked if we reach here.
|
|
1420
|
+
// warnPopupBlocked()
|
|
1421
|
+
// }
|
|
1422
|
+
// }, 1000)
|
|
1423
|
+
// const popupBlocked = popup === null || popup === undefined
|
|
1424
|
+
// if (popupBlocked) {
|
|
1425
|
+
// warnPopupBlocked()
|
|
1426
|
+
// return
|
|
1427
|
+
// }
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
this.onWindowEvent = event => {
|
|
1431
|
+
// Security check, ensure message is coming from wallet origin url
|
|
1432
|
+
if (event.origin !== this.walletURL.origin) {
|
|
1433
|
+
// Safetly can skip events not from the wallet
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
let message;
|
|
1438
|
+
|
|
1439
|
+
try {
|
|
1440
|
+
message = JSON.parse(event.data);
|
|
1441
|
+
} catch (err) {
|
|
1442
|
+
// event is not a ProviderMessage JSON object, skip
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (!message) {
|
|
1447
|
+
throw new Error('ProviderMessage object is empty');
|
|
1448
|
+
} // handle message with base message provider
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
this.handleMessage(message);
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
this.walletURL = new URL(walletAppURL);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
closeWallet() {
|
|
1458
|
+
var _this$walletWindow;
|
|
1459
|
+
|
|
1460
|
+
this.close();
|
|
1461
|
+
(_this$walletWindow = this.walletWindow) == null ? void 0 : _this$walletWindow.close();
|
|
1462
|
+
} // onmessage, receives ProviderMessageResponse from the wallet post-message transport
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
sendMessage(message) {
|
|
1466
|
+
if (!this.walletWindow) {
|
|
1467
|
+
logger.warn('WindowMessageProvider: sendMessage failed as walletWindow is unavailable');
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
const postedMessage = typeof message !== 'string' ? JSON.stringify(message) : message;
|
|
1472
|
+
this.walletWindow.postMessage(postedMessage, this.walletURL.origin);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
class WindowMessageHandler extends BaseWalletTransport {
|
|
1478
|
+
constructor(walletRequestHandler) {
|
|
1479
|
+
var _this;
|
|
1480
|
+
|
|
1481
|
+
super(walletRequestHandler);
|
|
1482
|
+
_this = this;
|
|
1483
|
+
this.parentWindow = void 0;
|
|
1484
|
+
this._isPopup = false;
|
|
1485
|
+
|
|
1486
|
+
this.onWindowEvent = async function (event) {
|
|
1487
|
+
if (!event.origin || event.origin === '') {
|
|
1488
|
+
// skip same-origin or when event.origin is empty/undefined
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
if (_this.appOrigin && event.origin !== _this.appOrigin) {
|
|
1493
|
+
// skip message as not from expected app origin
|
|
1494
|
+
return;
|
|
1495
|
+
} // Wallet always expects json-rpc request messages from a dapp
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
let request;
|
|
1499
|
+
|
|
1500
|
+
try {
|
|
1501
|
+
request = JSON.parse(event.data);
|
|
1502
|
+
} catch (err) {
|
|
1503
|
+
// event is not a ProviderMessage JSON object, skip
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
logger.debug('RECEIVED MESSAGE', request); // Record event origin for valid init ack
|
|
1508
|
+
|
|
1509
|
+
if (_this._init !== InitState.OK && _this.isValidInitAck(request)) {
|
|
1510
|
+
_this.appOrigin = event.origin;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
if (_this._init === InitState.OK && (!_this.appOrigin || _this.appOrigin.length < 8)) {
|
|
1514
|
+
// impossible state
|
|
1515
|
+
logger.error('impossible state, init.OK and appOrigin required');
|
|
1516
|
+
return;
|
|
1517
|
+
} // Handle message via the base transport
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
_this.handleMessage(request);
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
this.getWindowTransportSession = windowParams => {
|
|
1524
|
+
const params = new WindowSessionParams(windowParams);
|
|
1525
|
+
return {
|
|
1526
|
+
sessionId: params.get('sid'),
|
|
1527
|
+
networkId: params.get('net'),
|
|
1528
|
+
intent: base64DecodeObject(params.get('intent'))
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1532
|
+
this._init = InitState.NIL;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
register() {
|
|
1536
|
+
const isPopup = parent.window.opener !== null;
|
|
1537
|
+
this._isPopup = isPopup;
|
|
1538
|
+
|
|
1539
|
+
if (isPopup !== true) {
|
|
1540
|
+
return;
|
|
1541
|
+
} // record open details (sessionId + default network) from the window url
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
const {
|
|
1545
|
+
pathname,
|
|
1546
|
+
search: rawParams
|
|
1547
|
+
} = new URL(window.location.href);
|
|
1548
|
+
let session = this.getWindowTransportSession(rawParams); // provider should always include sid when opening a new window
|
|
1549
|
+
|
|
1550
|
+
const isNewWindowSession = !!session.sessionId; // attempt to restore previous session in the case of a redirect or window reload
|
|
1551
|
+
|
|
1552
|
+
if (!isNewWindowSession) {
|
|
1553
|
+
session = this.getCachedTransportSession();
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
if (!session) {
|
|
1557
|
+
logger.error('window session is undefined');
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
if (session.intent && isNewWindowSession) {
|
|
1562
|
+
window.history.replaceState({
|
|
1563
|
+
openWalletIntent: true
|
|
1564
|
+
}, document.title, pathname);
|
|
1565
|
+
} // record parent window instance for communication
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
this.parentWindow = parent.window.opener; // listen for window-transport requests
|
|
1569
|
+
|
|
1570
|
+
window.addEventListener('message', this.onWindowEvent, false);
|
|
1571
|
+
this._registered = true; // send open event to the app which opened us
|
|
1572
|
+
|
|
1573
|
+
this.open(session).then(opened => {
|
|
1574
|
+
if (!opened) {
|
|
1575
|
+
var _session;
|
|
1576
|
+
|
|
1577
|
+
const err = `failed to open to network ${(_session = session) == null ? void 0 : _session.networkId}`;
|
|
1578
|
+
logger.error(err);
|
|
1579
|
+
this.notifyClose({
|
|
1580
|
+
message: err
|
|
1581
|
+
});
|
|
1582
|
+
window.close();
|
|
1583
|
+
}
|
|
1584
|
+
}).catch(e => {
|
|
1585
|
+
var _session2;
|
|
1586
|
+
|
|
1587
|
+
const err = `failed to open to network ${(_session2 = session) == null ? void 0 : _session2.networkId}, due to: ${e}`;
|
|
1588
|
+
logger.error(err);
|
|
1589
|
+
this.notifyClose({
|
|
1590
|
+
message: err
|
|
1591
|
+
});
|
|
1592
|
+
window.close();
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
unregister() {
|
|
1597
|
+
window.removeEventListener('message', this.onWindowEvent);
|
|
1598
|
+
this._registered = false;
|
|
1599
|
+
} // onmessage is called when (the wallet) receives request messages from the dapp
|
|
1600
|
+
// over the window post-messaging transport
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
// postMessage sends message to the dapp window
|
|
1604
|
+
sendMessage(message) {
|
|
1605
|
+
// prepare payload
|
|
1606
|
+
const payload = JSON.stringify(message); // post-message to app.
|
|
1607
|
+
// only for init requests, we send to '*' origin
|
|
1608
|
+
|
|
1609
|
+
if (message.type === EventType.INIT) {
|
|
1610
|
+
this.postMessage(payload, true);
|
|
1611
|
+
} else {
|
|
1612
|
+
this.postMessage(payload);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
get isPopup() {
|
|
1617
|
+
return this._isPopup;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
postMessage(message, init = false) {
|
|
1621
|
+
if (init !== true && this._init !== InitState.OK) {
|
|
1622
|
+
logger.error('impossible state, should not be calling postMessage until inited');
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
if (init) {
|
|
1627
|
+
// init message transmission to global target -- for 'init' payloads only
|
|
1628
|
+
this.parentWindow.postMessage(message, '*');
|
|
1629
|
+
} else {
|
|
1630
|
+
// open message transmission
|
|
1631
|
+
if (this.appOrigin && this.appOrigin.length > 4) {
|
|
1632
|
+
// just above '.com'
|
|
1633
|
+
this.parentWindow.postMessage(message, this.appOrigin);
|
|
1634
|
+
} else {
|
|
1635
|
+
logger.error('unable to postMessage as parentOrigin is invalid');
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
class WalletRequestHandler {
|
|
1643
|
+
constructor(_signer, prompter, mainnetNetworks, testnetNetworks = []) {
|
|
1644
|
+
var _this = this;
|
|
1645
|
+
|
|
1646
|
+
this.signer = void 0;
|
|
1647
|
+
this.prompter = void 0;
|
|
1648
|
+
this.mainnetNetworks = void 0;
|
|
1649
|
+
this.testnetNetworks = void 0;
|
|
1650
|
+
this._connectOptions = void 0;
|
|
1651
|
+
this._defaultNetworkId = void 0;
|
|
1652
|
+
this._chainId = void 0;
|
|
1653
|
+
this.events = new EventEmitter2();
|
|
1654
|
+
|
|
1655
|
+
this.promptConnect = async function (options) {
|
|
1656
|
+
if (!options && !_this._connectOptions) {
|
|
1657
|
+
// this is an unexpected state and should not happen
|
|
1658
|
+
throw new Error('prompter connect options are empty');
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (!_this.prompter) {
|
|
1662
|
+
// if prompter is null, we'll auto connect
|
|
1663
|
+
return _this.connect(options);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
const promptConnectDetails = await _this.prompter.promptConnect(options || _this._connectOptions).catch(_ => {
|
|
1667
|
+
return {
|
|
1668
|
+
connected: false
|
|
1669
|
+
};
|
|
1670
|
+
});
|
|
1671
|
+
const connectDetails = promptConnectDetails;
|
|
1672
|
+
|
|
1673
|
+
if (connectDetails.connected && !connectDetails.session) {
|
|
1674
|
+
connectDetails.session = await _this.walletSession();
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
return promptConnectDetails;
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
this.sendAsync = async function (request, callback, chainId) {
|
|
1681
|
+
const response = {
|
|
1682
|
+
jsonrpc: '2.0',
|
|
1683
|
+
id: request.id,
|
|
1684
|
+
result: null
|
|
1685
|
+
};
|
|
1686
|
+
|
|
1687
|
+
try {
|
|
1688
|
+
// only allow public json rpc method to the provider when user is not logged in, aka signer is not set
|
|
1689
|
+
if ((!_this.signer || _this.signer === null) && !permittedJsonRpcMethods.includes(request.method)) {
|
|
1690
|
+
// throw new Error(`not logged in. ${request.method} is unavailable`)
|
|
1691
|
+
throw ErrSignedInRequired;
|
|
1692
|
+
} // wallet signer
|
|
1693
|
+
|
|
1694
|
+
|
|
1695
|
+
const signer = _this.signer;
|
|
1696
|
+
if (!signer) throw new Error('WalletRequestHandler: wallet signer is not configured'); // fetch the provider for the specific chain, or undefined will select defaultChain
|
|
1697
|
+
|
|
1698
|
+
const provider = await signer.getProvider(chainId);
|
|
1699
|
+
if (!provider) throw new Error(`WalletRequestHandler: wallet provider is not configured for chainId ${chainId}`);
|
|
1700
|
+
|
|
1701
|
+
switch (request.method) {
|
|
1702
|
+
case 'net_version':
|
|
1703
|
+
{
|
|
1704
|
+
const result = await provider.send('net_version', []);
|
|
1705
|
+
response.result = result;
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
case 'eth_chainId':
|
|
1710
|
+
{
|
|
1711
|
+
const result = await provider.send('eth_chainId', []);
|
|
1712
|
+
response.result = result;
|
|
1713
|
+
break;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
case 'eth_accounts':
|
|
1717
|
+
{
|
|
1718
|
+
const walletAddress = await signer.getAddress();
|
|
1719
|
+
response.result = [walletAddress];
|
|
1720
|
+
break;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
case 'eth_getBalance':
|
|
1724
|
+
{
|
|
1725
|
+
const [accountAddress, blockTag] = request.params;
|
|
1726
|
+
const walletBalance = await provider.getBalance(accountAddress, blockTag);
|
|
1727
|
+
response.result = walletBalance.toHexString();
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
case 'eth_sign':
|
|
1732
|
+
{
|
|
1733
|
+
// note: message from json-rpc input is in hex format
|
|
1734
|
+
const [signingAddress, message] = request.params;
|
|
1735
|
+
let sig = ''; // TODO:
|
|
1736
|
+
// if (process.env.TEST_MODE === 'true' && this.prompter === null) {
|
|
1737
|
+
|
|
1738
|
+
if (_this.prompter === null) {
|
|
1739
|
+
// prompter is null, so we'll sign from here
|
|
1740
|
+
sig = await signer.signMessage(ethers.utils.arrayify(message), chainId);
|
|
1741
|
+
} else {
|
|
1742
|
+
// prompt user to provide the response
|
|
1743
|
+
sig = await _this.prompter.promptSignMessage({
|
|
1744
|
+
chainId: chainId,
|
|
1745
|
+
message: message
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (sig && sig.length > 0) {
|
|
1750
|
+
response.result = sig;
|
|
1751
|
+
} else {
|
|
1752
|
+
// The user has declined the request when value is null
|
|
1753
|
+
throw new Error('declined by user');
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
break;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
case 'eth_signTypedData':
|
|
1760
|
+
case 'eth_signTypedData_v4':
|
|
1761
|
+
{
|
|
1762
|
+
// note: signingAddress from json-rpc input is in hex format, and typedDataObject
|
|
1763
|
+
// should be an object, but in some instances may be double string encoded
|
|
1764
|
+
const [signingAddress, typedDataObject] = request.params;
|
|
1765
|
+
let typedData = undefined;
|
|
1766
|
+
|
|
1767
|
+
if (typeof typedDataObject === 'string') {
|
|
1768
|
+
try {
|
|
1769
|
+
typedData = JSON.parse(typedDataObject);
|
|
1770
|
+
} catch (e) {}
|
|
1771
|
+
} else {
|
|
1772
|
+
typedData = typedDataObject;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
if (!typedData || !typedData.domain || !typedData.types || !typedData.message) {
|
|
1776
|
+
throw new Error('invalid typedData object');
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
let sig = '';
|
|
1780
|
+
|
|
1781
|
+
if (_this.prompter === null) {
|
|
1782
|
+
// prompter is null, so we'll sign from here
|
|
1783
|
+
sig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainId);
|
|
1784
|
+
} else {
|
|
1785
|
+
// prompt user to provide the response
|
|
1786
|
+
sig = await _this.prompter.promptSignMessage({
|
|
1787
|
+
chainId: chainId,
|
|
1788
|
+
typedData: typedData
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if (sig && sig.length > 0) {
|
|
1793
|
+
response.result = sig;
|
|
1794
|
+
} else {
|
|
1795
|
+
// The user has declined the request when value is null
|
|
1796
|
+
throw new Error('declined by user');
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
case 'eth_sendTransaction':
|
|
1803
|
+
{
|
|
1804
|
+
// https://eth.wiki/json-rpc/API#eth_sendtransaction
|
|
1805
|
+
const [transactionParams] = request.params;
|
|
1806
|
+
let txnHash = '';
|
|
1807
|
+
|
|
1808
|
+
if (_this.prompter === null) {
|
|
1809
|
+
// prompter is null, so we'll send from here
|
|
1810
|
+
const txnResponse = await signer.sendTransaction(transactionParams, chainId);
|
|
1811
|
+
txnHash = txnResponse.hash;
|
|
1812
|
+
} else {
|
|
1813
|
+
// prompt user to provide the response
|
|
1814
|
+
txnHash = await _this.prompter.promptSendTransaction(transactionParams, chainId);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
if (txnHash) {
|
|
1818
|
+
response.result = txnHash;
|
|
1819
|
+
} else {
|
|
1820
|
+
// The user has declined the request when value is null
|
|
1821
|
+
throw new Error('declined by user');
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
case 'eth_signTransaction':
|
|
1828
|
+
{
|
|
1829
|
+
// https://eth.wiki/json-rpc/API#eth_signTransaction
|
|
1830
|
+
const [transaction] = request.params;
|
|
1831
|
+
const sender = ethers.utils.getAddress(transaction.from);
|
|
1832
|
+
|
|
1833
|
+
if (sender !== (await signer.getAddress())) {
|
|
1834
|
+
throw new Error('sender address does not match wallet');
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
if (_this.prompter === null) {
|
|
1838
|
+
// The eth_signTransaction method expects a `string` return value we instead return a `SignedTransactions` object,
|
|
1839
|
+
// this can only be broadcasted using an RPC provider with support for signed Sequence transactions, like this one.
|
|
1840
|
+
//
|
|
1841
|
+
// TODO: verify serializing / transporting the SignedTransaction object works as expected, most likely however
|
|
1842
|
+
// we will want to resolveProperties the bignumber values to hex strings
|
|
1843
|
+
response.result = await signer.signTransactions(transaction, chainId);
|
|
1844
|
+
} else {
|
|
1845
|
+
response.result = await _this.prompter.promptSignTransaction(transaction, chainId);
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
break;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
case 'eth_sendRawTransaction':
|
|
1852
|
+
{
|
|
1853
|
+
// NOTE: we're not using a prompter here as the transaction is already signed
|
|
1854
|
+
// and would have prompted the user upon signing.
|
|
1855
|
+
// https://eth.wiki/json-rpc/API#eth_sendRawTransaction
|
|
1856
|
+
if (isSignedTransactions(request.params[0])) {
|
|
1857
|
+
const txChainId = BigNumber.from(request.params[0].chainId).toNumber();
|
|
1858
|
+
const tx = await (await signer.getRelayer(txChainId)).relay(request.params[0]);
|
|
1859
|
+
response.result = (await tx).hash;
|
|
1860
|
+
} else {
|
|
1861
|
+
const tx = await provider.sendTransaction(request.params[0]);
|
|
1862
|
+
response.result = tx.hash;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
break;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
case 'eth_getTransactionCount':
|
|
1869
|
+
{
|
|
1870
|
+
const address = ethers.utils.getAddress(request.params[0]);
|
|
1871
|
+
const tag = request.params[1];
|
|
1872
|
+
const walletAddress = ethers.utils.getAddress(await signer.getAddress());
|
|
1873
|
+
|
|
1874
|
+
if (address === walletAddress) {
|
|
1875
|
+
const count = await signer.getTransactionCount(tag);
|
|
1876
|
+
response.result = ethers.BigNumber.from(count).toHexString();
|
|
1877
|
+
} else {
|
|
1878
|
+
const count = await provider.getTransactionCount(address, tag);
|
|
1879
|
+
response.result = ethers.BigNumber.from(count).toHexString();
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
break;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
case 'eth_blockNumber':
|
|
1886
|
+
{
|
|
1887
|
+
response.result = await provider.getBlockNumber();
|
|
1888
|
+
break;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
case 'eth_getBlockByNumber':
|
|
1892
|
+
{
|
|
1893
|
+
response.result = await provider.getBlock(request.params[0]
|
|
1894
|
+
/* , jsonRpcRequest.params[1] */
|
|
1895
|
+
);
|
|
1896
|
+
break;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
case 'eth_getBlockByHash':
|
|
1900
|
+
{
|
|
1901
|
+
response.result = await provider.getBlock(request.params[0]
|
|
1902
|
+
/* , jsonRpcRequest.params[1] */
|
|
1903
|
+
);
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
case 'eth_getTransactionByHash':
|
|
1908
|
+
{
|
|
1909
|
+
response.result = await provider.getTransaction(request.params[0]);
|
|
1910
|
+
break;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
case 'eth_call':
|
|
1914
|
+
{
|
|
1915
|
+
const [transactionObject, blockTag] = request.params;
|
|
1916
|
+
response.result = await provider.call(transactionObject, blockTag);
|
|
1917
|
+
break;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
case 'eth_getCode':
|
|
1921
|
+
{
|
|
1922
|
+
const [contractAddress, blockTag] = request.params;
|
|
1923
|
+
response.result = await provider.getCode(contractAddress, blockTag);
|
|
1924
|
+
break;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
case 'eth_estimateGas':
|
|
1928
|
+
{
|
|
1929
|
+
const [transactionObject] = request.params;
|
|
1930
|
+
response.result = await provider.estimateGas(transactionObject);
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
case 'eth_gasPrice':
|
|
1935
|
+
{
|
|
1936
|
+
const gasPrice = await provider.getGasPrice();
|
|
1937
|
+
response.result = gasPrice.toHexString();
|
|
1938
|
+
break;
|
|
1939
|
+
}
|
|
1940
|
+
// smart wallet method
|
|
1941
|
+
|
|
1942
|
+
case 'sequence_getWalletContext':
|
|
1943
|
+
{
|
|
1944
|
+
response.result = await signer.getWalletContext();
|
|
1945
|
+
break;
|
|
1946
|
+
}
|
|
1947
|
+
// smart wallet method
|
|
1948
|
+
|
|
1949
|
+
case 'sequence_getWalletConfig':
|
|
1950
|
+
{
|
|
1951
|
+
const [_chainId] = request.params;
|
|
1952
|
+
response.result = await signer.getWalletConfig(_chainId);
|
|
1953
|
+
break;
|
|
1954
|
+
}
|
|
1955
|
+
// smart wallet method
|
|
1956
|
+
|
|
1957
|
+
case 'sequence_getWalletState':
|
|
1958
|
+
{
|
|
1959
|
+
const [_chainId2] = request.params;
|
|
1960
|
+
response.result = await signer.getWalletState(_chainId2);
|
|
1961
|
+
break;
|
|
1962
|
+
}
|
|
1963
|
+
// smart wallet method
|
|
1964
|
+
|
|
1965
|
+
case 'sequence_getNetworks':
|
|
1966
|
+
{
|
|
1967
|
+
// NOTE: must ensure that the response result below returns clean serialized data, which is to omit
|
|
1968
|
+
// the provider and relayer objects and only return the urls so can be reinstantiated on dapp side.
|
|
1969
|
+
// This is handled by this.getNetworks() but noted here for future readers.
|
|
1970
|
+
response.result = await _this.getNetworks(true);
|
|
1971
|
+
break;
|
|
1972
|
+
}
|
|
1973
|
+
// smart wallet method
|
|
1974
|
+
|
|
1975
|
+
case 'sequence_updateConfig':
|
|
1976
|
+
{
|
|
1977
|
+
throw new Error('sequence_updateConfig method is not allowed from a dapp'); // NOTE: method is disabled as we don't need a dapp to request to update a config.
|
|
1978
|
+
// However, if we ever want this, we can enable it but must also use the prompter
|
|
1979
|
+
// for confirmation.
|
|
1980
|
+
//
|
|
1981
|
+
// const [newConfig] = request.params
|
|
1982
|
+
// response.result = await signer.updateConfig(newConfig)
|
|
1983
|
+
|
|
1984
|
+
break;
|
|
1985
|
+
}
|
|
1986
|
+
// smart wallet method
|
|
1987
|
+
|
|
1988
|
+
case 'sequence_publishConfig':
|
|
1989
|
+
{
|
|
1990
|
+
throw new Error('sequence_publishConfig method is not allowed from a dapp');
|
|
1991
|
+
break;
|
|
1992
|
+
}
|
|
1993
|
+
// relayer method
|
|
1994
|
+
|
|
1995
|
+
case 'sequence_estimateGasLimits':
|
|
1996
|
+
{
|
|
1997
|
+
// TODO
|
|
1998
|
+
break;
|
|
1999
|
+
}
|
|
2000
|
+
// relayer method
|
|
2001
|
+
|
|
2002
|
+
case 'sequence_gasRefundOptions':
|
|
2003
|
+
{
|
|
2004
|
+
// TODO
|
|
2005
|
+
break;
|
|
2006
|
+
}
|
|
2007
|
+
// relayer method
|
|
2008
|
+
|
|
2009
|
+
case 'sequence_getNonce':
|
|
2010
|
+
{
|
|
2011
|
+
// TODO
|
|
2012
|
+
break;
|
|
2013
|
+
}
|
|
2014
|
+
// relayer method
|
|
2015
|
+
|
|
2016
|
+
case 'sequence_relay':
|
|
2017
|
+
{
|
|
2018
|
+
// TODO
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
2021
|
+
// set default network of wallet
|
|
2022
|
+
|
|
2023
|
+
case 'sequence_setDefaultNetwork':
|
|
2024
|
+
{
|
|
2025
|
+
const [defaultNetworkId] = request.params;
|
|
2026
|
+
|
|
2027
|
+
if (!defaultNetworkId) {
|
|
2028
|
+
throw new Error('invalid request, method argument defaultNetworkId cannot be empty');
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
const ok = await _this.setDefaultNetwork(defaultNetworkId);
|
|
2032
|
+
|
|
2033
|
+
if (!ok) {
|
|
2034
|
+
throw new Error(`unable to set default network ${defaultNetworkId}`);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
response.result = await _this.getNetworks(true);
|
|
2038
|
+
break;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
default:
|
|
2042
|
+
{
|
|
2043
|
+
// NOTE: provider here will be chain-bound if chainId is provided
|
|
2044
|
+
const providerResponse = await provider.send(request.method, request.params);
|
|
2045
|
+
response.result = providerResponse;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
logger.error(err); // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#rpc-errors
|
|
2050
|
+
|
|
2051
|
+
response.result = null;
|
|
2052
|
+
response.error = _extends({}, new Error(err), {
|
|
2053
|
+
code: 4001
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
callback(undefined, response);
|
|
2058
|
+
};
|
|
2059
|
+
|
|
2060
|
+
this.signer = _signer;
|
|
2061
|
+
this.prompter = prompter;
|
|
2062
|
+
this.mainnetNetworks = mainnetNetworks;
|
|
2063
|
+
this.testnetNetworks = testnetNetworks;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
async signIn(signer, options = {}) {
|
|
2067
|
+
this.signer = signer;
|
|
2068
|
+
const {
|
|
2069
|
+
connect,
|
|
2070
|
+
mainnetNetworks,
|
|
2071
|
+
testnetNetworks,
|
|
2072
|
+
defaultNetworkId
|
|
2073
|
+
} = options;
|
|
2074
|
+
|
|
2075
|
+
if (mainnetNetworks && mainnetNetworks.length > 0) {
|
|
2076
|
+
this.mainnetNetworks = mainnetNetworks;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
if (testnetNetworks && testnetNetworks.length > 0) {
|
|
2080
|
+
this.testnetNetworks = testnetNetworks;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
if ((!this.mainnetNetworks || this.mainnetNetworks.length === 0) && (!this.testnetNetworks || this.testnetNetworks.length === 0)) {
|
|
2084
|
+
throw new Error('signIn failed as network configuration is empty');
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
const networkId = defaultNetworkId || this._defaultNetworkId;
|
|
2088
|
+
|
|
2089
|
+
if (networkId) {
|
|
2090
|
+
if (!(await this.setDefaultNetwork(networkId, false))) {
|
|
2091
|
+
throw new Error(`WalletRequestHandler setup unable to set defaultNetworkId ${networkId}`);
|
|
2092
|
+
}
|
|
2093
|
+
} // Optionally, connect the dapp and wallet. In case connectOptions are provided, we will perform
|
|
2094
|
+
// necessary auth request, and then notify the dapp of the 'connect' details.
|
|
2095
|
+
//
|
|
2096
|
+
// NOTE: if a user is signing into a dapp from a fresh state, and and auth request is made
|
|
2097
|
+
// we don't trigger the promptConnect flow, as we consider the user just authenticated
|
|
2098
|
+
// for this dapp, so its safe to authorize in the connect() method without the prompt.
|
|
2099
|
+
//
|
|
2100
|
+
// NOTE: signIn can optionally connect and notify dapp at this time for new signIn flows
|
|
2101
|
+
|
|
2102
|
+
|
|
2103
|
+
if (connect) {
|
|
2104
|
+
const connectOptions = this._connectOptions;
|
|
2105
|
+
const connectDetails = await this.connect(connectOptions);
|
|
2106
|
+
this.notifyConnect(connectDetails);
|
|
2107
|
+
|
|
2108
|
+
if (!connectOptions || connectOptions.keepWalletOpened !== true) {
|
|
2109
|
+
this.notifyClose();
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
async connect(options) {
|
|
2115
|
+
if (!this.signer) {
|
|
2116
|
+
return {
|
|
2117
|
+
connected: false,
|
|
2118
|
+
chainId: '0x0',
|
|
2119
|
+
error: 'unable to connect without signed in account'
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
const connectDetails = {
|
|
2124
|
+
connected: true,
|
|
2125
|
+
chainId: ethers.utils.hexlify(await this.getChainId())
|
|
2126
|
+
};
|
|
2127
|
+
|
|
2128
|
+
if (options && options.authorize) {
|
|
2129
|
+
// Perform ethauth eip712 request and construct the ConnectDetails response
|
|
2130
|
+
// including the auth proof
|
|
2131
|
+
const authOptions = {
|
|
2132
|
+
app: options.app,
|
|
2133
|
+
origin: options.origin,
|
|
2134
|
+
expiry: options.expiry
|
|
2135
|
+
}; // if (typeof(options.authorize) === 'object') {
|
|
2136
|
+
// authOptions = { ...authOptions, ...options.authorize }
|
|
2137
|
+
// }
|
|
2138
|
+
|
|
2139
|
+
try {
|
|
2140
|
+
connectDetails.proof = await signAuthorization(this.signer, authOptions);
|
|
2141
|
+
} catch (err) {
|
|
2142
|
+
logger.warn(`connect, signAuthorization failed for options: ${JSON.stringify(options)}, due to: ${err.message}`);
|
|
2143
|
+
return {
|
|
2144
|
+
connected: false,
|
|
2145
|
+
chainId: '0x0',
|
|
2146
|
+
error: `signAuthorization failed: ${err.message}`
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
} // Build session response for connect details
|
|
2150
|
+
|
|
2151
|
+
|
|
2152
|
+
connectDetails.session = await this.walletSession();
|
|
2153
|
+
return connectDetails;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// sendMessageRequest will unwrap the ProviderMessageRequest and send it to the JsonRpcHandler
|
|
2157
|
+
// (aka, the signer in this instance) and then responds with a wrapped response of
|
|
2158
|
+
// ProviderMessageResponse to be sent over the transport
|
|
2159
|
+
sendMessageRequest(message) {
|
|
2160
|
+
return new Promise(resolve => {
|
|
2161
|
+
this.sendAsync(message.data, (error, response) => {
|
|
2162
|
+
// TODO: if response includes data.error, why do we need a separate error argument here?
|
|
2163
|
+
const responseMessage = _extends({}, message, {
|
|
2164
|
+
data: response
|
|
2165
|
+
}); // NOTE: we always resolve here, are the sendAsync call will wrap any exceptions
|
|
2166
|
+
// in the error field of the response to ensure we send back to the user
|
|
2167
|
+
|
|
2168
|
+
|
|
2169
|
+
resolve(responseMessage);
|
|
2170
|
+
}, message.chainId);
|
|
2171
|
+
});
|
|
2172
|
+
} // sendAsync implements the JsonRpcHandler interface for sending JsonRpcRequests to the wallet
|
|
2173
|
+
|
|
2174
|
+
|
|
2175
|
+
on(event, fn) {
|
|
2176
|
+
this.events.on(event, fn);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
once(event, fn) {
|
|
2180
|
+
this.events.once(event, fn);
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
async getAddress() {
|
|
2184
|
+
if (!this.signer) {
|
|
2185
|
+
return '';
|
|
2186
|
+
} else {
|
|
2187
|
+
return this.signer.getAddress();
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
async getChainId() {
|
|
2192
|
+
if (!this.signer) {
|
|
2193
|
+
return 0;
|
|
2194
|
+
} else {
|
|
2195
|
+
if (this._chainId) return this._chainId; // memoized
|
|
2196
|
+
|
|
2197
|
+
this._chainId = await this.signer.getChainId();
|
|
2198
|
+
return this._chainId;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
get connectOptions() {
|
|
2203
|
+
return this._connectOptions;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
setConnectOptions(options) {
|
|
2207
|
+
this._connectOptions = options;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
get defaultNetworkId() {
|
|
2211
|
+
return this._defaultNetworkId;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
async setDefaultNetwork(chainId, notifyNetworks = true) {
|
|
2215
|
+
if (!chainId) return undefined;
|
|
2216
|
+
this._defaultNetworkId = chainId;
|
|
2217
|
+
this._chainId = undefined;
|
|
2218
|
+
|
|
2219
|
+
if (this.signer && this.signer.setNetworks) {
|
|
2220
|
+
const defaultChainId = this.signer.setNetworks(this.mainnetNetworks, this.testnetNetworks, chainId);
|
|
2221
|
+
|
|
2222
|
+
if (defaultChainId && notifyNetworks) {
|
|
2223
|
+
await this.notifyNetworks();
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
return defaultChainId;
|
|
2227
|
+
} else {
|
|
2228
|
+
return undefined;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
async getNetworks(jsonRpcResponse) {
|
|
2233
|
+
if (!this.signer) {
|
|
2234
|
+
logger.warn('signer not set: getNetworks is returning an empty list');
|
|
2235
|
+
return [];
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
const networks = await this.signer.getNetworks();
|
|
2239
|
+
|
|
2240
|
+
if (jsonRpcResponse) {
|
|
2241
|
+
// omit provider and relayer objects as they are not serializable
|
|
2242
|
+
return networks.map(n => {
|
|
2243
|
+
const network = _extends({}, n);
|
|
2244
|
+
|
|
2245
|
+
network.provider = undefined;
|
|
2246
|
+
network.relayer = undefined;
|
|
2247
|
+
return network;
|
|
2248
|
+
});
|
|
2249
|
+
} else {
|
|
2250
|
+
return networks;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
async walletSession() {
|
|
2255
|
+
return !this.signer ? undefined : {
|
|
2256
|
+
walletContext: await this.signer.getWalletContext(),
|
|
2257
|
+
accountAddress: await this.signer.getAddress(),
|
|
2258
|
+
networks: await this.getNetworks(true)
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
notifyConnect(connectDetails) {
|
|
2263
|
+
var _connectDetails$sessi;
|
|
2264
|
+
|
|
2265
|
+
this.events.emit('connect', connectDetails);
|
|
2266
|
+
|
|
2267
|
+
if ((_connectDetails$sessi = connectDetails.session) != null && _connectDetails$sessi.accountAddress) {
|
|
2268
|
+
var _connectDetails$sessi2;
|
|
2269
|
+
|
|
2270
|
+
this.events.emit('accountsChanged', [(_connectDetails$sessi2 = connectDetails.session) == null ? void 0 : _connectDetails$sessi2.accountAddress]);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
notifyDisconnect() {
|
|
2275
|
+
this.events.emit('accountsChanged', []);
|
|
2276
|
+
this.events.emit('networks', []);
|
|
2277
|
+
this.events.emit('disconnect');
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
async notifyNetworks(networks) {
|
|
2281
|
+
const n = networks || (await this.getNetworks(true));
|
|
2282
|
+
this.events.emit('networks', n);
|
|
2283
|
+
|
|
2284
|
+
if (n && n.length > 0) {
|
|
2285
|
+
this.events.emit('chainChanged', ethers.utils.hexlify(n[0].chainId));
|
|
2286
|
+
} else {
|
|
2287
|
+
this.events.emit('chainChanged', '0x0');
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
async notifyWalletContext() {
|
|
2292
|
+
if (!this.signer) {
|
|
2293
|
+
logger.warn('signer not set: skipping to notify wallet context');
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
const walletContext = await this.signer.getWalletContext();
|
|
2298
|
+
this.events.emit('walletContext', walletContext);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
notifyClose(error) {
|
|
2302
|
+
this.events.emit('close', error);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
isSignedIn() {
|
|
2306
|
+
return !!this.signer;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
getSigner() {
|
|
2310
|
+
return this.signer;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
setSigner(signer) {
|
|
2314
|
+
this.signer = signer;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
}
|
|
2318
|
+
const permittedJsonRpcMethods = ['net_version', 'eth_chainId', 'eth_getBalance', 'eth_getTransactionCount', 'eth_blockNumber', 'eth_getBlockByNumber', 'eth_getBlockByHash', 'eth_getTransactionByHash', 'eth_getCode', 'eth_estimateGas', 'eth_gasPrice', 'sequence_getWalletContext', 'sequence_getNetworks', 'sequence_setDefaultNetwork'];
|
|
2319
|
+
|
|
2320
|
+
const CHANNEL_ID = 'sequence-extension-message-handler';
|
|
2321
|
+
class ExtensionMessageHandler extends BaseWalletTransport {
|
|
2322
|
+
constructor(walletRequestHandler, runtime) {
|
|
2323
|
+
super(walletRequestHandler);
|
|
2324
|
+
this.runtime = runtime;
|
|
2325
|
+
this.port = void 0;
|
|
2326
|
+
this._init = InitState.OK;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
register() {
|
|
2330
|
+
this._registered = true;
|
|
2331
|
+
this.port = this.runtime.connect({
|
|
2332
|
+
name: CHANNEL_ID
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
sendMessage(message) {
|
|
2337
|
+
console.log('[ExtensionMessageHandler send]', message);
|
|
2338
|
+
this.port.postMessage(message);
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
class ExtensionMessageProvider extends BaseProviderTransport {
|
|
2344
|
+
constructor(runtime) {
|
|
2345
|
+
super();
|
|
2346
|
+
|
|
2347
|
+
this.register = () => {
|
|
2348
|
+
this._registered = true;
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
runtime.onConnect.addListener(port => {
|
|
2352
|
+
if (port.name === CHANNEL_ID) {
|
|
2353
|
+
this._init = InitState.OK;
|
|
2354
|
+
port.onMessage.addListener(message => {
|
|
2355
|
+
this.handleMessage(message);
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
sendMessage(message) {//noop
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
unregister() {//noop
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
openWallet(path, intent, networkId) {//noop
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
closeWallet() {//noop
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// to be used on injected window.ethereum EIP1193 proxy
|
|
2376
|
+
class BaseInjectedTransport extends EventEmitter2 {
|
|
2377
|
+
constructor(stream) {
|
|
2378
|
+
var _this;
|
|
2379
|
+
|
|
2380
|
+
super();
|
|
2381
|
+
_this = this;
|
|
2382
|
+
this.stream = stream;
|
|
2383
|
+
this.responseCallbacks = new Map();
|
|
2384
|
+
this._messageIdx = 0;
|
|
2385
|
+
|
|
2386
|
+
this.nextMessageIdx = () => ++this._messageIdx;
|
|
2387
|
+
|
|
2388
|
+
this.handleMessage = message => {
|
|
2389
|
+
if (!message.type || !message.data) {
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
console.log('[received message]', message);
|
|
2394
|
+
const requestIdx = message.idx;
|
|
2395
|
+
const responseCallback = this.responseCallbacks.get(requestIdx);
|
|
2396
|
+
|
|
2397
|
+
if (requestIdx) {
|
|
2398
|
+
this.responseCallbacks.delete(requestIdx);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
switch (message.type) {
|
|
2402
|
+
case EventType.MESSAGE:
|
|
2403
|
+
if (responseCallback) {
|
|
2404
|
+
this.emit(EventType.MESSAGE, message);
|
|
2405
|
+
responseCallback(message.data.error, message);
|
|
2406
|
+
} else {
|
|
2407
|
+
// NOTE: this would occur if 'idx' isn't set, which should never happen
|
|
2408
|
+
// or when we register two handler, or duplicate messages with the same idx are sent,
|
|
2409
|
+
// all of which should be prevented prior to getting to this point
|
|
2410
|
+
throw new Error('impossible state');
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
break;
|
|
2414
|
+
|
|
2415
|
+
case EventType.DISCONNECT:
|
|
2416
|
+
case EventType.ACCOUNTS_CHANGED:
|
|
2417
|
+
case EventType.CHAIN_CHANGED:
|
|
2418
|
+
this.emit(message.type, message.data);
|
|
2419
|
+
break;
|
|
2420
|
+
|
|
2421
|
+
default:
|
|
2422
|
+
console.error('unknown message type', message);
|
|
2423
|
+
break;
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
|
|
2427
|
+
this.sendMessageRequest = async function (message) {
|
|
2428
|
+
return new Promise((resolve, reject) => {
|
|
2429
|
+
if (!message.idx || message.idx <= 0) {
|
|
2430
|
+
reject(new Error('message idx not set'));
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
const responseCallback = (error, response) => {
|
|
2434
|
+
if (error) {
|
|
2435
|
+
reject(error);
|
|
2436
|
+
} else if (response) {
|
|
2437
|
+
resolve(response);
|
|
2438
|
+
} else {
|
|
2439
|
+
throw new Error('no valid response to return');
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
|
|
2443
|
+
const {
|
|
2444
|
+
idx
|
|
2445
|
+
} = message;
|
|
2446
|
+
|
|
2447
|
+
if (!_this.responseCallbacks.get(idx)) {
|
|
2448
|
+
_this.responseCallbacks.set(idx, responseCallback);
|
|
2449
|
+
} else {
|
|
2450
|
+
reject(new Error('duplicate message idx, should never happen'));
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
_this.sendMessage(message);
|
|
2454
|
+
});
|
|
2455
|
+
};
|
|
2456
|
+
|
|
2457
|
+
this.stream.on('data', this.handleMessage);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
sendMessage(message) {
|
|
2461
|
+
if (!this.stream.writable) {
|
|
2462
|
+
console.error('window post message stream is not writable');
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
this.stream.write(message);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
class Web3Provider extends Web3Provider$1 {
|
|
2471
|
+
static isSequenceProvider(cand) {
|
|
2472
|
+
return isSequenceProvider(cand);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
constructor(provider, defaultChainId) {
|
|
2476
|
+
const sender = new JsonRpcSender(provider, maybeChainId(defaultChainId));
|
|
2477
|
+
provider = sender;
|
|
2478
|
+
super(provider, 'any');
|
|
2479
|
+
this._sender = void 0;
|
|
2480
|
+
this._isSequenceProvider = void 0;
|
|
2481
|
+
this._defaultChainId = void 0;
|
|
2482
|
+
this._sender = sender;
|
|
2483
|
+
this._isSequenceProvider = true;
|
|
2484
|
+
this._defaultChainId = maybeChainId(defaultChainId);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
sendAsync(request, callback, chainId) {
|
|
2488
|
+
this._sender.sendAsync(request, callback, chainId);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
send(method, params, chainId) {
|
|
2492
|
+
return this._sender.send(method, params, chainId);
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
getSigner() {
|
|
2496
|
+
return new Web3Signer(this, this._defaultChainId);
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
async getChainId() {
|
|
2500
|
+
const result = await this.send('eth_chainId', []);
|
|
2501
|
+
const chainId = ethers.BigNumber.from(result).toNumber();
|
|
2502
|
+
|
|
2503
|
+
if (this._defaultChainId && this._defaultChainId !== chainId) {
|
|
2504
|
+
throw new Error(`provider chainId (${chainId}) does not match provider-bound chainId ${this._defaultChainId}`);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
return chainId;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
}
|
|
2511
|
+
function isSequenceProvider(provider) {
|
|
2512
|
+
const cand = provider;
|
|
2513
|
+
return cand && cand.send !== undefined && cand._isSequenceProvider === true;
|
|
2514
|
+
}
|
|
2515
|
+
class LocalWeb3Provider extends Web3Provider {
|
|
2516
|
+
constructor(signer, networks) {
|
|
2517
|
+
const walletRequestHandler = new WalletRequestHandler(signer, null, networks || []);
|
|
2518
|
+
super(walletRequestHandler);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
}
|
|
2522
|
+
class Web3Signer extends Signer {
|
|
2523
|
+
constructor(provider, defaultChainId) {
|
|
2524
|
+
super();
|
|
2525
|
+
this.provider = void 0;
|
|
2526
|
+
this.defaultChainId = void 0;
|
|
2527
|
+
this._address = void 0;
|
|
2528
|
+
this._index = void 0;
|
|
2529
|
+
this._context = void 0;
|
|
2530
|
+
this._networks = void 0;
|
|
2531
|
+
this._providers = {};
|
|
2532
|
+
this.provider = provider;
|
|
2533
|
+
this.defaultChainId = defaultChainId;
|
|
2534
|
+
} // memoized
|
|
2535
|
+
|
|
2536
|
+
|
|
2537
|
+
//
|
|
2538
|
+
// ethers AbstractSigner methods
|
|
2539
|
+
//
|
|
2540
|
+
async getAddress() {
|
|
2541
|
+
if (this._address) return this._address;
|
|
2542
|
+
const accounts = await this.provider.send('eth_accounts', []);
|
|
2543
|
+
this._address = accounts[0];
|
|
2544
|
+
this._index = 0;
|
|
2545
|
+
return ethers.utils.getAddress(this._address);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
signTransaction(transaction) {
|
|
2549
|
+
// TODO .. since ethers isn't using this method, perhaps we will?
|
|
2550
|
+
throw new Error('signTransaction is unsupported, use signTransactions instead');
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
connect(provider) {
|
|
2554
|
+
throw new Error('unsupported: cannot alter JSON-RPC Signer connection');
|
|
2555
|
+
} //
|
|
2556
|
+
// Sequence Signer methods
|
|
2557
|
+
//
|
|
2558
|
+
// getProvider returns a Web3Provider instance for the current chain. Note that this method
|
|
2559
|
+
// and signer is bound to a particular chain to prevent misuse. If you'd like a provider
|
|
2560
|
+
// for a specific chain, try getSender(chainId), or wallet.getProvider(chainId).
|
|
2561
|
+
|
|
2562
|
+
|
|
2563
|
+
async getProvider(chainId) {
|
|
2564
|
+
if (chainId) {
|
|
2565
|
+
const currentChainId = await this.getChainId();
|
|
2566
|
+
|
|
2567
|
+
if (currentChainId !== chainId) {
|
|
2568
|
+
throw new Error(`signer is attempting to access chain ${chainId}, but is already bound to chain ${currentChainId}`);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
return this.provider;
|
|
2573
|
+
} // getSender returns a Web3Provider instance via the signer transport. Note: for our case
|
|
2574
|
+
// the of sequence wallet, this will bring up the wallet window whenever using it, as the json-rpc
|
|
2575
|
+
// requests are sent to the window transport. Therefore, for anything non-signing related
|
|
2576
|
+
// you can write a higher-order JsonRpcRouter sender to route to the public provider endpoints
|
|
2577
|
+
// as we do in the WalletProvider.
|
|
2578
|
+
//
|
|
2579
|
+
// This method is primarily utilized internally when routing requests to a particular chainId.
|
|
2580
|
+
|
|
2581
|
+
|
|
2582
|
+
async getSender(chainId) {
|
|
2583
|
+
if (!chainId || chainId && chainId === this.defaultChainId) {
|
|
2584
|
+
return this.provider;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
if (!this._providers[chainId]) {
|
|
2588
|
+
this._providers[chainId] = new Web3Provider(new JsonRpcSender(this.provider, chainId), chainId);
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
return this._providers[chainId];
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
getRelayer(chainId) {
|
|
2595
|
+
// TODO: JsonRpcRelayer ......? or, Web3Relayer.. or SequenceRelayer?
|
|
2596
|
+
// sequence_estimateGasLimits
|
|
2597
|
+
// sequence_gasRefundOptions
|
|
2598
|
+
// sequence_getNonce
|
|
2599
|
+
// sequence_relay
|
|
2600
|
+
throw new Error('TODO');
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
async getWalletContext() {
|
|
2604
|
+
if (!this._context) {
|
|
2605
|
+
this._context = await this.provider.send('sequence_getWalletContext', []);
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
return this._context;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
async getWalletConfig(chainId) {
|
|
2612
|
+
return await this.provider.send('sequence_getWalletConfig', [maybeChainId(chainId)], maybeChainId(chainId) || this.defaultChainId);
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
async getWalletState(chainId) {
|
|
2616
|
+
return await this.provider.send('sequence_getWalletState', [maybeChainId(chainId)], maybeChainId(chainId) || this.defaultChainId);
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
async getNetworks() {
|
|
2620
|
+
if (!this._networks) {
|
|
2621
|
+
this._networks = await this.provider.send('sequence_getNetworks', []);
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
return this._networks;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
async getSigners() {
|
|
2628
|
+
const networks = await this.getNetworks();
|
|
2629
|
+
const authChainId = networks.find(n => n.isAuthChain);
|
|
2630
|
+
|
|
2631
|
+
if (!authChainId) {
|
|
2632
|
+
throw new Error('authChainId could not be determined from network list');
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
const walletConfig = await this.getWalletConfig(authChainId);
|
|
2636
|
+
|
|
2637
|
+
if (!walletConfig || walletConfig.length === 0) {
|
|
2638
|
+
throw new Error(`walletConfig returned zero results for authChainId {authChainId}`);
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
return walletConfig[0].signers.map(s => s.address);
|
|
2642
|
+
} // signMessage matches implementation from ethers JsonRpcSigner for compatibility, but with
|
|
2643
|
+
// multi-chain support.
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
async signMessage(message, chainId, allSigners) {
|
|
2647
|
+
const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId);
|
|
2648
|
+
const data = typeof message === 'string' ? ethers.utils.toUtf8Bytes(message) : message;
|
|
2649
|
+
const address = await this.getAddress(); // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
|
|
2650
|
+
|
|
2651
|
+
return await provider.send('eth_sign', [address, ethers.utils.hexlify(data)]);
|
|
2652
|
+
} // signTypedData matches implementation from ethers JsonRpcSigner for compatibility, but with
|
|
2653
|
+
// multi-chain support.
|
|
2654
|
+
|
|
2655
|
+
|
|
2656
|
+
async signTypedData(domain, types, message, chainId, allSigners) {
|
|
2657
|
+
// Populate any ENS names (in-place)
|
|
2658
|
+
// const populated = await ethers.utils._TypedDataEncoder.resolveNames(domain, types, message, (name: string) => {
|
|
2659
|
+
// return this.provider.resolveName(name)
|
|
2660
|
+
// })
|
|
2661
|
+
return await this.provider.send('eth_signTypedData_v4', [await this.getAddress(), ethers.utils._TypedDataEncoder.getPayload(domain, types, message)], maybeChainId(chainId) || this.defaultChainId);
|
|
2662
|
+
} // sendTransaction matches implementation from ethers JsonRpcSigner for compatibility, but with
|
|
2663
|
+
// multi-chain support.
|
|
2664
|
+
|
|
2665
|
+
|
|
2666
|
+
async sendTransaction(transaction, chainId, allSigners) {
|
|
2667
|
+
const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId);
|
|
2668
|
+
const tx = this.sendUncheckedTransaction(transaction, chainId).then(hash => {
|
|
2669
|
+
return ethers.utils.poll(() => {
|
|
2670
|
+
return provider.getTransaction(hash).then(tx => {
|
|
2671
|
+
if (tx === null) {
|
|
2672
|
+
return undefined;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
return provider._wrapTransaction(tx, hash);
|
|
2676
|
+
});
|
|
2677
|
+
}, {
|
|
2678
|
+
onceBlock: this.provider
|
|
2679
|
+
}).catch(error => {
|
|
2680
|
+
error.transactionHash = hash;
|
|
2681
|
+
throw error;
|
|
2682
|
+
});
|
|
2683
|
+
}); // @ts-ignore
|
|
2684
|
+
|
|
2685
|
+
return tx;
|
|
2686
|
+
} // sendTransactionBatch is a convenience method to call sendTransaction in a batch format, allowing you to
|
|
2687
|
+
// send multiple transaction as a single payload and just one on-chain transaction.
|
|
2688
|
+
|
|
2689
|
+
|
|
2690
|
+
async sendTransactionBatch(transactions, chainId, allSigners) {
|
|
2691
|
+
const batch = await resolveArrayProperties(transactions);
|
|
2692
|
+
|
|
2693
|
+
if (!batch || batch.length === 0) {
|
|
2694
|
+
throw new Error('cannot send empty batch');
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
const tx = _extends({}, batch[0]);
|
|
2698
|
+
|
|
2699
|
+
if (batch.length > 1) {
|
|
2700
|
+
tx.auxiliary = batch.splice(1);
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
return this.sendTransaction(tx, chainId, allSigners);
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
signTransactions(transaction, chainId, allSigners) {
|
|
2707
|
+
transaction = shallowCopy(transaction); // TODO: transaction argument..? make sure to resolve any properties and serialize property before sending over
|
|
2708
|
+
// the wire.. see sendUncheckedTransaction and resolveProperties
|
|
2709
|
+
|
|
2710
|
+
return this.provider.send('eth_signTransaction', [transaction], maybeChainId(chainId) || this.defaultChainId);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
sendSignedTransactions(signedTxs, chainId) {
|
|
2714
|
+
// sequence_relay
|
|
2715
|
+
throw new Error('TODO');
|
|
2716
|
+
} // updateConfig..
|
|
2717
|
+
// NOTE: this is not supported by the remote wallet by default.
|
|
2718
|
+
|
|
2719
|
+
|
|
2720
|
+
async updateConfig(newConfig) {
|
|
2721
|
+
// sequence_updateConfig
|
|
2722
|
+
const [config, tx] = await this.provider.send('sequence_updateConfig', [newConfig], this.defaultChainId);
|
|
2723
|
+
|
|
2724
|
+
if (tx === null) {
|
|
2725
|
+
return [config, undefined];
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
const provider = await this.getSender(this.defaultChainId);
|
|
2729
|
+
return [config, provider._wrapTransaction(tx, tx.hash)];
|
|
2730
|
+
} // publishConfig..
|
|
2731
|
+
// NOTE: this is not supported by the remote wallet by default.
|
|
2732
|
+
|
|
2733
|
+
|
|
2734
|
+
async publishConfig() {
|
|
2735
|
+
const provider = await this.getSender(this.defaultChainId);
|
|
2736
|
+
const tx = await provider.send('sequence_publishConfig', []);
|
|
2737
|
+
|
|
2738
|
+
if (tx === null) {
|
|
2739
|
+
return undefined;
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
return provider._wrapTransaction(tx, tx.hash);
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
async isDeployed(chainId) {
|
|
2746
|
+
const provider = await this.getSender(maybeChainId(chainId));
|
|
2747
|
+
const walletCode = await provider.getCode(await this.getAddress());
|
|
2748
|
+
return !!walletCode && walletCode !== '0x';
|
|
2749
|
+
} //
|
|
2750
|
+
// ethers JsonRpcSigner methods
|
|
2751
|
+
//
|
|
2752
|
+
|
|
2753
|
+
|
|
2754
|
+
async _signTypedData(domain, types, message, chainId, allSigners) {
|
|
2755
|
+
return this.signTypedData(domain, types, message, chainId, allSigners);
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
async sendUncheckedTransaction(transaction, chainId) {
|
|
2759
|
+
transaction = shallowCopy(transaction);
|
|
2760
|
+
const fromAddress = this.getAddress(); // NOTE: we do not use provider estimation, and instead rely on our relayer to determine the gasLimit and gasPrice
|
|
2761
|
+
//
|
|
2762
|
+
// TODO: alternatively/one day, we could write a provider middleware to eth_estimateGas
|
|
2763
|
+
// and send it to our relayer url instead for estimation..
|
|
2764
|
+
//
|
|
2765
|
+
// if (!transaction.gasLimit) {
|
|
2766
|
+
// const estimate = shallowCopy(transaction)
|
|
2767
|
+
// estimate.from = fromAddress
|
|
2768
|
+
// transaction.gasLimit = this.provider.estimateGas(estimate)
|
|
2769
|
+
// }
|
|
2770
|
+
|
|
2771
|
+
const provider = await this.getSender(maybeChainId(chainId) || this.defaultChainId);
|
|
2772
|
+
return resolveProperties({
|
|
2773
|
+
tx: resolveProperties(transaction),
|
|
2774
|
+
sender: await fromAddress
|
|
2775
|
+
}).then(({
|
|
2776
|
+
tx,
|
|
2777
|
+
sender
|
|
2778
|
+
}) => {
|
|
2779
|
+
if (tx.from != null) {
|
|
2780
|
+
if (ethers.utils.getAddress(tx.from) !== sender) {
|
|
2781
|
+
// logger.throwArgumentError("from address mismatch", "transaction", transaction)
|
|
2782
|
+
throw new Error(`from address mismatch for transaction ${transaction}`);
|
|
2783
|
+
}
|
|
2784
|
+
} else {
|
|
2785
|
+
tx.from = sender;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
const hexTx = hexlifyTransaction(tx);
|
|
2789
|
+
return provider.send('eth_sendTransaction', [hexTx]).then(hash => {
|
|
2790
|
+
return hash;
|
|
2791
|
+
}, error => {
|
|
2792
|
+
// return checkError("sendTransaction", error, hexTx)
|
|
2793
|
+
throw new Error(`sendTransaction ${error}`);
|
|
2794
|
+
});
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
connectUnchecked() {
|
|
2799
|
+
throw new Error('connectUnchecked is unsupported');
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
async unlock(password) {
|
|
2803
|
+
const address = await this.getAddress();
|
|
2804
|
+
return this.provider.send('personal_unlockAccount', [address, password, null]);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
} // NOTE: method has been copied + modified from ethers.js JsonRpcProvider
|
|
2808
|
+
// Convert an ethers.js transaction into a JSON-RPC transaction
|
|
2809
|
+
|
|
2810
|
+
const allowedTransactionKeys = {
|
|
2811
|
+
chainId: true,
|
|
2812
|
+
data: true,
|
|
2813
|
+
gasLimit: true,
|
|
2814
|
+
gasPrice: true,
|
|
2815
|
+
nonce: true,
|
|
2816
|
+
to: true,
|
|
2817
|
+
value: true,
|
|
2818
|
+
from: true,
|
|
2819
|
+
auxiliary: true,
|
|
2820
|
+
expiration: true,
|
|
2821
|
+
afterNonce: true,
|
|
2822
|
+
delegateCall: true,
|
|
2823
|
+
revertOnError: true
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2826
|
+
const hexlifyTransaction = (transaction, allowExtra) => {
|
|
2827
|
+
// Check only allowed properties are given
|
|
2828
|
+
const allowed = shallowCopy(allowedTransactionKeys);
|
|
2829
|
+
|
|
2830
|
+
if (allowExtra) {
|
|
2831
|
+
for (const key in allowExtra) {
|
|
2832
|
+
if (allowExtra[key]) {
|
|
2833
|
+
allowed[key] = true;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
ethers.utils.checkProperties(transaction, allowed);
|
|
2839
|
+
const result = {} // Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
|
|
2840
|
+
;
|
|
2841
|
+
['gasLimit', 'gasPrice', 'nonce', 'value'].forEach(key => {
|
|
2842
|
+
if (!transaction[key]) {
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
const value = ethers.utils.hexValue(transaction[key]);
|
|
2847
|
+
|
|
2848
|
+
if (key === 'gasLimit') {
|
|
2849
|
+
key = 'gas';
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
result[key] = value;
|
|
2853
|
+
});
|
|
2854
|
+
['from', 'to', 'data'].forEach(key => {
|
|
2855
|
+
if (!transaction[key]) {
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
result[key] = ethers.utils.hexlify(transaction[key]);
|
|
2860
|
+
});
|
|
2861
|
+
const auxiliary = transaction['auxiliary'];
|
|
2862
|
+
|
|
2863
|
+
if (auxiliary && auxiliary.length > 0) {
|
|
2864
|
+
result['auxiliary'] = [];
|
|
2865
|
+
auxiliary.forEach(a => {
|
|
2866
|
+
result['auxiliary'].push(hexlifyTransaction(a));
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
return result;
|
|
2871
|
+
};
|
|
2872
|
+
|
|
2873
|
+
class WalletCommands {
|
|
2874
|
+
constructor(walletProvider) {
|
|
2875
|
+
var _this = this;
|
|
2876
|
+
|
|
2877
|
+
this.wallet = void 0;
|
|
2878
|
+
|
|
2879
|
+
this.recoverWalletConfig = async function (address, digest, signature, chainId, walletContext) {
|
|
2880
|
+
walletContext = walletContext || (await _this.wallet.getWalletContext());
|
|
2881
|
+
return recoverWalletConfig(address, digest, signature, chainId, walletContext);
|
|
2882
|
+
};
|
|
2883
|
+
|
|
2884
|
+
this.recoverWalletConfigFromMessage = async function (address, message, signature, chainId, walletContext) {
|
|
2885
|
+
walletContext = walletContext || (await _this.wallet.getWalletContext());
|
|
2886
|
+
return recoverWalletConfig(address, encodeMessageDigest(message), signature, chainId, walletContext);
|
|
2887
|
+
};
|
|
2888
|
+
|
|
2889
|
+
this.recoverWalletConfigFromTypedData = async function (address, typedData, signature, chainId, walletContext) {
|
|
2890
|
+
walletContext = walletContext || (await _this.wallet.getWalletContext());
|
|
2891
|
+
return recoverWalletConfig(address, encodeTypedDataDigest(typedData), signature, chainId, walletContext);
|
|
2892
|
+
};
|
|
2893
|
+
|
|
2894
|
+
this.wallet = walletProvider;
|
|
2895
|
+
} // Sign message on a specified chain, or DefaultChain by default
|
|
2896
|
+
|
|
2897
|
+
|
|
2898
|
+
signMessage(message, chainId, allSigners) {
|
|
2899
|
+
const signer = this.wallet.getSigner();
|
|
2900
|
+
if (!signer) throw new Error('unable to get signer');
|
|
2901
|
+
return signer.signMessage(message, chainId, allSigners);
|
|
2902
|
+
} // Sign message on the AuthChain
|
|
2903
|
+
|
|
2904
|
+
|
|
2905
|
+
async signAuthMessage(message, allSigners) {
|
|
2906
|
+
const signer = await this.wallet.getAuthSigner();
|
|
2907
|
+
if (!signer) throw new Error('unable to get AuthChain signer');
|
|
2908
|
+
return signer.signMessage(message, await signer.getChainId(), allSigners);
|
|
2909
|
+
} // Sign EIP-712 TypedData on a specified chain, or DefaultChain by default
|
|
2910
|
+
|
|
2911
|
+
|
|
2912
|
+
signTypedData(domain, types, message, chainId, allSigners) {
|
|
2913
|
+
const signer = this.wallet.getSigner();
|
|
2914
|
+
if (!signer) throw new Error('unable to get signer');
|
|
2915
|
+
return signer.signTypedData(domain, types, message, chainId, allSigners);
|
|
2916
|
+
} // Sign EIP-712 TypedData on the AuthChain
|
|
2917
|
+
|
|
2918
|
+
|
|
2919
|
+
async signAuthTypedData(domain, types, message, allSigners) {
|
|
2920
|
+
const signer = await this.wallet.getAuthSigner();
|
|
2921
|
+
if (!signer) throw new Error('unable to get AuthChain signer');
|
|
2922
|
+
return signer.signTypedData(domain, types, message, await signer.getChainId(), allSigners);
|
|
2923
|
+
} // Verify signature of a digest, one of a message, typedData or other
|
|
2924
|
+
|
|
2925
|
+
|
|
2926
|
+
async isValidSignature(address, digest, signature, chainId, walletContext) {
|
|
2927
|
+
const provider = this.wallet.getProvider(chainId);
|
|
2928
|
+
if (!provider) throw new Error(`unable to get provider for chainId ${chainId}`);
|
|
2929
|
+
return isValidSignature(address, digest, signature, provider, chainId, walletContext);
|
|
2930
|
+
} // Verify message signature
|
|
2931
|
+
|
|
2932
|
+
|
|
2933
|
+
isValidMessageSignature(address, message, signature, chainId, walletContext) {
|
|
2934
|
+
return this.isValidSignature(address, encodeMessageDigest(message), signature, chainId, walletContext);
|
|
2935
|
+
} // Verify typedData signature
|
|
2936
|
+
|
|
2937
|
+
|
|
2938
|
+
isValidTypedDataSignature(address, typedData, signature, chainId, walletContext) {
|
|
2939
|
+
return this.isValidSignature(address, encodeTypedDataDigest(typedData), signature, chainId, walletContext);
|
|
2940
|
+
} // Recover the WalletConfig from a signature + digest combo
|
|
2941
|
+
// sendTransaction()
|
|
2942
|
+
// sendTransactions()
|
|
2943
|
+
// sendETH()
|
|
2944
|
+
// sendToken()
|
|
2945
|
+
// sendCoin() -- sugar for sendToken()
|
|
2946
|
+
// sendCollectible() -- sugar for sendToken()
|
|
2947
|
+
// callContract()
|
|
2948
|
+
// transactionHistory()
|
|
2949
|
+
// getReceipt()
|
|
2950
|
+
// getLogs()
|
|
2951
|
+
// // ..
|
|
2952
|
+
// isWalletDeployed()
|
|
2953
|
+
// deployWallet()
|
|
2954
|
+
// validateSignature()
|
|
2955
|
+
// recoverWalletConfig()
|
|
2956
|
+
// recoverAddress()
|
|
2957
|
+
|
|
2958
|
+
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
class Wallet {
|
|
2962
|
+
constructor(defaultNetworkId, config) {
|
|
2963
|
+
var _this = this;
|
|
2964
|
+
|
|
2965
|
+
this.commands = void 0;
|
|
2966
|
+
this.config = void 0;
|
|
2967
|
+
this.session = void 0;
|
|
2968
|
+
this.connectedSites = void 0;
|
|
2969
|
+
this.transport = void 0;
|
|
2970
|
+
this.networks = void 0;
|
|
2971
|
+
this.providers = void 0;
|
|
2972
|
+
|
|
2973
|
+
this.init = () => {
|
|
2974
|
+
var _this$config$transpor, _this$config$transpor2, _this$config$transpor3, _this$config$transpor4, _this$config$transpor5, _this$config$transpor6;
|
|
2975
|
+
|
|
2976
|
+
if (this.transport.provider) {
|
|
2977
|
+
// init must have already been called
|
|
2978
|
+
return;
|
|
2979
|
+
} // Setup provider
|
|
2980
|
+
|
|
2981
|
+
|
|
2982
|
+
this.transport.messageProvider = new MuxMessageProvider(); // multiple message provider setup, first one to connect will be the main transport
|
|
2983
|
+
|
|
2984
|
+
if ((_this$config$transpor = this.config.transports) != null && (_this$config$transpor2 = _this$config$transpor.windowTransport) != null && _this$config$transpor2.enabled) {
|
|
2985
|
+
this.transport.windowMessageProvider = new WindowMessageProvider(this.config.walletAppURL);
|
|
2986
|
+
this.transport.messageProvider.add(this.transport.windowMessageProvider);
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
if ((_this$config$transpor3 = this.config.transports) != null && (_this$config$transpor4 = _this$config$transpor3.proxyTransport) != null && _this$config$transpor4.enabled) {
|
|
2990
|
+
this.transport.proxyMessageProvider = new ProxyMessageProvider(this.config.transports.proxyTransport.appPort);
|
|
2991
|
+
this.transport.messageProvider.add(this.transport.proxyMessageProvider);
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
if ((_this$config$transpor5 = this.config.transports) != null && (_this$config$transpor6 = _this$config$transpor5.extensionTransport) != null && _this$config$transpor6.enabled) {
|
|
2995
|
+
this.transport.extensionMessageProvider = new ExtensionMessageProvider(this.config.transports.extensionTransport.runtime);
|
|
2996
|
+
this.transport.messageProvider.add(this.transport.extensionMessageProvider);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
this.transport.messageProvider.register(); // .....
|
|
3000
|
+
|
|
3001
|
+
this.transport.allowProvider = allowProviderMiddleware(request => {
|
|
3002
|
+
if (request.method === 'sequence_setDefaultNetwork') return true;
|
|
3003
|
+
const isConnected = this.isConnected();
|
|
3004
|
+
|
|
3005
|
+
if (!isConnected) {
|
|
3006
|
+
throw new Error('Sequence: wallet not connected');
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
return isConnected;
|
|
3010
|
+
}); // ...
|
|
3011
|
+
|
|
3012
|
+
this.transport.networkProvider = networkProviderMiddleware(request => {
|
|
3013
|
+
// return stub chainId of 0 when not connected to any
|
|
3014
|
+
if (!this.networks || this.networks.length === 0) return 0; // return the default chainId as we're connected
|
|
3015
|
+
|
|
3016
|
+
return this.networks[0].chainId;
|
|
3017
|
+
}); // Provider proxy to support middleware stack of logging, caching and read-only rpc calls
|
|
3018
|
+
|
|
3019
|
+
this.transport.cachedProvider = new CachedProvider();
|
|
3020
|
+
this.transport.cachedProvider.onUpdate(() => {
|
|
3021
|
+
if (!this.session) this.session = {
|
|
3022
|
+
providerCache: {}
|
|
3023
|
+
};
|
|
3024
|
+
this.session.providerCache = this.transport.cachedProvider.getCache();
|
|
3025
|
+
this.saveSession(this.session);
|
|
3026
|
+
}); // ..
|
|
3027
|
+
|
|
3028
|
+
this.transport.router = new JsonRpcRouter([loggingProviderMiddleware, this.transport.networkProvider, this.transport.allowProvider, exceptionProviderMiddleware, this.transport.cachedProvider], this.transport.messageProvider);
|
|
3029
|
+
this.transport.provider = new Web3Provider(this.transport.router); // NOTE: we don't listen on 'connect' even here as we handle it within connect() method
|
|
3030
|
+
// in more synchronous flow.
|
|
3031
|
+
// below will update the wallet session object and persist it. In case the session
|
|
3032
|
+
// is undefined, we consider the session to have been removed by the user, so we clear it.
|
|
3033
|
+
|
|
3034
|
+
this.transport.messageProvider.on('open', openInfo => {
|
|
3035
|
+
const {
|
|
3036
|
+
session
|
|
3037
|
+
} = openInfo;
|
|
3038
|
+
|
|
3039
|
+
if (!session) {
|
|
3040
|
+
if (this.session && this.session.accountAddress) {
|
|
3041
|
+
// emit disconnect even if previously we had a session, and now we don't.
|
|
3042
|
+
this.transport.messageProvider.emit('disconnect');
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
this.clearSession();
|
|
3046
|
+
} else {
|
|
3047
|
+
this.useSession(session, true);
|
|
3048
|
+
}
|
|
3049
|
+
}); // below will update the account upon wallet connect/disconnect (aka, login/logout)
|
|
3050
|
+
|
|
3051
|
+
this.transport.messageProvider.on('accountsChanged', accounts => {
|
|
3052
|
+
if (!accounts || accounts.length === 0 || accounts[0] === '') {
|
|
3053
|
+
this.clearSession();
|
|
3054
|
+
} else {
|
|
3055
|
+
this.useSession({
|
|
3056
|
+
accountAddress: accounts[0]
|
|
3057
|
+
}, true);
|
|
3058
|
+
}
|
|
3059
|
+
}); // below will update the networks automatically when the wallet networks change
|
|
3060
|
+
|
|
3061
|
+
this.transport.messageProvider.on('networks', networks => {
|
|
3062
|
+
this.useSession({
|
|
3063
|
+
networks: networks
|
|
3064
|
+
}, true);
|
|
3065
|
+
}); // below will update the wallet context automatically
|
|
3066
|
+
|
|
3067
|
+
this.transport.messageProvider.on('walletContext', walletContext => {
|
|
3068
|
+
this.useSession({
|
|
3069
|
+
walletContext: walletContext
|
|
3070
|
+
}, true);
|
|
3071
|
+
}); // Load existing session from localStorage
|
|
3072
|
+
|
|
3073
|
+
const session = this.loadSession();
|
|
3074
|
+
|
|
3075
|
+
if (session) {
|
|
3076
|
+
this.useSession(session, false);
|
|
3077
|
+
}
|
|
3078
|
+
};
|
|
3079
|
+
|
|
3080
|
+
this.connect = async function (options) {
|
|
3081
|
+
if ((options == null ? void 0 : options.refresh) === true) {
|
|
3082
|
+
_this.disconnect();
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
if (_this.isConnected() && _this.isSiteConnected(options == null ? void 0 : options.origin) && !!_this.session && !(options != null && options.authorize) && !(options != null && options.askForEmail)) {
|
|
3086
|
+
return {
|
|
3087
|
+
connected: true,
|
|
3088
|
+
session: _this.session,
|
|
3089
|
+
chainId: ethers.utils.hexlify(await _this.getChainId())
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
if (options) {
|
|
3094
|
+
if (options.authorize && (!options.app || options.app === '')) {
|
|
3095
|
+
throw new Error(`connecting with 'authorize' option also requires 'app' to be set`);
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
await _this.openWallet(undefined, {
|
|
3100
|
+
type: 'connect',
|
|
3101
|
+
options
|
|
3102
|
+
});
|
|
3103
|
+
const connectDetails = await _this.transport.messageProvider.waitUntilConnected().catch(_ => {
|
|
3104
|
+
return {
|
|
3105
|
+
connected: false
|
|
3106
|
+
};
|
|
3107
|
+
});
|
|
3108
|
+
|
|
3109
|
+
if (connectDetails.connected) {
|
|
3110
|
+
if (!!connectDetails.session) {
|
|
3111
|
+
_this.useSession(connectDetails.session, true);
|
|
3112
|
+
|
|
3113
|
+
_this.addConnectedSite(options == null ? void 0 : options.origin);
|
|
3114
|
+
} else {
|
|
3115
|
+
throw new Error('impossible state, connect response is missing session');
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
return connectDetails;
|
|
3120
|
+
};
|
|
3121
|
+
|
|
3122
|
+
this.authorize = async function (options) {
|
|
3123
|
+
return _this.connect(_extends({}, options, {
|
|
3124
|
+
authorize: true
|
|
3125
|
+
}));
|
|
3126
|
+
};
|
|
3127
|
+
|
|
3128
|
+
this.getSession = () => {
|
|
3129
|
+
if (!this.isConnected()) {
|
|
3130
|
+
return undefined;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
return this.session;
|
|
3134
|
+
};
|
|
3135
|
+
|
|
3136
|
+
this.getAddress = async function () {
|
|
3137
|
+
if (!_this.isConnected()) {
|
|
3138
|
+
throw new Error('connect first');
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
const session = _this.getSession();
|
|
3142
|
+
|
|
3143
|
+
return session.accountAddress;
|
|
3144
|
+
};
|
|
3145
|
+
|
|
3146
|
+
this.getNetworks = async function (chainId) {
|
|
3147
|
+
if (!_this.isConnected() || !_this.networks) {
|
|
3148
|
+
throw new Error('connect first');
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
if (chainId) {
|
|
3152
|
+
// filter list to just the specific chain requested
|
|
3153
|
+
const network = findNetworkConfig(_this.networks, chainId);
|
|
3154
|
+
return network ? [network] : [];
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
return _this.networks;
|
|
3158
|
+
};
|
|
3159
|
+
|
|
3160
|
+
this.getChainId = async function () {
|
|
3161
|
+
if (!_this.networks || _this.networks.length < 1) {
|
|
3162
|
+
throw new Error('networks have not been set by session. connect first.');
|
|
3163
|
+
} // default chain id is first one in the list, by design
|
|
3164
|
+
|
|
3165
|
+
|
|
3166
|
+
const network = _this.networks[0];
|
|
3167
|
+
|
|
3168
|
+
if (!network.isDefaultChain) {
|
|
3169
|
+
throw new Error('expecting first network in list to be default chain');
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
return network.chainId;
|
|
3173
|
+
};
|
|
3174
|
+
|
|
3175
|
+
this.getAuthChainId = async function () {
|
|
3176
|
+
if (!_this.networks || _this.networks.length < 1) {
|
|
3177
|
+
throw new Error('networks have not been set by session. connect first.');
|
|
3178
|
+
} // auth chain is first or second one in the list, by design
|
|
3179
|
+
|
|
3180
|
+
|
|
3181
|
+
const network0 = _this.networks[0];
|
|
3182
|
+
|
|
3183
|
+
if (network0.isAuthChain) {
|
|
3184
|
+
return network0.chainId;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
if (_this.networks.length > 1) {
|
|
3188
|
+
const network1 = _this.networks[1];
|
|
3189
|
+
|
|
3190
|
+
if (network1.isAuthChain) {
|
|
3191
|
+
return network1.chainId;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
throw new Error('expecting first or second network in list to be the auth chain');
|
|
3196
|
+
};
|
|
3197
|
+
|
|
3198
|
+
this.openWallet = async function (path, intent, networkId) {
|
|
3199
|
+
if ((intent == null ? void 0 : intent.type) !== 'connect' && !_this.isConnected()) {
|
|
3200
|
+
throw new Error('connect first');
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
let currentNetworkId;
|
|
3204
|
+
|
|
3205
|
+
if (!_this.networks || _this.networks.length < 1) {
|
|
3206
|
+
currentNetworkId = _this.config.defaultNetworkId;
|
|
3207
|
+
} else {
|
|
3208
|
+
currentNetworkId = await _this.getChainId();
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
_this.transport.messageProvider.openWallet(path, intent, networkId || currentNetworkId);
|
|
3212
|
+
|
|
3213
|
+
await _this.transport.messageProvider.waitUntilOpened();
|
|
3214
|
+
return true;
|
|
3215
|
+
};
|
|
3216
|
+
|
|
3217
|
+
this.closeWallet = () => {
|
|
3218
|
+
this.transport.messageProvider.closeWallet();
|
|
3219
|
+
};
|
|
3220
|
+
|
|
3221
|
+
this.loadSession = () => {
|
|
3222
|
+
const data = window.localStorage.getItem('@sequence.session');
|
|
3223
|
+
|
|
3224
|
+
if (!data || data === '') {
|
|
3225
|
+
return undefined;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
try {
|
|
3229
|
+
const session = JSON.parse(data);
|
|
3230
|
+
return session;
|
|
3231
|
+
} catch (err) {
|
|
3232
|
+
logger.warn('loadSession failed, unable to parse session payload from localStorage.');
|
|
3233
|
+
return undefined;
|
|
3234
|
+
}
|
|
3235
|
+
};
|
|
3236
|
+
|
|
3237
|
+
this.saveSession = session => {
|
|
3238
|
+
logger.debug('wallet provider: saving session');
|
|
3239
|
+
const data = JSON.stringify(session);
|
|
3240
|
+
window.localStorage.setItem('@sequence.session', data);
|
|
3241
|
+
};
|
|
3242
|
+
|
|
3243
|
+
this.useSession = async function (session, autoSave = true) {
|
|
3244
|
+
if (!_this.session) _this.session = {}; // setup wallet context
|
|
3245
|
+
|
|
3246
|
+
if (_this.config.walletContext) {
|
|
3247
|
+
_this.session.walletContext = _this.config.walletContext;
|
|
3248
|
+
} else if (session.walletContext) {
|
|
3249
|
+
_this.session.walletContext = session.walletContext;
|
|
3250
|
+
} // setup account
|
|
3251
|
+
|
|
3252
|
+
|
|
3253
|
+
if (session.accountAddress) {
|
|
3254
|
+
_this.useAccountAddress(session.accountAddress);
|
|
3255
|
+
} // setup networks
|
|
3256
|
+
|
|
3257
|
+
|
|
3258
|
+
if (session.networks) {
|
|
3259
|
+
_this.useNetworks(session.networks);
|
|
3260
|
+
} // setup provider cache
|
|
3261
|
+
|
|
3262
|
+
|
|
3263
|
+
if (session.providerCache) {
|
|
3264
|
+
_this.transport.cachedProvider.setCache(session.providerCache);
|
|
3265
|
+
} // persist
|
|
3266
|
+
|
|
3267
|
+
|
|
3268
|
+
if (autoSave) {
|
|
3269
|
+
_this.saveSession(_this.session);
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
// config is a Partial, so that we may intersect it with the DefaultProviderConfig,
|
|
3274
|
+
// which allows easy overriding and control of the config.
|
|
3275
|
+
this.config = _extends({}, DefaultProviderConfig);
|
|
3276
|
+
|
|
3277
|
+
if (config) {
|
|
3278
|
+
this.config = _extends({}, this.config, config);
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
if (defaultNetworkId) {
|
|
3282
|
+
this.config.defaultNetworkId = defaultNetworkId;
|
|
3283
|
+
} else if (!this.config.defaultNetworkId) {
|
|
3284
|
+
this.config.defaultNetworkId = 'mainnet';
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
this.transport = {};
|
|
3288
|
+
this.networks = [];
|
|
3289
|
+
this.providers = {};
|
|
3290
|
+
this.connectedSites = new LocalStore('@sequence.connectedSites', []);
|
|
3291
|
+
this.commands = new WalletCommands(this);
|
|
3292
|
+
this.init();
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
addConnectedSite(origin) {
|
|
3296
|
+
origin = origin || window.location.origin;
|
|
3297
|
+
const connectedSites = this.connectedSites.get();
|
|
3298
|
+
|
|
3299
|
+
if (connectedSites) {
|
|
3300
|
+
if (connectedSites.includes(origin)) {
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
this.connectedSites.set([...connectedSites, origin]);
|
|
3305
|
+
} else {
|
|
3306
|
+
this.connectedSites.set([origin]);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
removeConnectedSite(origin) {
|
|
3311
|
+
const authorized = this.connectedSites.get();
|
|
3312
|
+
|
|
3313
|
+
if (authorized) {
|
|
3314
|
+
this.connectedSites.set(authorized.filter(domain => domain !== origin));
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
getConnectedSites() {
|
|
3319
|
+
return this.connectedSites.get();
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
isSiteConnected(origin) {
|
|
3323
|
+
const authorized = this.connectedSites.get();
|
|
3324
|
+
return !!authorized && authorized.includes(origin || window.location.origin);
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
disconnect() {
|
|
3328
|
+
if (this.isOpened()) {
|
|
3329
|
+
this.closeWallet();
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
this.clearSession();
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
getProviderConfig() {
|
|
3336
|
+
return this.config;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
isOpened() {
|
|
3340
|
+
return this.transport.messageProvider.isOpened();
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
isConnected() {
|
|
3344
|
+
return this.session !== undefined && this.session.networks !== undefined && this.session.networks.length > 0 && this.networks !== undefined && this.networks.length > 0 && !!this.session.accountAddress && this.session.accountAddress.startsWith('0x');
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
getProvider(chainId) {
|
|
3348
|
+
// return the top-level provider message transport when chainId is unspecified
|
|
3349
|
+
// and user has not logged in
|
|
3350
|
+
if (!this.isConnected()) {
|
|
3351
|
+
if (chainId) {
|
|
3352
|
+
throw new Error(`session is empty. connect and try again.`);
|
|
3353
|
+
} else {
|
|
3354
|
+
return this.transport.provider;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
let network = this.networks[0];
|
|
3359
|
+
|
|
3360
|
+
if (chainId) {
|
|
3361
|
+
network = findNetworkConfig(this.networks, chainId);
|
|
3362
|
+
|
|
3363
|
+
if (!network) {
|
|
3364
|
+
throw new Error(`network ${chainId} is not in the network list`);
|
|
3365
|
+
}
|
|
3366
|
+
} // return memoized network provider
|
|
3367
|
+
|
|
3368
|
+
|
|
3369
|
+
if (this.providers[network.chainId]) {
|
|
3370
|
+
return this.providers[network.chainId];
|
|
3371
|
+
} // builder web3 provider stack
|
|
3372
|
+
|
|
3373
|
+
|
|
3374
|
+
let provider; // network.provider may be set by the ProviderConfig override
|
|
3375
|
+
|
|
3376
|
+
const rpcProvider = network.provider ? network.provider : new JsonRpcProvider(network.rpcUrl, network.chainId);
|
|
3377
|
+
|
|
3378
|
+
if (network.isDefaultChain) {
|
|
3379
|
+
// communicating with defaultChain will prioritize the wallet message transport
|
|
3380
|
+
const router = new JsonRpcRouter([loggingProviderMiddleware, exceptionProviderMiddleware, new EagerProvider({
|
|
3381
|
+
accountAddress: this.session.accountAddress,
|
|
3382
|
+
walletContext: this.session.walletContext
|
|
3383
|
+
}), new SigningProvider(this.transport.provider), this.transport.cachedProvider], new JsonRpcSender(rpcProvider));
|
|
3384
|
+
provider = new Web3Provider(router, network.chainId);
|
|
3385
|
+
} else {
|
|
3386
|
+
// communicating with another chain will bind to that network, but will forward
|
|
3387
|
+
// any signing-related requests to the wallet message transport
|
|
3388
|
+
const router = new JsonRpcRouter([loggingProviderMiddleware, exceptionProviderMiddleware, new EagerProvider({
|
|
3389
|
+
accountAddress: this.session.accountAddress,
|
|
3390
|
+
walletContext: this.session.walletContext,
|
|
3391
|
+
chainId: network.chainId
|
|
3392
|
+
}), new SigningProvider(this.transport.provider), new CachedProvider(network.chainId)], new JsonRpcSender(rpcProvider));
|
|
3393
|
+
provider = new Web3Provider(router, network.chainId);
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
this.providers[network.chainId] = provider;
|
|
3397
|
+
return provider;
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
async getAuthProvider() {
|
|
3401
|
+
return this.getProvider((await this.getAuthNetwork()).chainId);
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
async getAuthNetwork() {
|
|
3405
|
+
return (await this.getNetworks()).find(n => n.isAuthChain);
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
getAllProviders() {
|
|
3409
|
+
return this.providers;
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
getSigner(chainId) {
|
|
3413
|
+
return this.getProvider(chainId).getSigner();
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
async getAuthSigner() {
|
|
3417
|
+
return (await this.getAuthProvider()).getSigner();
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
getWalletConfig(chainId) {
|
|
3421
|
+
return this.getSigner().getWalletConfig(chainId);
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
getWalletState(chainId) {
|
|
3425
|
+
return this.getSigner().getWalletState(chainId);
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
getWalletContext() {
|
|
3429
|
+
return this.getSigner().getWalletContext();
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
isDeployed(chainId) {
|
|
3433
|
+
return this.getSigner(chainId).isDeployed();
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
on(event, fn) {
|
|
3437
|
+
this.transport.messageProvider.on(event, fn);
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
once(event, fn) {
|
|
3441
|
+
this.transport.messageProvider.once(event, fn);
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
useAccountAddress(accountAddress) {
|
|
3445
|
+
if (!this.session) this.session = {};
|
|
3446
|
+
this.session.accountAddress = ethers.utils.getAddress(accountAddress);
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
useNetworks(networks) {
|
|
3450
|
+
// set networks in the session
|
|
3451
|
+
if (!this.session) this.session = {}; // set networks on session object
|
|
3452
|
+
|
|
3453
|
+
this.session.networks = networks; // short-circuit if setting empty network list (aka logged out state)
|
|
3454
|
+
|
|
3455
|
+
if (!this.session.networks || this.session.networks.length === 0) {
|
|
3456
|
+
return;
|
|
3457
|
+
} // check if any custom network settings, otherwise return early
|
|
3458
|
+
|
|
3459
|
+
|
|
3460
|
+
if (!this.config.networks && !this.config.networkRpcUrl) {
|
|
3461
|
+
this.networks = networks;
|
|
3462
|
+
return;
|
|
3463
|
+
} // combine custom network config with networks in the session
|
|
3464
|
+
|
|
3465
|
+
|
|
3466
|
+
if (this.config.networks) {
|
|
3467
|
+
this.networks = networks.map(n => _extends({}, n)); // copy
|
|
3468
|
+
|
|
3469
|
+
this.config.networks.forEach(n => {
|
|
3470
|
+
const network = findNetworkConfig(this.networks, n.chainId || n.name);
|
|
3471
|
+
if (!network) return;
|
|
3472
|
+
updateNetworkConfig(n, network);
|
|
3473
|
+
});
|
|
3474
|
+
ensureValidNetworks(this.networks, true);
|
|
3475
|
+
} // an extra override for convenience
|
|
3476
|
+
|
|
3477
|
+
|
|
3478
|
+
if (this.config.networkRpcUrl) {
|
|
3479
|
+
this.networks[0].rpcUrl = this.config.networkRpcUrl;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
clearSession() {
|
|
3484
|
+
var _this$transport$cache;
|
|
3485
|
+
|
|
3486
|
+
logger.debug('wallet provider: clearing session');
|
|
3487
|
+
window.localStorage.removeItem('@sequence.session');
|
|
3488
|
+
this.session = undefined;
|
|
3489
|
+
this.networks = [];
|
|
3490
|
+
this.providers = {};
|
|
3491
|
+
(_this$transport$cache = this.transport.cachedProvider) == null ? void 0 : _this$transport$cache.clearCache();
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
}
|
|
3495
|
+
const DefaultProviderConfig = {
|
|
3496
|
+
walletAppURL: 'https://sequence.app',
|
|
3497
|
+
// walletSessionURL: 'https://session.sequence.app',
|
|
3498
|
+
transports: {
|
|
3499
|
+
windowTransport: {
|
|
3500
|
+
enabled: true
|
|
3501
|
+
},
|
|
3502
|
+
proxyTransport: {
|
|
3503
|
+
enabled: false
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
};
|
|
3507
|
+
|
|
3508
|
+
export { BaseInjectedTransport, BaseProviderTransport, BaseWalletTransport, CHANNEL_ID, DefaultProviderConfig, ErrSignedInRequired, EventType, ExtensionMessageHandler, ExtensionMessageProvider, InitState, LocalStore, LocalWeb3Provider, MuxMessageProvider, OpenState, PROVIDER_OPEN_TIMEOUT, ProviderError, ProxyMessageChannel, ProxyMessageChannelPort, ProxyMessageHandler, ProxyMessageProvider, Wallet, WalletRequestHandler, Web3Provider, Web3Signer, WindowMessageHandler, WindowMessageProvider, WindowSessionParams, isBrowserExtension, isSequenceProvider, isValidSignature, nextMessageIdx, recoverWalletConfig };
|