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

EVM improvements #3499

Merged
merged 8 commits into from
Feb 27, 2025
6 changes: 0 additions & 6 deletions src/composables/aeSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
RPC_STATUS,
Encoded,
} from '@aeternity/aepp-sdk';
import { isEmpty } from 'lodash-es';

import type {
INetwork,
Expand Down Expand Up @@ -82,7 +81,6 @@ export function useAeSdk() {
onNetworkChange,
} = useNetworks();
const {
activeAccount,
accountsAddressList,
getLastActiveProtocolAccount,
onAccountChange,
Expand Down Expand Up @@ -156,8 +154,6 @@ export function useAeSdk() {
const aepp = aeppInfo[aeppId];
const host = IS_OFFSCREEN_TAB ? aepp.origin : origin;
if (await checkOrAskPermission(METHODS.subscribeAddress, host)) {
// Waiting for activeAccount to sync back to the background
await watchUntilTruthy(() => !isEmpty(activeAccount.value));
return getLastActiveProtocolAccount(PROTOCOLS.aeternity)!.address;
}
return Promise.reject(new RpcRejectedByUserError());
Expand All @@ -166,8 +162,6 @@ export function useAeSdk() {
const aepp = aeppInfo[aeppId];
const host = IS_OFFSCREEN_TAB ? aepp.origin : origin;
if (await checkOrAskPermission(METHODS.address, host)) {
// Waiting for activeAccount to sync back to the background
await watchUntilTruthy(() => !isEmpty(activeAccount.value));
return accountsAddressList.value;
}
return Promise.reject(new RpcRejectedByUserError());
Expand Down
17 changes: 15 additions & 2 deletions src/composables/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { METHODS } from '@aeternity/aepp-sdk';
import { isEmpty } from 'lodash-es';

import type {
IAppData,
Expand All @@ -24,10 +25,11 @@ import {
STORAGE_KEYS,
PROTOCOLS,
} from '@/constants';
import { getCleanModalOptions } from '@/utils';
import { getCleanModalOptions, watchUntilTruthy } from '@/utils';
import { aettosToAe, isTxOfASupportedType } from '@/protocols/aeternity/helpers';
import { openPopup } from '@/offscreen/popupHandler';
import migratePermissionsVuexToComposable from '@/migrations/003-permissions-vuex-to-composable';
import { useAccounts } from '@/composables';
import { useStorageRef } from './storageRef';
import { useModals } from './modals';

Expand Down Expand Up @@ -164,11 +166,22 @@ export function usePermissions() {
): Promise<boolean> {
let app: IAppData | undefined;
let props = getCleanModalOptions<typeof modalProps>(modalProps);
const { activeAccount } = useAccounts();

if (fullUrl) {
const url = new URL(fullUrl);
if (checkPermission(url.host, method, modalProps.tx)) {
return true;
try {
await Promise.race(
[
watchUntilTruthy(() => !isEmpty(activeAccount.value)),
new Promise((_r, reject) => setTimeout(reject, 1000)),
],
);
return true;
} catch (error) {
// Intentionally ignoring the error
}
}

app = {
Expand Down
11 changes: 9 additions & 2 deletions src/composables/walletConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export function useWalletConnect({ offscreen } = { offscreen: false }) {
// Connected DAPP requested action, e.g.: signing
web3wallet?.on('session_request', async ({ topic, params: proposal, id }) => {
const { url, name } = wcSession.value?.peer.metadata! || {};
const result = await handleEthereumRpcMethod(
const { result, error } = await handleEthereumRpcMethod(
url,
proposal.request.method as EthRpcSupportedMethods,
proposal.request.params[0],
Expand All @@ -177,7 +177,14 @@ export function useWalletConnect({ offscreen } = { offscreen: false }) {
response: {
id,
jsonrpc: '2.0',
...(result) ? { result } : { error: { code: 5000, message: 'User rejected.' } },
...(result
? { result }
: {
error: {
code: error?.code || 5000,
message: error?.message || 'User rejected.',
},
}),
},
});
});
Expand Down
20 changes: 19 additions & 1 deletion src/content-scripts/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const runContentScript = () => {
method: BackgroundMethod,
params: IEthRpcMethodParameters,
) {
const result = await sendToOffscreen(method, {
const { result, error }: any = await sendToOffscreen(method, {
rpcMethodParams: params,
aepp: event.origin,
});
Expand All @@ -45,6 +45,7 @@ const runContentScript = () => {
event.source.postMessage({
jsonrpc: '2.0',
result,
...(error ? { error } : {}),
method: event.data.method,
superheroWalletApproved: true,
type: 'result',
Expand All @@ -64,6 +65,23 @@ const runContentScript = () => {

if (method === ETH_RPC_METHODS.getBalance) {
handleEthRpcRequest(event, method, { address: event.data.params[0] });
} else if (method === ETH_RPC_METHODS.signPersonal) {
handleEthRpcRequest(event, method, { data: event.data.params?.[0] });
} else if (
method === ETH_RPC_ETHERSCAN_PROXY_METHODS.getTransactionByHash
|| method === ETH_RPC_ETHERSCAN_PROXY_METHODS.getTransactionReceipt
) {
handleEthRpcRequest(event, method, { txhash: event.data.params?.[0] });
} else if (method === ETH_RPC_ETHERSCAN_PROXY_METHODS.getBlockByNumber) {
handleEthRpcRequest(event, method, {
tag: event.data.params?.[0],
boolean: event.data.params?.[1],
});
} else if (method === ETH_RPC_ETHERSCAN_PROXY_METHODS.getUncleByBlockNumberAndIndex) {
handleEthRpcRequest(event, method, {
tag: event.data.params?.[0],
index: event.data.params?.[1],
});
} else if (
Object.values(ETH_RPC_METHODS).includes(method)
|| Object.values(ETH_RPC_ETHERSCAN_PROXY_METHODS).includes(method)
Expand Down
156 changes: 92 additions & 64 deletions src/content-scripts/inpage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,51 @@ interface EIP6963ProviderInfo {
rdns: string;
}

function shouldInjectProvider() {
return doctypeCheck() && suffixCheck() && documentElementCheck();
}

/**
* Checks the doctype of the current document if it exists
*/
function doctypeCheck() {
const { doctype } = window.document;
if (doctype) {
return doctype.name === 'html';
}
return true;
}

/**
* Returns whether or not the extension (suffix) of the current document is prohibited
*
* This checks {@code window.location.pathname} against a set of file extensions
* that we should not inject the provider into. This check is indifferent of
* query parameters in the location.
*/
function suffixCheck() {
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u];
const currentUrl = window.location.pathname;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < prohibitedTypes.length; i++) {
if (prohibitedTypes[i].test(currentUrl)) {
return false;
}
}
return true;
}

/**
* Checks the documentElement of the current document
*/
function documentElementCheck() {
const documentElement = document.documentElement.nodeName;
if (documentElement) {
return documentElement.toLowerCase() === 'html';
}
return true;
}

type SingleSendAsyncParam = { readonly id: string | number | null; readonly method: string; readonly params: readonly unknown[] }

type OnMessage = 'accountsChanged' | 'message' | 'connect' | 'close' | 'disconnect' | 'chainChanged'
Expand Down Expand Up @@ -208,7 +253,9 @@ class SuperheroWalletMessageListener {
private pendingSignerAddressRequest: Future<boolean> | undefined = undefined;

public constructor() {
this.injectEthereumIntoWindow();
if (shouldInjectProvider()) {
this.injectEthereumIntoWindow();
}
this.onPageLoad();
}

Expand Down Expand Up @@ -660,58 +707,7 @@ class SuperheroWalletMessageListener {
};

public readonly injectEthereumIntoWindow = () => {
if (!('ethereum' in window) || !window.ethereum) {
// no existing signer found
window.ethereum = {
isSuperheroWallet: true,
isConnected: this.WindowEthereumIsConnected.bind(window.ethereum),
request: this.WindowEthereumRequest.bind(window.ethereum),
send: this.WindowEthereumSend.bind(window.ethereum),
sendAsync: this.WindowEthereumSendAsync.bind(window.ethereum),
on: this.WindowEthereumOn.bind(window.ethereum),
removeListener: this.WindowEthereumRemoveListener.bind(window.ethereum),
enable: this.WindowEthereumEnable.bind(window.ethereum),
...this.unsupportedMethods(window.ethereum),
};
this.connected = true;
return;
}
if (window.ethereum.isSuperheroWallet) return;

// subscribe for signers events
window.ethereum.on('accountsChanged', (accounts: readonly string[]) => {
this.WindowEthereumRequest({ method: 'eth_accounts_reply', params: [{ type: 'success', accounts, requestAccounts: false }] });
});
window.ethereum.on('disconnect', (_error: ProviderRpcError) => {
this.sendMessageToBackgroundPage({ method: 'connected_to_signer', params: [false, this.currentSigner] });
});
window.ethereum.on('chainChanged', (chainId: string) => {
// TODO: this is a hack to get coinbase working that calls this numbers in base 10 instead of in base 16
// eslint-disable-next-line radix
const params = /\d/.test(chainId) ? [`0x${parseInt(chainId).toString(16)}`] : [chainId];
this.WindowEthereumRequest({ method: 'signer_chainChanged', params });
});

this.connected = !window.ethereum.isConnected || window.ethereum.isConnected();
this.signerWindowEthereumRequest = window.ethereum.request.bind(window.ethereum); // store the request object to signer

if (window.ethereum.isBraveWallet || window.ethereum.providerMap || window.ethereum.isCoinbaseWallet) {
const oldWinEthereum = (window.ethereum.providerMap ? window.ethereum.providerMap.get('CoinbaseWallet') : undefined) ?? window.ethereum;
window.ethereum = {
isSuperheroWallet: true,
isConnected: this.WindowEthereumIsConnected.bind(oldWinEthereum),
request: this.WindowEthereumRequest.bind(oldWinEthereum),
send: this.WindowEthereumSend.bind(oldWinEthereum),
sendAsync: this.WindowEthereumSendAsync.bind(oldWinEthereum),
on: this.WindowEthereumOn.bind(oldWinEthereum),
removeListener: this.WindowEthereumRemoveListener.bind(oldWinEthereum),
enable: this.WindowEthereumEnable.bind(oldWinEthereum),
...this.unsupportedMethods(oldWinEthereum),
};
return;
}
// we cannot inject window.ethereum alone here as it seems like window.ethereum is cached (maybe ethers.js does that?)
Object.assign(window.ethereum, {
const superheroWalletProvider = {
isSuperheroWallet: true,
isConnected: this.WindowEthereumIsConnected.bind(window.ethereum),
request: this.WindowEthereumRequest.bind(window.ethereum),
Expand All @@ -721,25 +717,57 @@ class SuperheroWalletMessageListener {
removeListener: this.WindowEthereumRemoveListener.bind(window.ethereum),
enable: this.WindowEthereumEnable.bind(window.ethereum),
...this.unsupportedMethods(window.ethereum),
};
Object.defineProperties(window, {
superheroWallet: {
value: superheroWalletProvider,
configurable: false,
writable: false,
},
ethereum: {
get() {
// @ts-expect-error
return window.superheroWalletRouter.currentProvider;
},
set(newProvider) {
// @ts-expect-error
window.superheroWalletRouter?.addProvider(newProvider);
},
configurable: false,
},
superheroWalletRouter: {
value: {
superheroWalletProvider,
lastInjectedProvider: window.ethereum,
currentProvider: superheroWalletProvider,
providers: [
superheroWalletProvider,
...(window.ethereum ? [window.ethereum] : []),
],
addProvider(provider: any) {
// @ts-expect-error
if (!window.superheroWalletRouter?.providers?.includes(provider)) {
// @ts-expect-error
window.superheroWalletRouter?.providers?.push(provider);
}
if (superheroWalletProvider !== provider) {
// @ts-expect-error
window.superheroWalletRouter.lastInjectedProvider = provider;
}
},
},
configurable: false,
writable: false,
},
});
this.connected = true;
};
}

function injectSuperheroWallet() {
const superheroWalletMessageListener = new SuperheroWalletMessageListener();
window.addEventListener('message', superheroWalletMessageListener.onMessage);
window.dispatchEvent(new Event('ethereum#initialized'));

// listen if Metamask injects (I think this method of injection is only supported by Metamask currently) their payload, and if so, reinject
const superheroWalletCapturedDispatcher = window.dispatchEvent;
window.dispatchEvent = (event: Event) => {
superheroWalletCapturedDispatcher(event);
if (!(typeof event === 'object' && event !== null && 'type' in event && typeof event.type === 'string')) return true;
if (event.type !== 'ethereum#initialized') return true;
superheroWalletMessageListener.injectEthereumIntoWindow();
window.dispatchEvent = superheroWalletCapturedDispatcher;
return true;
};
}

injectSuperheroWallet();
3 changes: 2 additions & 1 deletion src/popup/pages/Popups/MessageSign.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ export default defineComponent({
setPopupProps,
} = usePopupProps();

const activeAccount = getLastActiveProtocolAccount(PROTOCOLS.aeternity);
const protocol = popupProps.value?.protocol || PROTOCOLS.aeternity;
const activeAccount = getLastActiveProtocolAccount(protocol);

async function approve() {
const { openModal } = useModals();
Expand Down
9 changes: 6 additions & 3 deletions src/popup/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ RouteQueryActionsController.init(router);
RouteLastUsedRoutes.init(router);

router.beforeEach(async (to, from, next) => {
let popupProps: IPopupProps | null = null;

if (RUNNING_IN_POPUP && to.name !== ROUTE_NOT_FOUND && !Object.keys(to.params).length) {
popupProps = await getPopupProps();
}

await checkUserAuth();

await watchUntilTruthy(areAccountsReady);
Expand Down Expand Up @@ -107,10 +113,7 @@ router.beforeEach(async (to, from, next) => {
[POPUP_TYPE_UNSAFE_SIGN]: ROUTE_POPUP_UNSAFE_SIGN,
}[POPUP_TYPE];

let popupProps: IPopupProps | null = null;

if (!Object.keys(to.params).length) {
popupProps = await getPopupProps();
if (!popupProps?.app) {
next({ name: ROUTE_NOT_FOUND, params: { hideHomeButton: true as any } });
return;
Expand Down
4 changes: 3 additions & 1 deletion src/protocols/ethereum/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ export const ETH_RPC_METHODS = {
getBlockNumber: 'eth_blockNumber',
getChainId: 'eth_chainId',
getAccounts: 'eth_accounts',
sendTransaction: 'eth_sendTransaction',
requestAccounts: 'eth_requestAccounts',
// signing
sendTransaction: 'eth_sendTransaction',
signPersonal: 'personal_sign',
// wallet
requestPermissions: 'wallet_requestPermissions',
switchNetwork: 'wallet_switchEthereumChain',
Expand Down
Loading
Loading