Skip to content

Commit

Permalink
Merge pull request #138 from gnosisguild/sidepanel
Browse files Browse the repository at this point in the history
Turn it into a side panel
  • Loading branch information
jfschwarz authored Dec 4, 2024
2 parents 782fed9 + d61011c commit b0fd888
Show file tree
Hide file tree
Showing 1,491 changed files with 36,476 additions and 68,850 deletions.
52 changes: 42 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
name: CI

on: [push]
on:
pull_request:
push:
branches:
- main

jobs:
tests:
static_checks:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./extension
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
node-version: 21
cache: "yarn"
cache-dependency-path: "**/yarn.lock"
- run: yarn install --immutable
working-directory: ./extension
version: 9
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- run: pnpm install --prefer-offline
- name: Run static checks
run: yarn run check
run: pnpm check
unit_tests:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./extension
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- run: pnpm install --prefer-offline
- run: pnpm test:unit
- name: 'Report Coverage'
# Set if: always() to also generate the report if tests are failing
# Only works if you set `reportOnFailure: true` in your vite config as specified above
if: always()
uses: davelosert/vitest-coverage-report-action@v2
with:
working-directory: extension
37 changes: 0 additions & 37 deletions .github/workflows/e2e.yml

This file was deleted.

43 changes: 43 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Playwright Tests
on:
deployment_status:
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
if: github.event.deployment_status.state == 'success'
defaults:
run:
working-directory: ./extension
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install --prefer-offline

- name: Build
run: pnpm build

- name: Install Playwright Browsers
run: pnpm playwright install --with-deps chromium

- name: Run Playwright tests
run: xvfb-run pnpm playwright test
env:
PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }}
VERCEL_PROTECTION_BYPASS: ${{ secrets.VERCEL_PROTECTION_BYPASS }}
PW_CHROMIUM_ATTACH_TO_OTHER: 1

- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
94 changes: 8 additions & 86 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,101 +7,23 @@ Chrome extension to simulate Dapp interactions and record transactions. [Availab

## Contribute

### Run in development
### Overview

Build a development bundle of the extension in watch mode:
The [extension](./extension/) folder hosts the code of the browser extension.
The included [README](./extension/README.md) documents the most important concepts.

```
To run a development version of the extension from a branch:

```bash
cd extension
yarn dev
```

The build output is written to public/build.
The `dev` script will watch for changes and automatically rebuild.

To enable the extension in Chrome, follow these steps:

1. Open the Extension Management page by navigating to [chrome://extensions](chrome://extensions).
2. Enable **Developer Mode** by clicking the toggle switch at the top right of the page.
3. Click the **Load unpacked** button and select the `zodiac-pilot/public` directory.

### Package for production

```
yarn build
```

## How it works

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](extension/src/launch.ts) which is injected into pages running under the [Zodiac Pilot host](#zodiac-pilot-host) via a [content script](https://developer.chrome.com/docs/extensions/mv3/content_scripts/).
- **background script:** A [service worker script](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/#service-workers) that allows to hook into different Chrome events and APIs: [src/background.ts](extension/src/background.ts)
- **injected script:** Whenever we load any page in the the extension app iframe, we inject [src/inject.ts](extension/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](extension/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`.

### Zodiac Pilot host

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. We might consider implementing a local forking mechanism using Ganache as an alternative to the existing Tenderly integration.

That's why the extension is now running under an external host, https://pilot.gnosisguild.org.

### Open Dapps in iframe

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](https://developer.chrome.com/docs/extensions/reference/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.

### Syncing iframe location

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.

### Inject EIP-1193 provider

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](https://eips.ethereum.org/EIPS/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 recorded and executed in a fork of the connected network.

We also subscribe to messages sent via the [safe-apps-sdk](https://github.com/safe-global/safe-apps-sdk).
This enables instant and smart-account optimized connections to Safe-compatible apps.

### Simulating transaction in a fork

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 Safe.
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.

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.

### Reroute JSON-RPC fetch requests

Apps commonly make read-only JSON-RPC requests to providers such as Infura or Alchemy rather than going through the EIP-1193 provider injected by the wallet.
Once the network has been forked for simulating recorded transaction such requests should reflect the state of the fork as well.

This first requires detecting which of the JSON-RPC endpoints used by an app actually serve the forked network.
The `webRequest` extension API allows inspecting the body of each outgoing request.
If a requests looks like a JSON-RPC request we probe the target endpoint for `requestChainId`.

Once the network is forked we update `declarativeNetRequest` xhr redirection rules for endpoints identified as serving the forked network.
All of this is implemented as part of the [background service worker](src/background.ts).

### Submitting transactions

A batch of recorded transaction can finally be submitted as a multi-send transaction.
Zodiac Pilot can be configured to submit transactions directly to the Safe if the Pilot account is an owner or delegate, or to route the transaction through Zodiac mods.
This is implemented in [WrappingProvider](src/providers/WrappingProvider.ts).
It currently supports the [Roles](https://github.com/gnosisguild/zodiac-modifier-roles) and [Delay](https://github.com/gnosisguild/zodiac-modifier-delay) mods.

### Overview of providers

![Diagram giving an overview of providers](./docs/providers-diagram.png)
5 changes: 3 additions & 2 deletions connect/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
This is a minimal web app running under https://connect.pilot.gnosisguild.org to allow connecting to the user's wallet extension.
This is an empty web app running under https://connect.pilot.gnosisguild.org to allow connecting to the user's wallet extension.

It communicates with the Pilot extension side panel through `window.postMessage`.
It's sole purpose is having a host for connecting to the wallet.
Since extensions don't have access to injected wallets, Pilot will load this empty site in an iframe injecting a script to use it as a bridge to the wallet.
Loading

0 comments on commit b0fd888

Please sign in to comment.