@tomo-inc/inject-providers 0.0.2

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.
@@ -0,0 +1,49 @@
1
+ import { EvmProvider } from "./metamask";
2
+
3
+ /**
4
+ * If no existing window.web3 is found, this function injects a web3 "shim" to
5
+ * not break dapps that rely on window.web3.currentProvider.
6
+ *
7
+ * @param provider - The provider to set as window.web3.currentProvider.
8
+ * @param log - The logging API to use.
9
+ */
10
+ export default function shimWeb3(provider: EvmProvider, log: any): void {
11
+ let loggedCurrentProvider = false;
12
+ let loggedMissingProperty = false;
13
+
14
+ if (!(window as Record<string, any>).web3) {
15
+ const SHIM_IDENTIFIER = "__isShim__";
16
+
17
+ let web3Shim = { currentProvider: provider };
18
+ Object.defineProperty(web3Shim, SHIM_IDENTIFIER, {
19
+ value: true,
20
+ enumerable: true,
21
+ configurable: false,
22
+ writable: false,
23
+ });
24
+
25
+ web3Shim = new Proxy(web3Shim, {
26
+ get: (target, property, ...args) => {
27
+ if (property === "currentProvider" && !loggedCurrentProvider) {
28
+ loggedCurrentProvider = true;
29
+ } else if (property !== "currentProvider" && property !== SHIM_IDENTIFIER && !loggedMissingProperty) {
30
+ loggedMissingProperty = true;
31
+ provider.request({ method: "wallet_logWeb3ShimUsage" }).catch((error) => {
32
+ log.debug(`Failed to log web3 shim usage.`, error);
33
+ });
34
+ }
35
+ return Reflect.get(target, property, ...args);
36
+ },
37
+ set: (...args) => {
38
+ return Reflect.set(...args);
39
+ },
40
+ });
41
+
42
+ Object.defineProperty(window, "web3", {
43
+ value: web3Shim,
44
+ enumerable: false,
45
+ configurable: true,
46
+ writable: true,
47
+ });
48
+ }
49
+ }
@@ -0,0 +1,81 @@
1
+ import { JsonRpcId, JsonRpcRequest, JsonRpcVersion } from "json-rpc-engine";
2
+ import { ConsoleLike } from "./utils";
3
+
4
+ export interface UnvalidatedJsonRpcRequest {
5
+ id?: JsonRpcId;
6
+ jsonrpc?: JsonRpcVersion;
7
+ method: string;
8
+ params?: unknown;
9
+ }
10
+
11
+ export interface InpageProviderOptions {
12
+ /**
13
+ * The logging API to use.
14
+ */
15
+ logger?: ConsoleLike;
16
+
17
+ jsonRpcStreamName?: any;
18
+
19
+ /**
20
+ * The maximum number of event listeners.
21
+ */
22
+ maxEventListeners?: number;
23
+
24
+ /**
25
+ * Whether the provider should send page metadata.
26
+ */
27
+ shouldSendMetadata?: boolean;
28
+
29
+ sendRequest: (chainType: string, data: any) => void;
30
+
31
+ onResponse: any;
32
+ }
33
+
34
+ export interface RequestArguments {
35
+ /** The RPC method to request. */
36
+ method: string;
37
+
38
+ /** The params of the RPC method, if any. */
39
+ params?: unknown[] | Record<string, unknown>;
40
+ }
41
+
42
+ export interface SendSyncJsonRpcRequest extends JsonRpcRequest<unknown> {
43
+ method: "eth_accounts" | "eth_coinbase" | "eth_uninstallFilter" | "net_version";
44
+ }
45
+
46
+ export interface InternalState {
47
+ sentWarnings: {
48
+ // methods
49
+ enable: boolean;
50
+ experimentalMethods: boolean;
51
+ send: boolean;
52
+ // events
53
+ events: {
54
+ close: boolean;
55
+ data: boolean;
56
+ networkChanged: boolean;
57
+ notification: boolean;
58
+ };
59
+ };
60
+ accounts: null | string[];
61
+ isConnected: boolean;
62
+ isUnlocked: boolean;
63
+ initialized: boolean;
64
+ isPermanentlyDisconnected: boolean;
65
+ }
66
+
67
+ export type WarningEventName = keyof InternalState["sentWarnings"]["events"];
68
+
69
+ export interface InitializeProviderOptions extends InpageProviderOptions {
70
+ /**
71
+ * Whether the provider should be set as window.ethereum.
72
+ */
73
+ shouldSetOnWindow?: boolean;
74
+
75
+ jsonRpcStreamName?: any;
76
+
77
+ /**
78
+ * Whether the window.web3 shim should be set.
79
+ */
80
+ shouldShimWeb3?: boolean;
81
+ }
@@ -0,0 +1,85 @@
1
+ import { ethErrors } from "eth-rpc-errors";
2
+ import { EventEmitter } from "events";
3
+ import { JsonRpcMiddleware, PendingJsonRpcResponse } from "json-rpc-engine";
4
+
5
+ export type Maybe<T> = Partial<T> | null | undefined;
6
+
7
+ export type ConsoleLike = Pick<Console, "log" | "warn" | "error" | "debug" | "info" | "trace">;
8
+
9
+ // utility functions
10
+
11
+ /**
12
+ * json-rpc-engine middleware that logs RPC errors and and validates req.method.
13
+ *
14
+ * @param log - The logging API to use.
15
+ * @returns json-rpc-engine middleware function
16
+ */
17
+ export function createErrorMiddleware(log: ConsoleLike): JsonRpcMiddleware<unknown, unknown> {
18
+ return (req, res, next) => {
19
+ // json-rpc-engine will terminate the request when it notices this error
20
+ if (typeof req.method !== "string" || !req.method) {
21
+ res.error = ethErrors.rpc.invalidRequest({
22
+ message: `The request 'method' must be a non-empty string.`,
23
+ data: req,
24
+ });
25
+ }
26
+
27
+ next((done) => {
28
+ const { error } = res;
29
+ if (!error) {
30
+ return done();
31
+ }
32
+ log.error(`RPC Error: ${error.message}`, error);
33
+ return done();
34
+ });
35
+ };
36
+ }
37
+
38
+ // resolve response.result or response, reject errors
39
+ export const getRpcPromiseCallback =
40
+ (resolve: (value?: any) => void, reject: (error?: Error) => void, unwrapResult = true) =>
41
+ (error: Error, response: PendingJsonRpcResponse<unknown>): void => {
42
+ if (error || response.error) {
43
+ reject(error || (response.error as Error));
44
+ } else {
45
+ !unwrapResult || Array.isArray(response) ? resolve(response) : resolve(response.result);
46
+ }
47
+ };
48
+
49
+ /**
50
+ * Logs a stream disconnection error. Emits an 'error' if given an
51
+ * EventEmitter that has listeners for the 'error' event.
52
+ *
53
+ * @param log - The logging API to use.
54
+ * @param remoteLabel - The label of the disconnected stream.
55
+ * @param error - The associated error to log.
56
+ * @param emitter - The logging API to use.
57
+ */
58
+ export function logStreamDisconnectWarning(
59
+ log: ConsoleLike,
60
+ remoteLabel: string,
61
+ error: Error,
62
+ emitter: EventEmitter,
63
+ ): void {
64
+ let warningMsg = `Lost connection to "${remoteLabel}".`;
65
+ if (error?.stack) {
66
+ warningMsg += `\n${error.stack}`;
67
+ }
68
+ log.warn(warningMsg);
69
+ if (emitter && emitter.listenerCount("error") > 0) {
70
+ emitter.emit("error", warningMsg);
71
+ }
72
+ }
73
+
74
+ export const NOOP = () => undefined;
75
+
76
+ // constants
77
+
78
+ export const EMITTED_NOTIFICATIONS = [
79
+ "eth_subscription", // per eth-json-rpc-filters/subscriptionManager
80
+ ];
81
+
82
+ export const guid = () => {
83
+ const guid = `w-` + (Math.random() * (1 << 30)).toString(16).replace(".", "");
84
+ return guid;
85
+ };
@@ -0,0 +1,37 @@
1
+ declare module "eth-rpc-errors" {
2
+ export class EthereumRpcError<T = any> extends Error {
3
+ code: number;
4
+ data?: T;
5
+ constructor(code: number, message: string, data?: T);
6
+ }
7
+
8
+ export interface EthereumProviderError<T = any> extends Error {
9
+ message: string;
10
+ code: number;
11
+ data?: T;
12
+ }
13
+
14
+ export const ethErrors: {
15
+ rpc: {
16
+ invalidInput: (opts?: { message?: string; data?: any }) => EthereumRpcError;
17
+ resourceNotFound: (opts?: { message?: string; data?: any }) => EthereumRpcError;
18
+ resourceUnavailable: (opts?: { message?: string; data?: any }) => EthereumRpcError;
19
+ transactionRejected: (opts?: { message?: string; data?: any }) => EthereumRpcError;
20
+ methodNotSupported: (opts?: { message?: string; data?: any }) => EthereumRpcError;
21
+ limitExceeded: (opts?: { message?: string; data?: any }) => EthereumRpcError;
22
+ parse: (opts?: { message?: string; data?: any }) => EthereumRpcError;
23
+ invalidRequest: (opts?: { message?: string; data?: any }) => EthereumRpcError;
24
+ methodNotFound: (opts?: { message?: string; data?: any }) => EthereumRpcError;
25
+ invalidParams: (opts?: { message?: string; data?: any }) => EthereumRpcError;
26
+ internal: (opts?: { message?: string; data?: any }) => EthereumRpcError;
27
+ };
28
+ provider: {
29
+ userRejectedRequest: (opts?: { message?: string; data?: any }) => EthereumRpcError;
30
+ unauthorized: (opts?: { message?: string; data?: any }) => EthereumRpcError;
31
+ unsupportedMethod: (opts?: { message?: string; data?: any }) => EthereumRpcError;
32
+ disconnected: (opts?: { message?: string; data?: any }) => EthereumRpcError;
33
+ chainDisconnected: (opts?: { message?: string; data?: any }) => EthereumRpcError;
34
+ custom: (opts: { code: number; message: string; data?: any }) => EthereumRpcError;
35
+ };
36
+ };
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { BtcProvider } from "./btc";
2
+ import { DogecoinProvider } from "./dogecoin";
3
+ import { EvmProvider } from "./evm";
4
+ import { SolanaProvider } from "./solana";
5
+ import { TronProvider } from "./tron";
6
+
7
+ export { BtcProvider, DogecoinProvider, EvmProvider, SolanaProvider, TronProvider };
@@ -0,0 +1,3 @@
1
+ import { PhantomProvider } from "./phantom";
2
+
3
+ export { PhantomProvider as SolanaProvider };
@@ -0,0 +1,424 @@
1
+ import { ethErrors } from "eth-rpc-errors";
2
+ import { EventEmitter } from "events";
3
+
4
+ import { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js";
5
+ import bs58 from "bs58";
6
+
7
+ import { ReadyPromise, domReadyCall, getDappInfo, hexToUint8Array, isHexString } from "../utils/index";
8
+ import { hexToTx, txToHex } from "./utils";
9
+
10
+ import { ChainTypes, IConnectors, IProductInfo } from "../types";
11
+ import { OriginTransaction, SignInInput } from "./types";
12
+ const chainType = ChainTypes.SOL;
13
+
14
+ interface StateProvider {
15
+ accounts: any[] | null;
16
+ isConnected: boolean;
17
+ isUnlocked: boolean;
18
+ initialized: boolean;
19
+ isPermanentlyDisconnected: boolean;
20
+ }
21
+
22
+ export class PhantomProvider extends EventEmitter {
23
+ _isUnlocked = false;
24
+ name = "";
25
+ icon = "";
26
+ publicKey: PublicKey | null = null;
27
+ sendRequest: (chainType: string, data: any) => void;
28
+ onResponse: any;
29
+ events = {
30
+ connect: true,
31
+ disconnect: true,
32
+ accountChanged: true,
33
+ };
34
+
35
+ _state: StateProvider = {
36
+ accounts: null,
37
+ isConnected: false,
38
+ isUnlocked: false,
39
+ initialized: false,
40
+ isPermanentlyDisconnected: false,
41
+ };
42
+
43
+ // private _pushEventHandlers: PushEventHandlers;
44
+ private _requestPromise = new ReadyPromise(0);
45
+
46
+ constructor(productInfo: IProductInfo, { sendRequest, onResponse }: IConnectors) {
47
+ super();
48
+ this.setMaxListeners(100);
49
+ this.name = productInfo.name;
50
+ this.icon = productInfo.icon;
51
+ this.sendRequest = sendRequest;
52
+ this.onResponse = onResponse;
53
+
54
+ this.initialize();
55
+ this._handleAccountsChanged = this._handleAccountsChanged.bind(this);
56
+ // this._pushEventHandlers = new PushEventHandlers(this);
57
+
58
+ this.on("connect", () => {
59
+ this._state.isConnected = true;
60
+ });
61
+ }
62
+
63
+ initialize = async () => {
64
+ //dapp tab switch send data;
65
+ document.addEventListener("visibilitychange", async () => {
66
+ if (document.visibilityState === "visible") {
67
+ this._request({
68
+ method: "wallet_sendDomainMetadata",
69
+ params: await getDappInfo(),
70
+ });
71
+ }
72
+ });
73
+
74
+ // this._bcm.connect().on("message", this._handleBackgroundMessage);
75
+ domReadyCall(async () => {
76
+ this._request({
77
+ method: "wallet_sendDomainMetadata",
78
+ params: await getDappInfo(),
79
+ });
80
+ });
81
+
82
+ window.addEventListener("message", (event: MessageEvent<any>) => {
83
+ const { data, type, method } = event.data;
84
+ if (type === "subscribeWalletEvents" && method) {
85
+ this.subscribeWalletEventsCallback({ data, method });
86
+ }
87
+ });
88
+
89
+ this.keepAlive();
90
+ this._state.initialized = true;
91
+ };
92
+
93
+ subscribeWalletEventsCallback = ({ method, data }: { method: string; data: any }) => {
94
+ if (method === "accountsChanged") {
95
+ const accounts = data?.[chainType];
96
+ this._handleAccountsChanged(accounts);
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Sending a message to the extension to receive will keep the service worker alive.
102
+ */
103
+ private keepAlive = () => {
104
+ this._request({
105
+ method: "keepAlive",
106
+ params: {},
107
+ }).then(() => {
108
+ setTimeout(() => {
109
+ this.keepAlive();
110
+ }, 1000);
111
+ });
112
+ };
113
+
114
+ on(eventName: string, listener: (...args: unknown[]) => void) {
115
+ if (!this.events[eventName as keyof typeof this.events]) {
116
+ throw Error("${eventName} no supported.");
117
+ }
118
+ return super.on(eventName, listener);
119
+ }
120
+
121
+ private _handleAccountsChanged(accounts: any[]): void {
122
+ let _accounts = accounts;
123
+
124
+ if (!Array.isArray(accounts)) {
125
+ console.error(`${this.name}: Received invalid accounts parameter.`, accounts);
126
+ _accounts = [];
127
+ }
128
+
129
+ if (this._state.accounts !== _accounts) {
130
+ this._state.accounts = _accounts;
131
+
132
+ // handle selectedAddress
133
+ // if (this._state.publicKey !== _accounts[0]?.publicKey) {
134
+ // this._state.publicKey = _accounts[0]?.publicKey;
135
+ // }
136
+ const { publicKey, address } = _accounts[0] || {};
137
+ const currentPublicKey = publicKey || address;
138
+ if (currentPublicKey) {
139
+ this.publicKey = new PublicKey(currentPublicKey);
140
+ }
141
+
142
+ // finally, after all state has been updated, emit the event
143
+ if (this._state.initialized) {
144
+ this.emit("accountChanged", this.publicKey);
145
+ }
146
+ }
147
+ }
148
+
149
+ _request = async (data: any, responseAdaptor?: any) => {
150
+ if (!data || !data.method) {
151
+ throw ethErrors.rpc.invalidRequest();
152
+ }
153
+
154
+ const dappInfo = await getDappInfo();
155
+
156
+ this.sendRequest(chainType, { ...data, dappInfo });
157
+
158
+ return this.onResponse(data).then((res: any) => {
159
+ let { data } = res || {};
160
+ if (data && data.publicKey) {
161
+ data = {
162
+ ...data,
163
+ ...{ publicKey: new PublicKey(data.publicKey) },
164
+ };
165
+ }
166
+ if (data && data.address && !data.publicKey) {
167
+ data = {
168
+ ...data,
169
+ ...{ address: new PublicKey(data.address) },
170
+ };
171
+ }
172
+
173
+ if (responseAdaptor) {
174
+ return responseAdaptor(data);
175
+ }
176
+
177
+ return data;
178
+ });
179
+ };
180
+
181
+ request = async ({ method, params }: { method: string; params: any }, responseAdaptor: any) => {
182
+ if (method === "signMessage") {
183
+ const { message, display = "utf8" } = params;
184
+ params = {
185
+ ...params,
186
+ ...{
187
+ message: new TextDecoder().decode(message),
188
+ display,
189
+ },
190
+ };
191
+ }
192
+ const dappInfo = await getDappInfo();
193
+ return this._request(
194
+ {
195
+ method,
196
+ params: { ...dappInfo, ...(params || {}) },
197
+ },
198
+ responseAdaptor,
199
+ );
200
+ };
201
+
202
+ //phantom api
203
+ connect = async (params?: { onlyIfTrusted: boolean }) => {
204
+ const dappInfo = await getDappInfo();
205
+ return this._request(
206
+ {
207
+ method: "connect",
208
+ params: { ...dappInfo, ...(params || {}) },
209
+ },
210
+ (data: any) => {
211
+ this.emit("connect", true);
212
+ this.publicKey = data?.publicKey;
213
+ return data;
214
+ },
215
+ );
216
+ };
217
+
218
+ disconnect = async () => {
219
+ return this._request(
220
+ {
221
+ method: "disconnect",
222
+ params: await getDappInfo(),
223
+ },
224
+ (data: any) => {
225
+ this.emit("disconnect", true);
226
+ this.publicKey = null;
227
+ return data;
228
+ },
229
+ );
230
+ };
231
+
232
+ getAccount = async () => {
233
+ return this._request({
234
+ method: "getAccount",
235
+ params: {},
236
+ });
237
+ };
238
+
239
+ signMessage = async (message: Uint8Array, display: "utf8" | "hex") => {
240
+ return this._request(
241
+ {
242
+ method: "signMessage",
243
+ params: {
244
+ message: new TextDecoder().decode(message),
245
+ display,
246
+ },
247
+ },
248
+ (data: any) => {
249
+ if (data && data.signedMessage) {
250
+ data = {
251
+ ...data,
252
+ ...{ signedMessage: new TextEncoder().encode(data.signedMessage) },
253
+ };
254
+ }
255
+ if (data && data.signature) {
256
+ const signature = hexToUint8Array(data.signature);
257
+ data = {
258
+ ...data,
259
+ ...{ signature },
260
+ };
261
+ }
262
+ return data;
263
+ },
264
+ );
265
+ };
266
+
267
+ //doc: https://github.com/phantom/sign-in-with-solana
268
+ signIn = async (params?: SignInInput) => {
269
+ const { domain = window.location.host, statement, nonce, version, issuedAt, resources = [] } = params || {};
270
+ const address = this.publicKey ? this.publicKey.toString() : "";
271
+
272
+ let message = `${domain} wants you to sign in with your Solana account:\n${address}`;
273
+
274
+ if (statement) {
275
+ message += `\n\n${statement}`;
276
+ }
277
+ if (version) {
278
+ message += `\n\nVersion:${version}`;
279
+ }
280
+ if (nonce) {
281
+ message += `\nNonce:${nonce}`;
282
+ }
283
+ if (issuedAt) {
284
+ message += `\nIssued At:${issuedAt}`;
285
+ }
286
+ if (resources.length > 0) {
287
+ message += `\nResources:`;
288
+ resources.forEach((resource) => {
289
+ message += `\n- ${resource}`;
290
+ });
291
+ }
292
+
293
+ return this._request(
294
+ {
295
+ method: "signIn",
296
+ params: {
297
+ message,
298
+ },
299
+ },
300
+ (data: any) => {
301
+ if (data && data.signedMessage) {
302
+ data = {
303
+ ...data,
304
+ ...{ signedMessage: new TextEncoder().encode(data.signedMessage) },
305
+ };
306
+ }
307
+ if (data && data.signature) {
308
+ const signature = hexToUint8Array(data.signature);
309
+ data = {
310
+ ...data,
311
+ ...{ signature },
312
+ };
313
+ }
314
+ return data;
315
+ },
316
+ );
317
+ };
318
+
319
+ signTransaction = async (tx: VersionedTransaction | Transaction) => {
320
+ return this._request(
321
+ {
322
+ method: "signTransaction",
323
+ params: {
324
+ rawTransaction: txToHex(tx),
325
+ },
326
+ },
327
+ (data: any) => {
328
+ return hexToTx(data.signature);
329
+ },
330
+ );
331
+ };
332
+
333
+ signAllTransactions = async (txs: (VersionedTransaction | Transaction)[]) => {
334
+ const rawTransactions = [];
335
+ for (const tx of txs) {
336
+ rawTransactions.push(txToHex(tx));
337
+ }
338
+ return this._request(
339
+ {
340
+ method: "signAllTransactions",
341
+ params: {
342
+ rawTransactions,
343
+ },
344
+ },
345
+ (data: any) => {
346
+ return data;
347
+ },
348
+ );
349
+ };
350
+
351
+ signAndSendTransaction = async (tx: VersionedTransaction | Transaction) => {
352
+ return this._request(
353
+ {
354
+ method: "signAndSendTransaction",
355
+ params: {
356
+ rawTransaction: txToHex(tx),
357
+ },
358
+ },
359
+ (data: any) => {
360
+ if (data && data.signature) {
361
+ const signature = bs58.decode(data.signature);
362
+ data = {
363
+ ...data,
364
+ ...{ signature },
365
+ };
366
+ }
367
+ return data;
368
+ },
369
+ );
370
+ };
371
+
372
+ signAndSendAllTransactions = async (txs: (VersionedTransaction | Transaction)[]) => {
373
+ const rawTransactions = [];
374
+ for (const tx of txs) {
375
+ rawTransactions.push(txToHex(tx));
376
+ }
377
+ return this._request({
378
+ method: "signAndSendAllTransactions",
379
+ params: {
380
+ rawTransactions,
381
+ },
382
+ });
383
+ };
384
+
385
+ sendSolana = async (tx: OriginTransaction) => {
386
+ return this._request(
387
+ {
388
+ method: "sendSolana",
389
+ intent: true,
390
+ params: tx,
391
+ },
392
+ (data: any) => {
393
+ if (data && data.signature) {
394
+ const signature = isHexString(data.signature) ? hexToUint8Array(data.signature) : bs58.decode(data.signature);
395
+ data = {
396
+ ...data,
397
+ ...{ signature },
398
+ };
399
+ }
400
+ return data;
401
+ },
402
+ );
403
+ };
404
+
405
+ sendToken = async (tx: OriginTransaction) => {
406
+ return this._request(
407
+ {
408
+ method: "sendToken",
409
+ intent: true,
410
+ params: tx,
411
+ },
412
+ (data: any) => {
413
+ if (data && data.signature) {
414
+ const signature = isHexString(data.signature) ? hexToUint8Array(data.signature) : bs58.decode(data.signature);
415
+ data = {
416
+ ...data,
417
+ ...{ signature },
418
+ };
419
+ }
420
+ return data;
421
+ },
422
+ );
423
+ };
424
+ }