Implement EIP-1193 provider for dApp connectivity
All checks were successful
check / check (push) Successful in 13s
All checks were successful
check / check (push) Successful in 13s
Three-part architecture: - inpage.js: creates window.ethereum in page context with request(), on(), send(), sendAsync(), enable() methods. Sets isMetaMask=true for compatibility. - content/index.js: bridge between page and extension via postMessage (page<->content) and runtime.sendMessage (content<->background). - background/index.js: handles RPC routing. Proxies read-only methods (eth_call, eth_getBalance, etc.) to configured RPC. Handles eth_requestAccounts (auto-connect for now), wallet_switchEthereumChain (mainnet only), and returns informative errors for unimplemented signing methods. Manifests updated with web_accessible_resources for inpage.js. Build updated to bundle inpage.js as a separate output file.
This commit is contained in:
@@ -1,2 +1,218 @@
|
||||
// AutistMask background service worker
|
||||
// TODO: wallet management, message routing, provider implementation
|
||||
// Handles EIP-1193 RPC requests from content scripts and proxies
|
||||
// non-sensitive calls to the configured Ethereum JSON-RPC endpoint.
|
||||
|
||||
const CHAIN_ID = "0x1";
|
||||
const DEFAULT_RPC = "https://eth.llamarpc.com";
|
||||
|
||||
const storageApi =
|
||||
typeof browser !== "undefined"
|
||||
? browser.storage.local
|
||||
: chrome.storage.local;
|
||||
const runtime =
|
||||
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
|
||||
|
||||
// Connected sites: { origin: [address, ...] }
|
||||
const connectedSites = {};
|
||||
|
||||
async function getState() {
|
||||
const result = await storageApi.get("autistmask");
|
||||
return result.autistmask || { wallets: [], rpcUrl: DEFAULT_RPC };
|
||||
}
|
||||
|
||||
async function getAccounts() {
|
||||
const state = await getState();
|
||||
const accounts = [];
|
||||
for (const wallet of state.wallets) {
|
||||
for (const addr of wallet.addresses) {
|
||||
accounts.push(addr.address);
|
||||
}
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
async function getRpcUrl() {
|
||||
const state = await getState();
|
||||
return state.rpcUrl || DEFAULT_RPC;
|
||||
}
|
||||
|
||||
// Proxy an RPC call to the Ethereum node
|
||||
async function proxyRpc(method, params) {
|
||||
const rpcUrl = await getRpcUrl();
|
||||
const resp = await fetch(rpcUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method,
|
||||
params,
|
||||
}),
|
||||
});
|
||||
const json = await resp.json();
|
||||
if (json.error) {
|
||||
throw new Error(json.error.message || "RPC error");
|
||||
}
|
||||
return json.result;
|
||||
}
|
||||
|
||||
// Methods that are safe to proxy directly to the RPC node
|
||||
const PROXY_METHODS = [
|
||||
"eth_blockNumber",
|
||||
"eth_call",
|
||||
"eth_chainId",
|
||||
"eth_estimateGas",
|
||||
"eth_gasPrice",
|
||||
"eth_getBalance",
|
||||
"eth_getBlockByHash",
|
||||
"eth_getBlockByNumber",
|
||||
"eth_getCode",
|
||||
"eth_getLogs",
|
||||
"eth_getStorageAt",
|
||||
"eth_getTransactionByHash",
|
||||
"eth_getTransactionCount",
|
||||
"eth_getTransactionReceipt",
|
||||
"eth_maxPriorityFeePerGas",
|
||||
"eth_sendRawTransaction",
|
||||
"net_version",
|
||||
"web3_clientVersion",
|
||||
"eth_feeHistory",
|
||||
"eth_getBlockTransactionCountByHash",
|
||||
"eth_getBlockTransactionCountByNumber",
|
||||
];
|
||||
|
||||
async function handleRpc(method, params, origin) {
|
||||
// Methods that need wallet involvement
|
||||
if (method === "eth_requestAccounts" || method === "eth_accounts") {
|
||||
const accounts = await getAccounts();
|
||||
if (accounts.length === 0) {
|
||||
return { error: { message: "No accounts available" } };
|
||||
}
|
||||
// Auto-connect for now (approval flow is a future TODO)
|
||||
connectedSites[origin] = accounts;
|
||||
return { result: accounts };
|
||||
}
|
||||
|
||||
if (method === "eth_chainId") {
|
||||
return { result: CHAIN_ID };
|
||||
}
|
||||
|
||||
if (method === "net_version") {
|
||||
return { result: "1" };
|
||||
}
|
||||
|
||||
if (method === "wallet_switchEthereumChain") {
|
||||
const chainId = params?.[0]?.chainId;
|
||||
if (chainId === CHAIN_ID) {
|
||||
return { result: null };
|
||||
}
|
||||
return {
|
||||
error: {
|
||||
code: 4902,
|
||||
message: "AutistMask only supports Ethereum mainnet.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (method === "wallet_addEthereumChain") {
|
||||
return {
|
||||
error: {
|
||||
code: 4902,
|
||||
message: "AutistMask only supports Ethereum mainnet.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (method === "wallet_requestPermissions") {
|
||||
const accounts = await getAccounts();
|
||||
if (accounts.length === 0) {
|
||||
return { error: { message: "No accounts available" } };
|
||||
}
|
||||
connectedSites[origin] = accounts;
|
||||
return {
|
||||
result: [
|
||||
{
|
||||
parentCapability: "eth_accounts",
|
||||
caveats: [
|
||||
{
|
||||
type: "restrictReturnedAccounts",
|
||||
value: accounts,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (method === "wallet_getPermissions") {
|
||||
const accounts = connectedSites[origin] || [];
|
||||
if (accounts.length === 0) {
|
||||
return { result: [] };
|
||||
}
|
||||
return {
|
||||
result: [
|
||||
{
|
||||
parentCapability: "eth_accounts",
|
||||
caveats: [
|
||||
{
|
||||
type: "restrictReturnedAccounts",
|
||||
value: accounts,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (method === "personal_sign" || method === "eth_sign") {
|
||||
// TODO: implement signature approval flow
|
||||
return {
|
||||
error: { message: "Signing not yet implemented in AutistMask." },
|
||||
};
|
||||
}
|
||||
|
||||
if (method === "eth_signTypedData_v4" || method === "eth_signTypedData") {
|
||||
// TODO: implement typed data signing
|
||||
return {
|
||||
error: {
|
||||
message:
|
||||
"Typed data signing not yet implemented in AutistMask.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (method === "eth_sendTransaction") {
|
||||
// TODO: implement transaction signing approval flow
|
||||
// For now, return an error directing the user to use the popup
|
||||
return {
|
||||
error: {
|
||||
message:
|
||||
"Transaction signing via dApps not yet implemented. Use the AutistMask popup to send transactions.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Proxy safe read-only methods to the RPC node
|
||||
if (PROXY_METHODS.includes(method)) {
|
||||
try {
|
||||
const result = await proxyRpc(method, params);
|
||||
return { result };
|
||||
} catch (e) {
|
||||
return { error: { message: e.message } };
|
||||
}
|
||||
}
|
||||
|
||||
return { error: { message: "Unsupported method: " + method } };
|
||||
}
|
||||
|
||||
// Listen for messages from content scripts
|
||||
runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (msg.type !== "AUTISTMASK_RPC") return;
|
||||
|
||||
handleRpc(msg.method, msg.params, msg.origin).then((response) => {
|
||||
sendResponse(response);
|
||||
});
|
||||
|
||||
// Return true to indicate async response
|
||||
return true;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user