Chrome extension to simulate Dapp interactions and record transactions. Available on the Chrome Webstore
Build a development bundle of the extension in watch mode:
yarn dev
The build output is written to public/build.
To enable the extension in Chrome, follow these steps:
- Open the Extension Management page by navigating to chrome://extensions.
- Enable Developer Mode by clicking the toggle switch at the top right of the page.
- Click the Load unpacked button and select the
zodiac-pilot/public
directory.
yarn build
The extension consists of three different interacting pieces:
- extension app: This is the main app rendering the iframe. The entrypoint to the app is launch.ts which is injected into pages running under the Zodiac Pilot host via a content script.
- background script: A service worker script that allows to hook into different Chrome events and APIs: src/background.ts
- injected script: Whenever we load any page in the the extension app iframe, we inject src/inject.ts into the page so that this script runs in the context of that page. The injection happens via the content script at src/contentScript.ts.
The different scripts communicate exclusively via message passing. Extension page and background script use chrome.runtime.sendMessage
while extension page and injected script talk via window.postMessage
.
Originally, we started out building the Pilot as an extension page, which are hosted under chrome-extension://<EXTENSION_ID>
. However extension pages are subject to some restrictions that make implementing certain integrations difficult, most notably:
- All extensions are sandboxed from each other, meaning that the MetaMask injected provider would not be available to an extension page.
- Extension pages have no access to Indexed DB, which is a dependency of Ganache.
That's why the extension is now running under an external host, https://pilot.gnosisguild.org.
For allowing arbitrary pages to be loaded in our iframe we drop X-Frame-Options
and Content-Security-Policy
HTTP response headers for any requests originating from tabs showing our extension.
As we don't want to generally lift cross origin restrictions, we dynamically adjust the condition under which the declarativeNetRequest rule applies. In our background script, we track tabs running our extension and will apply the header removal only for requests originating from any of these tabs.
The problem: When the user navigates the Dapp, the address bar of the Zodiac Pilot should update accordingly. The browser back button should function as usual and when reloading the extension page the iframe should continue showing the original page. Since browsers block access to foreign origin iframes we need to leverage Chrome extension super powers to detect navigation events in the iframe.
The solution: We listen to chrome.tabs.onUpdated
in the background script and notify the content script about it via a message, which relays the message to the extension app.
This relaying is necessary because a background script can not directly talk to an externally hosted app.
For retrieving the new iframe location, we then post a message to the injected script in the iframe window, which will send us the response in another message.
When the simulator iframe opens any page, we inject the build/inject.js script as a node into the DOM of the Dapp.
The injected script then runs in the context of the Dapp and injects an EIP-1193 compatible API at window.ethereum
.
The injected provider forwards all request
calls to the parent extension page via window.postMessage
where they are handled with on of our providers.
We currently offer two kinds of providers, the WrappingProvider
for synchronously dispatching the transaction as a meta transaction of a Zodiac module function call, and ForkProvider
for simulating the transaction in a local fork of the connected network and recording it for later batch transmission.
When the provider we inject into the Dapp iframe receives a transaction request, we record it and simulate the transaction in a fork of the target network, impersonating the Avatar account. That way the app can continue communicating with the fork network, so that a whole session of multiple transactions can be recorded before anything is signed and submitted to the real chain.
There are two options available for simulating transactions in a fork, Tenderly and a Ganache EVM running locally in the browser.
Tenderly provides rich debugging capabilities, which help in understanding the exact effects of each recorded transaction before actually signing anything. Fresh forks are created via Tenderly's Simulation API and each fork will have its own JSON RPC URL.
We use Ganache to run a local EVM with a fork of the network the user is connected to.
TODO: The following is still true, but we should adjust the implementation now that the extension is running under an external host.
Ganache depends on Indexed DB, which is not available to extension pages. For this reason we run it via an injected script on an externally hosted page in an iframe. Again we communicate via
window.postMessage
. That way we connect Ganache to the WalletConnect provider in the extension page so it can fork the active network. At the same time, we connect the Dapp injected provider toForkProvider
in the host page, which forwards requests to the Ganache provider running in the ganache iframe.
A batch of recorded transaction can finally be submitted as a multi-send transaction.
Zodiac Pilot is currently geared to submitting transactions via a Roles mod, which means that the multi-send call needs to wrapped in a Roles mod's execTransactionWithRole
call.
This is implemented in the WrappingProvider.
In the future, we plan to make Zodiac Pilot more generally useful, meaning that users will be able to customize the exact way of transaction wrapping.