diff --git a/extension/src/browser/Drawer/RolePermissionCheck.tsx b/extension/src/browser/Drawer/RolePermissionCheck.tsx index 6897988f..85437e06 100644 --- a/extension/src/browser/Drawer/RolePermissionCheck.tsx +++ b/extension/src/browser/Drawer/RolePermissionCheck.tsx @@ -33,6 +33,7 @@ const simulateRolesTransaction = async ( ) as SerRoute const plan = await planExecution([encodedTransaction], routeWithInitiator) + // TODO generalize permission checking logic (ser-kit) if (plan.length > 1) { throw new Error('Multi-step execution not yet supported') } diff --git a/extension/src/contentScript.ts b/extension/src/contentScript.ts index ee8aaf01..445780e6 100644 --- a/extension/src/contentScript.ts +++ b/extension/src/contentScript.ts @@ -1,6 +1,8 @@ function inject(windowName: string, scriptPath: string) { if (window.name === windowName) { const node = document.createElement('script') + node.type = 'text/javascript' + node.async = false node.src = chrome.runtime.getURL(scriptPath) const parent = document.head || document.documentElement @@ -11,9 +13,7 @@ function inject(windowName: string, scriptPath: string) { } parent.dataset.zodiacPilotInjected = 'true' parent.insertBefore(node, parent.children[0]) - node.onload = function () { - node.remove() - } + node.remove() } } diff --git a/extension/src/injection.ts b/extension/src/injection.ts index a8e7e55e..bae7efa8 100644 --- a/extension/src/injection.ts +++ b/extension/src/injection.ts @@ -1,34 +1,54 @@ // This script will be injected via contentScripts.ts into the browser iframe running the Dapp. import InjectedProvider from './bridge/InjectedProvider' -declare let window: Window & { ethereum: InjectedProvider } - -if (window.ethereum) { - // There is already a provider injected - const descriptor = Object.getOwnPropertyDescriptor(window, 'ethereum') - if (descriptor?.configurable === false) { - // We got a problem: The provider is not configurable (most probably Rabby) - alert( - 'Zodiac Pilot is unable to connect. In Rabby, flip the setting so it is banned and reload the page.' - ) +declare let window: Window & { + ethereum: InjectedProvider + rabbyWalletRouter?: { + setDefaultProvider(rabbyAsDefault: boolean): void + addProvider(provider: InjectedProvider): void } } // inject bridged ethereum provider const injectedProvider = new InjectedProvider() -Object.defineProperties(window, { - ethereum: { - get() { - return injectedProvider - }, - set() { - // do nothing + +const canSetWindowEthereum = + Object.getOwnPropertyDescriptor(window, 'ethereum')?.configurable !== false + +if (canSetWindowEthereum) { + Object.defineProperties(window, { + ethereum: { + get() { + return injectedProvider + }, + set() { + // do nothing + }, + configurable: false, }, - configurable: false, - }, -}) + }) + + console.log('Injected Zodiac Pilot provider') +} else { + // Houston, we have a problem: There is already a provider injected by another extension and it's not configurable -console.log('injected into', document.title) + // If it's Rabby we have a trick to make sure it routes to the Pilot provider + if (window.rabbyWalletRouter) { + console.log( + 'Rabby detected, setting Pilot as default provider in Rabby Wallet Router', + window.rabbyWalletRouter + ) + window.rabbyWalletRouter.addProvider(injectedProvider) + window.rabbyWalletRouter.setDefaultProvider(false) + // prevent Rabby from setting its own provider as default subsequently + window.rabbyWalletRouter.setDefaultProvider = () => {} + } else { + // If it's not Rabby, we have to alert the user + alert( + 'Zodiac Pilot is unable to connect because of another wallet extension. Disable the other extension and reload the page.' + ) + } +} // establish message bridge for location requests window.addEventListener('message', (ev: MessageEvent) => { diff --git a/extension/src/providers/ForkProvider.ts b/extension/src/providers/ForkProvider.ts index bb37fe85..7cd6393d 100644 --- a/extension/src/providers/ForkProvider.ts +++ b/extension/src/providers/ForkProvider.ts @@ -180,6 +180,7 @@ class ForkProvider extends EventEmitter { * * Even for regular calls (operation: 0) this method is useful to simulate the transaction through the Safe contract with the module or owner as sender (if set). * This is necessary to make sure the simulation succeeds for some edge cases: If a contract calls `.transfer()` on the sender's address this comes only with a 2300 gas stipend, not enough to run a cold Safe's `fallback` function. + * This could alternatively be solved using EIP-2930 access lists, though. * * @param metaTx A MetaTransaction object, can be operation: 1 (delegatecall) */ @@ -325,6 +326,9 @@ const readBlockGasLimit = async (provider: Eip1193Provider) => { return block?.gasLimit || 30_000_000n } +/** + * Makes sure that the given Safe is ready for simulating transactions, which requires at least one module to be enabled. + */ async function prepareSafeForSimulation( { chainId,