@tomo-inc/inject-providers 0.0.1
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 +3 -0
- package/README.md +58 -0
- package/eth-rpc-errors.d.ts +37 -0
- package/package.json +41 -0
- package/project.json +59 -0
- package/src/btc/index.ts +3 -0
- package/src/btc/types.ts +38 -0
- package/src/btc/unisat.ts +332 -0
- package/src/const.ts +0 -0
- package/src/dogecoin/dogecoin.ts +304 -0
- package/src/dogecoin/index.ts +3 -0
- package/src/dogecoin/types.ts +38 -0
- package/src/evm/index.ts +3 -0
- package/src/evm/messages.ts +39 -0
- package/src/evm/metamask.ts +890 -0
- package/src/evm/shimWeb3.ts +49 -0
- package/src/evm/type.ts +81 -0
- package/src/evm/utils.ts +85 -0
- package/src/global.d.ts +37 -0
- package/src/index.ts +7 -0
- package/src/solana/index.ts +3 -0
- package/src/solana/phantom.ts +424 -0
- package/src/solana/types.ts +63 -0
- package/src/solana/utils.ts +30 -0
- package/src/tron/index.ts +3 -0
- package/src/tron/tronLink.ts +341 -0
- package/src/tron/types.ts +56 -0
- package/src/types/dapp.ts +7 -0
- package/src/types/index.ts +34 -0
- package/src/utils/dapp-info.ts +96 -0
- package/src/utils/dom.ts +29 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/ready-promise.ts +44 -0
- package/src/utils/utils.ts +23 -0
- package/src/utils.ts +35 -0
- package/tsconfig.json +7 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createIdRemapMiddleware,
|
|
5
|
+
JsonRpcEngine,
|
|
6
|
+
JsonRpcRequest,
|
|
7
|
+
JsonRpcResponse,
|
|
8
|
+
JsonRpcSuccess,
|
|
9
|
+
} from "json-rpc-engine";
|
|
10
|
+
import { createStreamMiddleware } from "json-rpc-middleware-stream";
|
|
11
|
+
|
|
12
|
+
import { getDappInfo } from "../utils/index";
|
|
13
|
+
import messages from "./messages";
|
|
14
|
+
|
|
15
|
+
import { EthereumRpcError, ethErrors } from "eth-rpc-errors";
|
|
16
|
+
import { toHex } from "viem";
|
|
17
|
+
import {
|
|
18
|
+
ConsoleLike,
|
|
19
|
+
createErrorMiddleware,
|
|
20
|
+
EMITTED_NOTIFICATIONS,
|
|
21
|
+
getRpcPromiseCallback,
|
|
22
|
+
logStreamDisconnectWarning,
|
|
23
|
+
Maybe,
|
|
24
|
+
NOOP,
|
|
25
|
+
} from "./utils";
|
|
26
|
+
|
|
27
|
+
import { ChainTypes, IProductInfo } from "../types";
|
|
28
|
+
import {
|
|
29
|
+
InitializeProviderOptions,
|
|
30
|
+
InpageProviderOptions,
|
|
31
|
+
InternalState,
|
|
32
|
+
RequestArguments,
|
|
33
|
+
SendSyncJsonRpcRequest,
|
|
34
|
+
UnvalidatedJsonRpcRequest,
|
|
35
|
+
WarningEventName,
|
|
36
|
+
} from "./type";
|
|
37
|
+
|
|
38
|
+
const chainType = ChainTypes.EVM;
|
|
39
|
+
|
|
40
|
+
export class EvmProvider extends EventEmitter {
|
|
41
|
+
private readonly _log: ConsoleLike;
|
|
42
|
+
|
|
43
|
+
private _state: InternalState;
|
|
44
|
+
|
|
45
|
+
private _rpcEngine: JsonRpcEngine;
|
|
46
|
+
|
|
47
|
+
public sendRequest: (chainType: string, data: any) => void;
|
|
48
|
+
|
|
49
|
+
public onResponse: any;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The chain ID of the currently connected Ethereum chain.
|
|
53
|
+
* See [chainId.network]{@link https://chainid.network} for more information.
|
|
54
|
+
*/
|
|
55
|
+
public chainId: string | null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The network ID of the currently connected Ethereum chain.
|
|
59
|
+
*/
|
|
60
|
+
public networkVersion: string | null;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The user's currently selected Ethereum address.
|
|
64
|
+
* If null, is either locked or the user has not permitted any
|
|
65
|
+
* addresses to be viewed.
|
|
66
|
+
*/
|
|
67
|
+
public selectedAddress: string | null;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Indicating that this provider is a provider.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
public name: string;
|
|
74
|
+
public icon: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param connectionStream - A Node.js duplex stream
|
|
78
|
+
* @param options - An options bag
|
|
79
|
+
* @param options.jsonRpcStreamName - The name of the internal JSON-RPC stream.
|
|
80
|
+
* @param options.logger - The logging API to use. Default: console
|
|
81
|
+
* @param options.maxEventListeners - The maximum number of event
|
|
82
|
+
* listeners. Default: 100
|
|
83
|
+
* @param options.shouldSendMetadata - Whether the provider should
|
|
84
|
+
* send page metadata. Default: true
|
|
85
|
+
*/
|
|
86
|
+
constructor(
|
|
87
|
+
productInfo: IProductInfo,
|
|
88
|
+
{
|
|
89
|
+
logger = console,
|
|
90
|
+
maxEventListeners = 100,
|
|
91
|
+
shouldSendMetadata = true,
|
|
92
|
+
sendRequest,
|
|
93
|
+
onResponse,
|
|
94
|
+
}: InpageProviderOptions,
|
|
95
|
+
) {
|
|
96
|
+
if (typeof maxEventListeners !== "number" || typeof shouldSendMetadata !== "boolean") {
|
|
97
|
+
throw new Error(messages.errors.invalidOptions(maxEventListeners, shouldSendMetadata));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
validateLoggerObject(logger);
|
|
101
|
+
|
|
102
|
+
super();
|
|
103
|
+
|
|
104
|
+
this._log = logger;
|
|
105
|
+
this.name = productInfo.name;
|
|
106
|
+
this.icon = productInfo.icon;
|
|
107
|
+
this.sendRequest = sendRequest;
|
|
108
|
+
this.onResponse = onResponse;
|
|
109
|
+
|
|
110
|
+
this.setMaxListeners(maxEventListeners);
|
|
111
|
+
|
|
112
|
+
// private state
|
|
113
|
+
this._state = {
|
|
114
|
+
sentWarnings: {
|
|
115
|
+
// methods
|
|
116
|
+
enable: false,
|
|
117
|
+
experimentalMethods: false,
|
|
118
|
+
send: false,
|
|
119
|
+
// events
|
|
120
|
+
events: {
|
|
121
|
+
close: false,
|
|
122
|
+
data: false,
|
|
123
|
+
networkChanged: false,
|
|
124
|
+
notification: false,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
accounts: null,
|
|
128
|
+
isConnected: false,
|
|
129
|
+
isUnlocked: false,
|
|
130
|
+
initialized: true,
|
|
131
|
+
isPermanentlyDisconnected: false,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// public state
|
|
135
|
+
this.selectedAddress = null;
|
|
136
|
+
this.networkVersion = null;
|
|
137
|
+
this.chainId = null;
|
|
138
|
+
|
|
139
|
+
// bind functions (to prevent consumers from making unbound calls)
|
|
140
|
+
this._handleAccountsChanged = this._handleAccountsChanged.bind(this);
|
|
141
|
+
this._handleConnect = this._handleConnect.bind(this);
|
|
142
|
+
this._handleChainChanged = this._handleChainChanged.bind(this);
|
|
143
|
+
this._handleDisconnect = this._handleDisconnect.bind(this);
|
|
144
|
+
this._handleStreamDisconnect = this._handleStreamDisconnect.bind(this);
|
|
145
|
+
this._handleUnlockStateChanged = this._handleUnlockStateChanged.bind(this);
|
|
146
|
+
this._sendSync = this._sendSync.bind(this);
|
|
147
|
+
this._rpcRequest = this._rpcRequest.bind(this);
|
|
148
|
+
this._warnOfDeprecation = this._warnOfDeprecation.bind(this);
|
|
149
|
+
this.enable = this.enable.bind(this);
|
|
150
|
+
this.connect = this.connect.bind(this);
|
|
151
|
+
this.disconnect = this.disconnect.bind(this);
|
|
152
|
+
this.request = this.request.bind(this);
|
|
153
|
+
this.send = this.send.bind(this);
|
|
154
|
+
this.sendAsync = this.sendAsync.bind(this);
|
|
155
|
+
|
|
156
|
+
// EIP-1193 connect
|
|
157
|
+
this.on("connect", () => {
|
|
158
|
+
this._state.isConnected = true;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const jsonRpcConnection = createStreamMiddleware();
|
|
162
|
+
|
|
163
|
+
// handle RPC requests via dapp-side rpc engine
|
|
164
|
+
const rpcEngine = new JsonRpcEngine();
|
|
165
|
+
rpcEngine.push(createIdRemapMiddleware());
|
|
166
|
+
rpcEngine.push(createErrorMiddleware(this._log));
|
|
167
|
+
rpcEngine.push(jsonRpcConnection.middleware as any);
|
|
168
|
+
this._rpcEngine = rpcEngine;
|
|
169
|
+
|
|
170
|
+
this._initializeState();
|
|
171
|
+
|
|
172
|
+
// handle JSON-RPC notifications
|
|
173
|
+
jsonRpcConnection.events.on("notification", (payload) => {
|
|
174
|
+
const { method, params } = payload;
|
|
175
|
+
|
|
176
|
+
if (method === "wallet_accountsChanged") {
|
|
177
|
+
this._handleAccountsChanged(params);
|
|
178
|
+
} else if (method === "wallet_unlockStateChanged") {
|
|
179
|
+
this._handleUnlockStateChanged(params);
|
|
180
|
+
} else if (method === "wallet_chainChanged") {
|
|
181
|
+
this._handleChainChanged(params);
|
|
182
|
+
} else if (EMITTED_NOTIFICATIONS.includes(method)) {
|
|
183
|
+
// deprecated
|
|
184
|
+
// emitted here because that was the original order
|
|
185
|
+
this.emit("data", payload);
|
|
186
|
+
|
|
187
|
+
this.emit("message", {
|
|
188
|
+
type: method,
|
|
189
|
+
data: params,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// deprecated
|
|
193
|
+
this.emit("notification", payload.params.result);
|
|
194
|
+
} else if (method === "WALLET_STREAM_FAILURE") {
|
|
195
|
+
console.error(messages.errors.permanentlyDisconnected());
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// miscellanea
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
//====================
|
|
203
|
+
// Public Methods
|
|
204
|
+
//====================
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Returns whether the provider can process RPC requests.
|
|
208
|
+
*/
|
|
209
|
+
isConnected(): boolean {
|
|
210
|
+
return this._state.isConnected;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Submits an RPC request for the given method, with the given params.
|
|
215
|
+
* Resolves with the result of the method call, or rejects on error.
|
|
216
|
+
*
|
|
217
|
+
* @param args - The RPC request arguments.
|
|
218
|
+
* @param args.method - The RPC method name.
|
|
219
|
+
* @param args.params - The parameters for the RPC method.
|
|
220
|
+
* @returns A Promise that resolves with the result of the RPC method,
|
|
221
|
+
* or rejects if an error is encountered.
|
|
222
|
+
*/
|
|
223
|
+
async request<T>(args: RequestArguments): Promise<Maybe<T>> {
|
|
224
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
225
|
+
throw ethErrors.rpc.invalidRequest({
|
|
226
|
+
message: messages.errors.invalidRequestArgs(),
|
|
227
|
+
data: args,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const { method, params } = args;
|
|
232
|
+
|
|
233
|
+
if (typeof method !== "string" || method.length === 0) {
|
|
234
|
+
throw ethErrors.rpc.invalidRequest({
|
|
235
|
+
message: messages.errors.invalidRequestMethod(),
|
|
236
|
+
data: args,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (method === "wallet_addEthereumChain") {
|
|
241
|
+
if (!Array.isArray(params) || params.length === 0) {
|
|
242
|
+
throw ethErrors.rpc.invalidRequest({
|
|
243
|
+
message: messages.errors.invalidRequestParams(),
|
|
244
|
+
data: args,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const network = params[0] as any;
|
|
248
|
+
if (!network || !network.chainName || !network.rpcUrls || !network.chainId || !network.nativeCurrency) {
|
|
249
|
+
throw ethErrors.rpc.invalidRequest({
|
|
250
|
+
message: messages.errors.invalidRequestParams(),
|
|
251
|
+
data: args,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (method === "personal_sign") {
|
|
257
|
+
if (!Array.isArray(params) || params.length !== 2 || !params[0] || !params[1]) {
|
|
258
|
+
throw ethErrors.rpc.invalidRequest({
|
|
259
|
+
message: messages.errors.invalidRequestParams(),
|
|
260
|
+
data: args,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (method === "wallet_watchAsset") {
|
|
266
|
+
const { type, options } = params as {
|
|
267
|
+
type: string;
|
|
268
|
+
options: {
|
|
269
|
+
address: string;
|
|
270
|
+
symbol: string;
|
|
271
|
+
decimals: number;
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
const isNotCompelete = !options || !options.address || !options.symbol || !options.decimals;
|
|
275
|
+
if (!type || isNotCompelete) {
|
|
276
|
+
throw ethErrors.rpc.invalidRequest({
|
|
277
|
+
message: messages.errors.invalidRequestParams(),
|
|
278
|
+
data: args,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (params !== undefined && !Array.isArray(params) && (typeof params !== "object" || params === null)) {
|
|
284
|
+
throw ethErrors.rpc.invalidRequest({
|
|
285
|
+
message: messages.errors.invalidRequestParams(),
|
|
286
|
+
data: args,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const connectFuns: Record<string, boolean> = {
|
|
291
|
+
enable: true,
|
|
292
|
+
connect: true,
|
|
293
|
+
eth_accounts: true,
|
|
294
|
+
eth_requestAccounts: true,
|
|
295
|
+
};
|
|
296
|
+
// if (connectFuns[args.method] || args.method === "_wallet_getProviderState" || args.method === "disconnect") {
|
|
297
|
+
const dappInfo = await getDappInfo();
|
|
298
|
+
args = { ...args, ...{ dappInfo } };
|
|
299
|
+
// }
|
|
300
|
+
|
|
301
|
+
this.sendRequest(chainType, args);
|
|
302
|
+
|
|
303
|
+
return this.onResponse(args).then((res: any) => {
|
|
304
|
+
const { data, method } = res || {};
|
|
305
|
+
|
|
306
|
+
if (method === "_wallet_getProviderState") {
|
|
307
|
+
this._handleAccountsChanged(data?.accounts || [], true);
|
|
308
|
+
}
|
|
309
|
+
if (method === "wallet_revokePermissions") {
|
|
310
|
+
this._handleAccountsChanged([], true);
|
|
311
|
+
}
|
|
312
|
+
if (method in connectFuns && connectFuns[method]) {
|
|
313
|
+
this._handleAccountsChanged(data || [], true);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (method === "wallet_switchEthereumChain" || method === "eth_chainId") {
|
|
317
|
+
this._handleChainChanged({ chainId: data, isConnected: true });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return data;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Submits an RPC request per the given JSON-RPC request object.
|
|
326
|
+
*
|
|
327
|
+
* @param payload - The RPC request object.
|
|
328
|
+
* @param cb - The callback function.
|
|
329
|
+
*/
|
|
330
|
+
sendAsync(
|
|
331
|
+
payload: JsonRpcRequest<unknown>,
|
|
332
|
+
callback: (error: Error | null, result?: JsonRpcResponse<unknown>) => void,
|
|
333
|
+
): void {
|
|
334
|
+
this._rpcRequest(payload, callback);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* We override the following event methods so that we can warn consumers
|
|
339
|
+
* about deprecated events:
|
|
340
|
+
* addListener, on, once, prependListener, prependOnceListener
|
|
341
|
+
*/
|
|
342
|
+
|
|
343
|
+
addListener(eventName: string, listener: (...args: unknown[]) => void) {
|
|
344
|
+
this._warnOfDeprecation(eventName);
|
|
345
|
+
return super.addListener(eventName, listener);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
on(eventName: string, listener: (...args: unknown[]) => void) {
|
|
349
|
+
this._warnOfDeprecation(eventName);
|
|
350
|
+
return super.on(eventName, listener);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
once(eventName: string, listener: (...args: unknown[]) => void) {
|
|
354
|
+
this._warnOfDeprecation(eventName);
|
|
355
|
+
return super.once(eventName, listener);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
prependListener(eventName: string, listener: (...args: unknown[]) => void) {
|
|
359
|
+
this._warnOfDeprecation(eventName);
|
|
360
|
+
return super.prependListener(eventName, listener);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
prependOnceListener(eventName: string, listener: (...args: unknown[]) => void) {
|
|
364
|
+
this._warnOfDeprecation(eventName);
|
|
365
|
+
return super.prependOnceListener(eventName, listener);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
//====================
|
|
369
|
+
// Private Methods
|
|
370
|
+
//====================
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Constructor helper.
|
|
374
|
+
* Populates initial state by calling 'getProviderState' and emits
|
|
375
|
+
* necessary events.
|
|
376
|
+
*/
|
|
377
|
+
private async _initializeState() {
|
|
378
|
+
try {
|
|
379
|
+
const {
|
|
380
|
+
accounts = [],
|
|
381
|
+
chainId = "0x01",
|
|
382
|
+
isUnlocked = false,
|
|
383
|
+
isConnected = false,
|
|
384
|
+
} = (await this.request({
|
|
385
|
+
method: "wallet_getProviderState",
|
|
386
|
+
})) as {
|
|
387
|
+
accounts: string[];
|
|
388
|
+
chainId: string;
|
|
389
|
+
isUnlocked: boolean;
|
|
390
|
+
isConnected: boolean;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// indicate that we've connected, for EIP-1193 compliance
|
|
394
|
+
this.emit("connect", { chainId });
|
|
395
|
+
|
|
396
|
+
this._handleChainChanged({ chainId, isConnected });
|
|
397
|
+
this._handleUnlockStateChanged({ accounts, isUnlocked });
|
|
398
|
+
this._handleAccountsChanged(accounts);
|
|
399
|
+
|
|
400
|
+
window.addEventListener("message", (event: MessageEvent<any>) => {
|
|
401
|
+
const { data, type, method } = event.data;
|
|
402
|
+
if (type === "subscribeWalletEvents" && method) {
|
|
403
|
+
this.subscribeWalletEventsCallback({ data, method });
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
} catch (error) {
|
|
407
|
+
this._log.error(`${this.name}: Failed to get initial state.`, error);
|
|
408
|
+
} finally {
|
|
409
|
+
this._state.initialized = true;
|
|
410
|
+
this.emit("_initialized");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
this.keepAlive();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private subscribeWalletEventsCallback = ({ method, data }: { method: string; data: any }) => {
|
|
417
|
+
if (method === "accountsChanged") {
|
|
418
|
+
const addresses = data?.[chainType] || [];
|
|
419
|
+
const accounts = addresses.map(({ address }: { address: string }) => address);
|
|
420
|
+
this._handleAccountsChanged(accounts, true);
|
|
421
|
+
}
|
|
422
|
+
if (method === "chainChanged") {
|
|
423
|
+
const { chainId, type }: { chainId: string; type: string } = data;
|
|
424
|
+
if (type.indexOf(`${chainType}:`) === 0) {
|
|
425
|
+
const chainIdHex = toHex(parseInt(chainId));
|
|
426
|
+
this._handleChainChanged({ chainId: chainIdHex, isConnected: true });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
private keepAlive = () => {
|
|
432
|
+
this.request({
|
|
433
|
+
method: "keepAlive",
|
|
434
|
+
params: {},
|
|
435
|
+
}).then(() => {
|
|
436
|
+
setTimeout(() => {
|
|
437
|
+
this.keepAlive();
|
|
438
|
+
}, 1000);
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Internal RPC method. Forwards requests to background via the RPC engine.
|
|
444
|
+
* Also remap ids inbound and outbound.
|
|
445
|
+
*
|
|
446
|
+
* @param payload - The RPC request object.
|
|
447
|
+
* @param callback - The consumer's callback.
|
|
448
|
+
*/
|
|
449
|
+
private _rpcRequest(
|
|
450
|
+
payload: UnvalidatedJsonRpcRequest | UnvalidatedJsonRpcRequest[],
|
|
451
|
+
callback: (...args: any[]) => void,
|
|
452
|
+
) {
|
|
453
|
+
let cb = callback;
|
|
454
|
+
|
|
455
|
+
if (!Array.isArray(payload)) {
|
|
456
|
+
if (!payload.jsonrpc) {
|
|
457
|
+
payload.jsonrpc = "2.0";
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (payload.method === "eth_accounts" || payload.method === "eth_requestAccounts") {
|
|
461
|
+
// handle accounts changing
|
|
462
|
+
cb = (err: Error, res: JsonRpcSuccess<string[]>) => {
|
|
463
|
+
this._handleAccountsChanged(res.result || [], payload.method === "eth_accounts");
|
|
464
|
+
callback(err, res);
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return this._rpcEngine.handle(payload as JsonRpcRequest<unknown>, cb);
|
|
468
|
+
}
|
|
469
|
+
return this._rpcEngine.handle(payload as JsonRpcRequest<unknown>[], cb);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* When the provider becomes connected, updates internal state and emits
|
|
474
|
+
* required events. Idempotent.
|
|
475
|
+
*
|
|
476
|
+
* @param chainId - The ID of the newly connected chain.
|
|
477
|
+
* @emits InpageProvider#connect
|
|
478
|
+
*/
|
|
479
|
+
private _handleConnect(chainId: string) {
|
|
480
|
+
if (!this._state.isConnected) {
|
|
481
|
+
this._state.isConnected = true;
|
|
482
|
+
this.emit("connect", { chainId });
|
|
483
|
+
// this._log.debug(messages.info.connected(chainId));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* When the provider becomes disconnected, updates internal state and emits
|
|
489
|
+
* required events. Idempotent with respect to the isRecoverable parameter.
|
|
490
|
+
*
|
|
491
|
+
* Error codes per the CloseEvent status codes as required by EIP-1193:
|
|
492
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
|
|
493
|
+
*
|
|
494
|
+
* @param isRecoverable - Whether the disconnection is recoverable.
|
|
495
|
+
* @param errorMessage - A custom error message.
|
|
496
|
+
* @emits InpageProvider#disconnect
|
|
497
|
+
*/
|
|
498
|
+
private _handleDisconnect(isRecoverable: boolean, errorMessage?: string) {
|
|
499
|
+
if (this._state.isConnected || (!this._state.isPermanentlyDisconnected && !isRecoverable)) {
|
|
500
|
+
this._state.isConnected = false;
|
|
501
|
+
|
|
502
|
+
let error;
|
|
503
|
+
if (isRecoverable) {
|
|
504
|
+
error = new EthereumRpcError(
|
|
505
|
+
1013, // Try again later
|
|
506
|
+
errorMessage || messages.errors.disconnected(),
|
|
507
|
+
);
|
|
508
|
+
// this._log.debug(error);
|
|
509
|
+
} else {
|
|
510
|
+
error = new EthereumRpcError(
|
|
511
|
+
1011, // Internal error
|
|
512
|
+
errorMessage || messages.errors.permanentlyDisconnected(),
|
|
513
|
+
);
|
|
514
|
+
// this._log.error(error);
|
|
515
|
+
this.chainId = null;
|
|
516
|
+
this.networkVersion = null;
|
|
517
|
+
this._state.accounts = null;
|
|
518
|
+
this.selectedAddress = null;
|
|
519
|
+
this._state.isUnlocked = false;
|
|
520
|
+
this._state.isPermanentlyDisconnected = true;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
this.emit("disconnect", error);
|
|
524
|
+
this.emit("close", error); // deprecated
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Called when connection is lost to critical streams.
|
|
530
|
+
*
|
|
531
|
+
* @emits InpageProvider#disconnect
|
|
532
|
+
*/
|
|
533
|
+
private _handleStreamDisconnect(streamName: string, error: Error) {
|
|
534
|
+
logStreamDisconnectWarning(this._log, streamName, error, this);
|
|
535
|
+
this._handleDisconnect(false, error ? error?.message : undefined);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Upon receipt of a new chainId and networkVersion, emits corresponding
|
|
540
|
+
* events and sets relevant public state.
|
|
541
|
+
* Does nothing if neither the chainId nor the networkVersion are different
|
|
542
|
+
* from existing values.
|
|
543
|
+
*
|
|
544
|
+
* @emits InpageProvider#chainChanged
|
|
545
|
+
* @param networkInfo - An object with network info.
|
|
546
|
+
* @param networkInfo.chainId - The latest chain ID.
|
|
547
|
+
* @param networkInfo.networkVersion - The latest network ID.
|
|
548
|
+
*/
|
|
549
|
+
private _handleChainChanged({ chainId, isConnected }: { chainId?: string; isConnected?: boolean } = {}) {
|
|
550
|
+
if (!chainId || typeof chainId !== "string" || !chainId.startsWith("0x")) {
|
|
551
|
+
// this._log.error(`${this.name}: Received invalid network parameters.`, {
|
|
552
|
+
// chainId,
|
|
553
|
+
// });
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (!isConnected) {
|
|
558
|
+
this._handleDisconnect(true);
|
|
559
|
+
} else {
|
|
560
|
+
this._handleConnect(chainId);
|
|
561
|
+
|
|
562
|
+
if (chainId !== this.chainId) {
|
|
563
|
+
this.chainId = chainId;
|
|
564
|
+
if (this._state.initialized) {
|
|
565
|
+
this.emit("chainChanged", this.chainId);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Called when accounts may have changed. Diffs the new accounts value with
|
|
573
|
+
* the current one, updates all state as necessary, and emits the
|
|
574
|
+
* accountsChanged event.
|
|
575
|
+
*
|
|
576
|
+
* @param accounts - The new accounts value.
|
|
577
|
+
* @param isEthAccounts - Whether the accounts value was returned by
|
|
578
|
+
* a call to eth_accounts.
|
|
579
|
+
*/
|
|
580
|
+
private _handleAccountsChanged(accounts: unknown[], isEthAccounts = false): void {
|
|
581
|
+
let _accounts = accounts;
|
|
582
|
+
|
|
583
|
+
if (!Array.isArray(accounts)) {
|
|
584
|
+
this._log.error(`${this.name}: Received invalid accounts parameter.`, accounts);
|
|
585
|
+
_accounts = [];
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
for (const account of accounts) {
|
|
589
|
+
if (typeof account !== "string") {
|
|
590
|
+
this._log.error(`${this.name}: Received non-string account.`, accounts);
|
|
591
|
+
_accounts = [];
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// emit accountsChanged if anything about the accounts array has changed
|
|
597
|
+
if (this._state.accounts !== _accounts) {
|
|
598
|
+
// we should always have the correct accounts even before eth_accounts
|
|
599
|
+
// returns
|
|
600
|
+
if (isEthAccounts && this._state.accounts !== null) {
|
|
601
|
+
// this._log.error(
|
|
602
|
+
// `${this.name}: 'eth_accounts' unexpectedly updated accounts. Please report this bug.`,
|
|
603
|
+
// _accounts,
|
|
604
|
+
// );
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
this._state.accounts = _accounts as string[];
|
|
608
|
+
|
|
609
|
+
// handle selectedAddress
|
|
610
|
+
if (this.selectedAddress !== _accounts[0]) {
|
|
611
|
+
this.selectedAddress = (_accounts[0] as string) || null;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// finally, after all state has been updated, emit the event
|
|
615
|
+
if (this._state.initialized) {
|
|
616
|
+
this.emit("accountsChanged", _accounts);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Upon receipt of a new isUnlocked state, sets relevant public state.
|
|
623
|
+
* Calls the accounts changed handler with the received accounts, or an empty
|
|
624
|
+
* array.
|
|
625
|
+
*
|
|
626
|
+
* Does nothing if the received value is equal to the existing value.
|
|
627
|
+
* There are no lock/unlock events.
|
|
628
|
+
*
|
|
629
|
+
* @param opts - Options bag.
|
|
630
|
+
* @param opts.accounts - The exposed accounts, if any.
|
|
631
|
+
* @param opts.isUnlocked - The latest isUnlocked value.
|
|
632
|
+
*/
|
|
633
|
+
private _handleUnlockStateChanged({ accounts, isUnlocked }: { accounts?: string[]; isUnlocked?: boolean } = {}) {
|
|
634
|
+
if (typeof isUnlocked !== "boolean") {
|
|
635
|
+
this._log.error(`${this.name}: Received invalid isUnlocked parameter.`);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (isUnlocked !== this._state.isUnlocked) {
|
|
640
|
+
this._state.isUnlocked = isUnlocked;
|
|
641
|
+
this._handleAccountsChanged(accounts || []);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Warns of deprecation for the given event, if applicable.
|
|
647
|
+
*/
|
|
648
|
+
private _warnOfDeprecation(eventName: string): void {
|
|
649
|
+
const events = this._state.sentWarnings.events;
|
|
650
|
+
if (events[eventName as WarningEventName] === false) {
|
|
651
|
+
this._log.warn(messages.warnings.events[eventName as WarningEventName]);
|
|
652
|
+
events[eventName as WarningEventName] = true;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
//====================
|
|
657
|
+
// Deprecated Methods
|
|
658
|
+
//====================
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Equivalent to: ethereum.request('eth_requestAccounts')
|
|
662
|
+
*
|
|
663
|
+
* @deprecated Use request({ method: 'eth_requestAccounts' }) instead.
|
|
664
|
+
* @returns A promise that resolves to an array of addresses.
|
|
665
|
+
*/
|
|
666
|
+
enable(): Promise<string[]> {
|
|
667
|
+
return this.connect();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
setConnectedStatus({ connected, address }: { connected: boolean; address: string[] }) {
|
|
671
|
+
this._state.isConnected = connected;
|
|
672
|
+
this._state.accounts = address;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
connect(): Promise<string[]> {
|
|
676
|
+
if (!this._state.sentWarnings.enable) {
|
|
677
|
+
this._log.warn(messages.warnings.enableDeprecation);
|
|
678
|
+
this._state.sentWarnings.enable = true;
|
|
679
|
+
}
|
|
680
|
+
return new Promise<string[]>((resolve, reject) => {
|
|
681
|
+
if (this._state.isConnected) {
|
|
682
|
+
resolve(this._state.accounts as string[]);
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
this.request<string[]>({
|
|
686
|
+
method: "eth_requestAccounts",
|
|
687
|
+
}).then((accounts) => {
|
|
688
|
+
if (accounts) {
|
|
689
|
+
this.emit("connect", {});
|
|
690
|
+
resolve(accounts as string[]);
|
|
691
|
+
} else {
|
|
692
|
+
reject(new Error("No accounts returned"));
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
} catch (error) {
|
|
696
|
+
reject(error);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
disconnect(): Promise<boolean> {
|
|
702
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
703
|
+
try {
|
|
704
|
+
(async () => {
|
|
705
|
+
const { isConnected } = (await this.request({
|
|
706
|
+
method: "wallet_revokePermissions",
|
|
707
|
+
})) as {
|
|
708
|
+
isConnected: boolean;
|
|
709
|
+
};
|
|
710
|
+
if (!isConnected) {
|
|
711
|
+
this._handleDisconnect(true);
|
|
712
|
+
this.emit("disconnect", null);
|
|
713
|
+
}
|
|
714
|
+
resolve(!isConnected);
|
|
715
|
+
})();
|
|
716
|
+
} catch (error) {
|
|
717
|
+
reject(error);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Submits an RPC request for the given method, with the given params.
|
|
724
|
+
*
|
|
725
|
+
* @deprecated Use "request" instead.
|
|
726
|
+
* @param method - The method to request.
|
|
727
|
+
* @param params - Any params for the method.
|
|
728
|
+
* @returns A Promise that resolves with the JSON-RPC response object for the
|
|
729
|
+
* request.
|
|
730
|
+
*/
|
|
731
|
+
send<T>(method: string, params?: T[]): Promise<JsonRpcResponse<T>>;
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Submits an RPC request per the given JSON-RPC request object.
|
|
735
|
+
*
|
|
736
|
+
* @deprecated Use "request" instead.
|
|
737
|
+
* @param payload - A JSON-RPC request object.
|
|
738
|
+
* @param callback - An error-first callback that will receive the JSON-RPC
|
|
739
|
+
* response object.
|
|
740
|
+
*/
|
|
741
|
+
send<T>(payload: JsonRpcRequest<unknown>, callback: (error: Error | null, result?: JsonRpcResponse<T>) => void): void;
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Accepts a JSON-RPC request object, and synchronously returns the cached result
|
|
745
|
+
* for the given method. Only supports 4 specific RPC methods.
|
|
746
|
+
*
|
|
747
|
+
* @deprecated Use "request" instead.
|
|
748
|
+
* @param payload - A JSON-RPC request object.
|
|
749
|
+
* @returns A JSON-RPC response object.
|
|
750
|
+
*/
|
|
751
|
+
send<T>(payload: SendSyncJsonRpcRequest): JsonRpcResponse<T>;
|
|
752
|
+
|
|
753
|
+
send(methodOrPayload: unknown, callbackOrArgs?: unknown): unknown {
|
|
754
|
+
if (!this._state.sentWarnings.send) {
|
|
755
|
+
this._log.warn(messages.warnings.sendDeprecation);
|
|
756
|
+
this._state.sentWarnings.send = true;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (typeof methodOrPayload === "string" && (!callbackOrArgs || Array.isArray(callbackOrArgs))) {
|
|
760
|
+
return new Promise((resolve, reject) => {
|
|
761
|
+
try {
|
|
762
|
+
this._rpcRequest(
|
|
763
|
+
{ method: methodOrPayload, params: callbackOrArgs },
|
|
764
|
+
getRpcPromiseCallback(resolve, reject, false),
|
|
765
|
+
);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
reject(error);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
} else if (methodOrPayload && typeof methodOrPayload === "object" && typeof callbackOrArgs === "function") {
|
|
771
|
+
return this._rpcRequest(
|
|
772
|
+
methodOrPayload as JsonRpcRequest<unknown>,
|
|
773
|
+
callbackOrArgs as (...args: unknown[]) => void,
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
return this._sendSync(methodOrPayload as SendSyncJsonRpcRequest);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Internal backwards compatibility method, used in send.
|
|
781
|
+
*
|
|
782
|
+
* @deprecated
|
|
783
|
+
*/
|
|
784
|
+
private _sendSync(payload: SendSyncJsonRpcRequest) {
|
|
785
|
+
let result;
|
|
786
|
+
switch (payload.method) {
|
|
787
|
+
case "eth_accounts":
|
|
788
|
+
result = this.selectedAddress ? [this.selectedAddress] : [];
|
|
789
|
+
break;
|
|
790
|
+
|
|
791
|
+
case "eth_coinbase":
|
|
792
|
+
result = this.selectedAddress || null;
|
|
793
|
+
break;
|
|
794
|
+
|
|
795
|
+
case "eth_uninstallFilter":
|
|
796
|
+
this._rpcRequest(payload, NOOP);
|
|
797
|
+
result = true;
|
|
798
|
+
break;
|
|
799
|
+
|
|
800
|
+
case "net_version":
|
|
801
|
+
result = this.networkVersion || null;
|
|
802
|
+
break;
|
|
803
|
+
|
|
804
|
+
default:
|
|
805
|
+
throw new Error(messages.errors.unsupportedSync(payload.method));
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return {
|
|
809
|
+
id: payload.id,
|
|
810
|
+
jsonrpc: payload.jsonrpc,
|
|
811
|
+
result,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function validateLoggerObject(logger: ConsoleLike): void {
|
|
817
|
+
if (logger !== console) {
|
|
818
|
+
if (typeof logger === "object") {
|
|
819
|
+
const methodKeys = ["log", "warn", "error", "debug", "info", "trace"];
|
|
820
|
+
for (const key of methodKeys) {
|
|
821
|
+
if (typeof logger[key as keyof ConsoleLike] !== "function") {
|
|
822
|
+
throw new Error(messages.errors.invalidLoggerMethod(key));
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
throw new Error(messages.errors.invalidLoggerObject());
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Initializes a InpageProvider and (optionally) assigns it as window.ethereum.
|
|
833
|
+
*
|
|
834
|
+
* @param options - An options bag.
|
|
835
|
+
* @param options.connectionStream - A Node.js stream.
|
|
836
|
+
* @param options.jsonRpcStreamName - The name of the internal JSON-RPC stream.
|
|
837
|
+
* @param options.maxEventListeners - The maximum number of event listeners.
|
|
838
|
+
* @param options.shouldSendMetadata - Whether the provider should send page metadata.
|
|
839
|
+
* @param options.shouldSetOnWindow - Whether the provider should be set as window.ethereum.
|
|
840
|
+
* @param options.shouldShimWeb3 - Whether a window.web3 shim should be injected.
|
|
841
|
+
* @returns The initialized provider (whether set or not).
|
|
842
|
+
*/
|
|
843
|
+
export function initializeEvmProvider(
|
|
844
|
+
productInfo: IProductInfo,
|
|
845
|
+
{
|
|
846
|
+
jsonRpcStreamName,
|
|
847
|
+
logger = console,
|
|
848
|
+
maxEventListeners = 100,
|
|
849
|
+
shouldSendMetadata = true,
|
|
850
|
+
shouldSetOnWindow = true,
|
|
851
|
+
shouldShimWeb3 = false,
|
|
852
|
+
sendRequest,
|
|
853
|
+
onResponse,
|
|
854
|
+
}: InitializeProviderOptions,
|
|
855
|
+
): EvmProvider {
|
|
856
|
+
let provider = new EvmProvider(productInfo, {
|
|
857
|
+
jsonRpcStreamName,
|
|
858
|
+
logger,
|
|
859
|
+
maxEventListeners,
|
|
860
|
+
shouldSendMetadata,
|
|
861
|
+
sendRequest,
|
|
862
|
+
onResponse,
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
provider = new Proxy(provider, {
|
|
866
|
+
// some common libraries, e.g. web3@1.x, mess with our API
|
|
867
|
+
deleteProperty: () => true,
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
if (shouldSetOnWindow) {
|
|
871
|
+
setGlobalProvider(provider);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (shouldShimWeb3) {
|
|
875
|
+
// shimWeb3(provider, logger);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return provider;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Sets the given provider instance as window.ethereum and dispatches the
|
|
883
|
+
* 'ethereum#initialized' event on window.
|
|
884
|
+
*
|
|
885
|
+
* @param providerInstance - The provider instance.
|
|
886
|
+
*/
|
|
887
|
+
export function setGlobalProvider(providerInstance: EvmProvider): void {
|
|
888
|
+
(window as Record<string, any>).ethereum = providerInstance;
|
|
889
|
+
window.dispatchEvent(new Event("ethereum#initialized"));
|
|
890
|
+
}
|