Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move RPC calls to background service worker #95

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/lib/wallet/evm/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import config from "@unstoppabledomains/config";
import {getProviderUrl} from "@unstoppabledomains/ui-components/lib/wallet/evm/provider";

export const getWeb3Provider = (chainId: number) => {
// inspect configurations of supported chains
const chainSymbol = Object.keys(config.BLOCKCHAINS).find(k => {
return (
config.WALLETS.CHAINS.SEND.map(c =>
c.split("/")[0].toUpperCase(),
).includes(k.toUpperCase()) &&
config.BLOCKCHAINS[k as keyof typeof config.BLOCKCHAINS].CHAIN_ID ===
chainId
chainId
);
});
if (!chainSymbol) {
Expand Down
85 changes: 82 additions & 3 deletions src/scripts/liteWalletProvider/background.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* eslint-disable promise/prefer-await-to-then */
import {utils as web3utils} from "web3";

import config from "../../config";
import {StorageSyncKey, chromeStorageSet} from "../../lib/chromeStorage";
import {Logger} from "../../lib/logger";
Expand All @@ -8,6 +10,7 @@ import {
getConnectedSite,
setConnectedSite,
} from "../../lib/wallet/evm/connection";
import {getWeb3Provider} from "../../lib/wallet/evm/provider";
import {getWalletPreferences} from "../../lib/wallet/preferences";
import {sleep} from "../../lib/wallet/sleep";
import {waitForXmtpMessages} from "../../lib/xmtp/listener";
Expand All @@ -17,6 +20,7 @@ import {
NotConnectedError,
ProviderEventResponse,
ProviderRequest,
RpcRequest,
getResponseType,
isExternalRequestType,
isInternalRequestType,
Expand Down Expand Up @@ -82,6 +86,9 @@ export const backgroundEventListener = (
void waitForXmtpMessages(request.params[0]);
}
break;
case "rpcRequest":
void handleRpcRequest(request, popupResponseHandler);
break;
}
return true;
}
Expand Down Expand Up @@ -311,9 +318,18 @@ const handleResponse = (
popupResponseHandler: (response: ProviderEventResponse) => void,
response: ProviderEventResponse,
) => {
// log the incoming event
Logger.log("Handling event complete", JSON.stringify(response));
popupResponseHandler(response);
// make the response bigint safe
const normalizedResponse = JSON.parse(
JSON.stringify(response, (key, value) =>
typeof value === "bigint" ? web3utils.numberToHex(value) : value,
),
);

// log the event response
Logger.log("Handling event complete", JSON.stringify(normalizedResponse));

// call the response callback with result
popupResponseHandler(normalizedResponse);
};

const handleTabStatus = async (tab: chrome.tabs.Tab) => {
Expand Down Expand Up @@ -377,6 +393,69 @@ const handlePrepareXmtp = async (
}
};

const handleRpcRequest = async (
request: ProviderRequest,
popupResponseHandler: (response: ProviderEventResponse) => void,
) => {
try {
// requires at least two parameters
if (!request?.params || request.params.length < 2) {
throw new Error("invalid parameters");
}

// parse the RPC parameters
const chainId = request.params[0] as number;
const rpcMethod = request.params[1] as RpcRequest;
const rpcParams = request.params.length > 2 ? request.params.slice(2) : [];

// get web3 object
const web3 = getWeb3Provider(chainId);

// call the web3 provider method
let result: any | undefined;
switch (rpcMethod) {
case "getBlockNumber":
result = web3utils.numberToHex(await web3.eth.getBlockNumber());
break;
case "estimateGas":
result = web3utils.numberToHex(
await web3.eth.estimateGas(rpcParams[0]),
);
break;
case "getTransaction":
result = await web3.eth.getTransaction(rpcParams[0]);
break;
case "getTransactionReceipt":
result = await web3.eth.getTransactionReceipt(rpcParams[0]);
break;
case "call":
result = await web3.eth.call(
rpcParams[0],
rpcParams.length > 1 ? rpcParams[1] : undefined,
);
break;
default:
throw new Error(`RPC method not supported: ${rpcMethod}`);
}

// parse the parameters and prepare the account
handleResponse(popupResponseHandler, {
type: getResponseType("rpcRequest"),
response: result,
});
} catch (e) {
// provide an error response
Logger.warn(
"error handling RPC request",
JSON.stringify({request, error: String(e)}),
);
handleResponse(popupResponseHandler, {
type: getResponseType("rpcRequest"),
error: String(e),
});
}
};

const handleFetchResolution = async (
request: ProviderRequest,
popupResponseHandler: (response: ProviderEventResponse) => void,
Expand Down
112 changes: 53 additions & 59 deletions src/scripts/liteWalletProvider/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {Logger} from "../../lib/logger";
import {isEthAddress} from "../../lib/sherlock/matcher";
import {ResolutionData} from "../../lib/sherlock/types";
import {announceProvider} from "../../lib/wallet/evm/eip6963";
import {getWeb3Provider} from "../../lib/wallet/evm/provider";
import {EIP_712_KEY, TypedMessage} from "../../types/wallet/eip712";
import {WalletPreferences} from "../../types/wallet/preferences";
import {
Expand All @@ -35,6 +34,7 @@ import {
ProviderMethodsWithPrompt,
ProviderResponse,
ResponseType,
RpcRequest,
UnexpectedResponseError,
UnsupportedMethodError,
isClientSideRequestType,
Expand Down Expand Up @@ -174,19 +174,19 @@ class LiteWalletProvider extends EventEmitter {
break;
// Transaction methods
case "eth_blockNumber":
result = await this.handleGetBlockNumber();
result = await this.handleRpcGetBlockNumber();
break;
case "eth_call":
result = await this.handleReadOnlyCall(clone(request.params));
result = await this.handleRpcCall(clone(request.params));
break;
case "eth_estimateGas":
result = await this.handleEstimateGas(clone(request.params));
result = await this.handleRpcEstimateGas(clone(request.params));
break;
case "eth_getTransactionByHash":
result = await this.handleGetTransactionByHash(clone(request.params));
result = await this.handleRpcGetTransaction(clone(request.params));
break;
case "eth_getTransactionReceipt":
result = await this.handleGetTransactionReceipt(
result = await this.handleRpcGetTransactionReceipt(
clone(request.params),
);
break;
Expand Down Expand Up @@ -516,75 +516,69 @@ class LiteWalletProvider extends EventEmitter {
return [address];
}

private async handleGetBlockNumber() {
// retrieve the web3 provider for connected chain
private async handleRpcGetBlockNumber() {
const chainIdHex = await this.handleGetConnectedChainIds();
const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number);

// query the block number
const block = await web3.eth.getBlockNumber();
return web3utils.numberToHex(block);
const chainId = web3utils.hexToNumber(chainIdHex) as number;
return await this.handleRpcMethod(chainId, "getBlockNumber", []);
}

private async handleEstimateGas(params: any[]) {
private async handleRpcEstimateGas(params: any[]) {
// retrieve the web3 provider for connected chain
const chainIdHex = await this.handleGetConnectedChainIds();
const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number);

// validate any Tx parameters have been passed
if (!params || params.length === 0) {
throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError);
}

// query the estimated gas
return web3utils.numberToHex(await web3.eth.estimateGas(params[0]));
const chainId = web3utils.hexToNumber(chainIdHex) as number;
return await this.handleRpcMethod(chainId, "estimateGas", params);
}

private async handleGetTransactionByHash(params: any[]) {
// retrieve the web3 provider for connected chain
private async handleRpcGetTransaction(params: any[]) {
const chainIdHex = await this.handleGetConnectedChainIds();
const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number);

// validate any Tx parameters have been passed
if (!params || params.length === 0) {
throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError);
}

// query the requested transaction
return await web3.eth.getTransaction(params[0]);
const chainId = web3utils.hexToNumber(chainIdHex) as number;
return await this.handleRpcMethod(chainId, "getTransaction", params);
}

private async handleGetTransactionReceipt(params: any[]) {
// retrieve the web3 provider for connected chain
private async handleRpcGetTransactionReceipt(params: any[]) {
const chainIdHex = await this.handleGetConnectedChainIds();
const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number);

// validate any Tx parameters have been passed
if (!params || params.length === 0) {
throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError);
}

// query the requested transaction
return await web3.eth.getTransactionReceipt(params[0]);
const chainId = web3utils.hexToNumber(chainIdHex) as number;
return await this.handleRpcMethod(chainId, "getTransactionReceipt", params);
}

private async handleReadOnlyCall(params: any[]) {
// retrieve the web3 provider for connected chain
private async handleRpcCall(params: any[]) {
const chainIdHex = await this.handleGetConnectedChainIds();
const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number);
const chainId = web3utils.hexToNumber(chainIdHex) as number;
return await this.handleRpcMethod(chainId, "call", params);
}

// validate any Tx parameters have been passed
if (!params || params.length === 0) {
throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError);
}
private async handleRpcMethod(
chainId: number,
method: RpcRequest,
params: any[],
) {
const rpcResponse = new Promise((resolve, reject) => {
document.dispatchEvent(
new ProviderEvent("rpcRequest", {detail: [chainId, method, ...params]}),
);
this.addEventListener("rpcResponse", (event: ProviderResponse) => {
if (event.detail.error) {
reject(
new EthereumProviderError(
PROVIDER_CODE_USER_ERROR,
event.detail.error,
),
);
} else if ("response" in event.detail) {
resolve(event.detail.response);
} else {
reject(
new EthereumProviderError(
PROVIDER_CODE_USER_ERROR,
UnexpectedResponseError,
),
);
}
});
});

// query the requested transaction
return await web3.eth.call(
// call parameters
params[0],
// optional block number
params.length > 1 ? params[1] : undefined,
);
// return list of accounts
return await rpcResponse;
}

private async handleGetConnectedChainIds() {
Expand Down
11 changes: 10 additions & 1 deletion src/types/wallet/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export const ProviderMethodsWithPrompt: ProviderMethod[] = [
"eth_sendTransaction",
];

export type RpcRequest =
| "call"
| "estimateGas"
| "getBlockNumber"
| "getTransaction"
| "getTransactionReceipt";

// define required EIP-1193 events
export type Eip1193Event =
| "accountsChanged"
Expand Down Expand Up @@ -74,6 +81,7 @@ export const InternalMessageTypes = [
"signInRequest",
"xmtpReadyRequest",
"prepareXmtpRequest",
"rpcRequest",
] as const;
export type InternalRequestType = (typeof InternalMessageTypes)[number];
export const isInternalRequestType = (v: string): v is InternalRequestType => {
Expand Down Expand Up @@ -113,7 +121,8 @@ export type ResponseType =
| "getPreferencesResponse"
| "getDomainProfileResponse"
| "getResolutionResponse"
| "prepareXmtpResponse";
| "prepareXmtpResponse"
| "rpcResponse";
export const isResponseType = (v: string): v is ResponseType => {
return isExternalRequestType(v.replaceAll("Response", "Request"));
};
Expand Down
Loading