Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Docs

on:
push:
branches: [main]
release:
types: [released]
pull_request:
types: [opened, synchronize]

concurrency:
# Cancel older, in-progress jobs from the same PR, same workflow.
# use run_id if the job is triggered by a push to ensure
# push-triggered jobs to not get canceled.
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
docs:
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install .[docs]

- name: Ape Docs
uses: apeworx/sphinx-ape@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
126 changes: 59 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# Quick Start
# Overview

Account plugin for the [Safe](https://safe.global//) Multisig wallet (previously known as Gnosis Safe).
Account plugin for the [Safe](https://safe.global/) multisig wallet (previously known as Gnosis Safe) for the [Ape Framework](https://github.com/ApeWorX/ape).

## Features

- **Safe Account Management**: Add, list, and remove Safe multisig wallets
- **Transaction Management**: Create, propose, sign, and execute Safe transactions
- **Multisig Workflows**: Manage transaction approval workflows with multiple signers
- **MultiSend Support**: Batch multiple transactions together efficiently
- **CLI Interface**: Comprehensive command line tools for Safe management
- **Python API**: Programmatic access to all Safe functionality

## Dependencies

- [python3](https://www.python.org/downloads) version 3.9 up to 3.12.
- [python3](https://www.python.org/downloads) version 3.9 or newer

## Installation

Expand Down Expand Up @@ -36,24 +45,28 @@ $ python3 setup.py install

## Quick Usage

To use the plugin, first use the CLI extension to add a safe you created:
To use the plugin, first use the Ape CLI extension to add a safe you want to control:

```sh
ape safe add --network ethereum:mainnet "my-safe.eth" my-safe
# ape safe add ADDRESS ALIAS
ape safe add --network ethereum:mainnet my-safe.eth my-safe
```

If you made a mistake or just need to remove the safe, use the `remove` command:

```sh
# ape safe remove ALIAS
ape safe remove my-safe --yes
```

**NOTE** `--yes` is a way to skip the prompt.
```{note}
`--yes` is a way to skip the prompt.
```

If you only add one safe, you will not have to specify which safe to use other commands.
If you only have one safe, you will not have to specify which safe to use other commands.
Otherwise, for most `pending` commands, you specify the safe to use (by alias) via the `--safe` option.

Additionally, you can configure a safe to use as the default in your `ape-config.yaml` file:
Additionally, you can configure a safe to use as the default when no `--safe` argument is present by configuring the following in your `ape-config.yaml` file:

```yaml
safe:
Expand All @@ -67,23 +80,15 @@ or via `pyproject.toml`:
default_safe = "my-safe"
```

To specify via environment variable, do:
or specify via environment variable:

```sh
APE_SAFE_DEFAULT_SAFE="my-safe"
export APE_SAFE_DEFAULT_SAFE="my-safe"
```

**NOTE**: Also, to avoid always needing to specify `--network`, you can set a default ecosystem, network, and provider in your config file.
The rest of the guide with not specify `--network` on each command but assume the correct one is set in the config file.
Here is an example:

```yaml
default_ecosystem: optimism

ethereum:
default_network: sepolia
sepolia:
default_provider: infura
```{note}
To avoid always needing to specify `--network`, you can set a default ecosystem, network, and provider in your config file.
The rest of the guide will not specify `--network` on each command but assume it matches the network your Safe is on.
```

Once you have a safe, you can view pending transactions:
Expand All @@ -94,83 +99,70 @@ ape safe pending list

```{note}
You must specify the environment variable `APE_SAFE_GATEWAY_API_KEY=` to use the Safe Gateway API.
Get an API key at https://developer.safe.global/.
Get an API key at the [Safe Developer Portal](https://developer.safe.global).
```

It should show transactions like this:

```sh
Transaction 8 rejection (1/2) safe_tx_hash=0x09ab9a229fc60da66ec0fa8fa886ab7c95902fdf5df5a5009ba06010fbb9a9a7
Transaction 8 transfer (1/2) safe_tx_hash=0xed43d80255bcd5ffacb755e8f51bee825913373705d6baea006419d2a33a0a5b
Transaction 8 rejection (1/2) safe_tx_hash=0x09ab...a9a7
Transaction 8 transfer (1/2) safe_tx_hash=0xed43...0a5b
```

**NOTE**: Use the `--verbose` flag to see more information about each transaction.
Use the `--verbose` flag to see more information about each transaction:

```sh
ape safe pending list --verbose
```

There are several operations you can do on a pending transaction.
One of them is "approve" which adds your local signers' signatures to the transaction.
There are several operations you can do on a pending transaction:

```sh
ape safe pending approve 0x09ab9a229fc60da66ec0fa8fa886ab7c95902fdf5df5a5009ba06010fbb9a9a7
```
# Add more signatures using locally-configured Ape signer(s)
# NOTE: can specify either SafeTxID or Nonce
ape safe pending approve 0x09ab...a9a7

**NOTE**: Here we are using the transaction hash `0x09ab9a229fc60da66ec0fa8fa886ab7c95902fdf5df5a5009ba06010fbb9a9a7` to specify the transaction because there are more than one.
However, you can also use the nonce if there is only a single transaction.
# Add remaining signatures and execute transction w/ account alias `submitter`
# NOTE: can specify either SafeTxID or Nonce
ape safe pending approve 2 --execute submitter

If you want to both execute and approve at the same time, you can use the `--execute` option on approve and specify a sender:
# Execute an already-signed transaction using `submitter`
ape safe pending execute 2 --account submitter

```sh
ape safe pending approve 2 --execute my_account
```

Else, you can use the `execute` command directly:

```sh
ape safe pending execute 2
```

**NOTE**: `execute` requires a full signed transaction ready to be submitted on-chain.

The last main operation is `reject`.
Rejecting a transaction replaces that transaction with a zero-value transfer from the safe to itself.

```sh
# Create an on-chain rejection for an existing transaction queue item
ape safe pending reject 2
```

### Multisend
### MultiSend Support

The following example shows how to use multisend:
Ape Safe allows sending "batched transactions" using the `MultiSend` module:

```python
from ape_safe import multisend
from ape import accounts
from ape_safe import multisend
from ape_tokens import tokens

me = accounts.load("my-key")
safe = accounts.load("my-safe")

# Load some contracts (here using ape-tokens)
# Load some contracts using ape-tokens
dai = tokens["DAI"]
vault = tokens["yvDAI"]
amount = dai.balanceOf(safe) # How much we want to deposit

# Create a multisend transaction (a transaction that executes multiple calls)
txn = multisend.MultiSend()
txn.add(dai.approve, vault, amount)
txn.add(vault.deposit, amount)
# Create a multisend batch transaction
batch = safe.create_batch()
batch.add(dai.approve, vault, amount)
batch.add(vault.deposit, amount)

# Fetch signatures from any local signers, and broadcast if confirmations are met
# Note that in case the user intends to only stage a transaction, then `submit=False` argument can also be added
# It is normal that when a user only intend to stage a transaction, an error is thrown
# this can be ignored by including the necessary try-catch (from ape.exceptions import SignatureError)
# Note that transaction is automatically prompted for execution if enough signers are available in local
txn(sender=safe,gas=0)
# Fetch signatures from local signer(s)
# NOTE: will broadcast unless `submit=False`
batch(submitter=me)
# OR add to the Safe Gateway for later execution
batch.propose()
```

## Cloud Usage
### Cloud Environment

To use this plugin in a cloud environment, such as with the [Silverback Platform](https://silverback.apeworx.io), you will need to make sure that you have configured your Safe to exist within the environment.
The easiest way to do this is to use the `require` configuration item.
Expand All @@ -181,15 +173,15 @@ safe:
require:
my-safe:
address: "0x1234...AbCd"
deployed_chain_ids: [1, ...]
deployed_chain_ids: [1, 10] # Add all deployed chains here
```

or in `pyproject.toml`:

```toml
[tool.ape.safe.require."my-safe"]
address = "0x1234...AbCd"
deployed_chain_ids = [1, ...]
deployed_chain_ids = [1, 10] # Add all deployed chains here
```

To specify via environment variable, do:
Expand All @@ -198,7 +190,7 @@ To specify via environment variable, do:
APE_SAFE_REQUIRE='{"my-safe":{"address":"0x1234...AbCd","deployed_chain_ids":[1,...]}}'
```

```{notice}
```{note}
If a safe with the same alias as an entry in `require` exists in your local environment, this will skip adding it, even if the existing alias points to a different address than the one in the config item.
```

Expand All @@ -209,5 +201,5 @@ Comments, questions, criticisms and pull requests are welcomed.

## Acknowledgements

This package was inspired by [the original ape-safe](https://github.com/banteg/ape-safe#readme) by [banteg](https://github.com/banteg).
This package was inspired by the original ape-safe, now [brownie-safe](https://github.com/banteg/brownie-safe) by [banteg](https://github.com/banteg).
For versions prior to v0.6.0, the original package should be referenced.
31 changes: 31 additions & 0 deletions ape_safe/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
from ape.api.address import BaseAddress
from ape.contracts import ContractInstance

from ape_safe.multisend import MultiSend


class SafeContainer(AccountContainerAPI):
_accounts: dict[str, "SafeAccount"] = {}
Expand Down Expand Up @@ -194,6 +196,30 @@ class SafeAccount(AccountAPI):
account_file_path: Path # NOTE: Cache any relevant data here
_factory: ClassVar[SafeFactory] = SafeFactory()

def __dir__(self) -> list[str]:
return [
"contract",
"fallback_handler",
"guard",
"set_guard",
"version",
"confirmations_required",
"signers",
"local_signers",
"safe_tx_def",
"create_safe_tx",
"propose_safe_tx",
"submit_safe_tx",
"client",
"propose",
"pending_transactions",
"add_signatures",
"create_execute_transaction",
"create_batch",
"all_delegates",
*super().__dir__(),
Comment thread
fubuloubu marked this conversation as resolved.
]

@property
def alias(self) -> str:
return self.account_file_path.stem
Expand Down Expand Up @@ -403,6 +429,11 @@ def create_safe_tx(self, txn: Optional[TransactionAPI] = None, **safe_tx_kwargs)
}
return self.safe_tx_def(**safe_tx)

def create_batch(self) -> "MultiSend":
from ape_safe.multisend import MultiSend

return MultiSend(safe=self, version=self.version)

def all_delegates(self) -> Iterator[AddressType]:
for delegates in self.client.get_delegates().values():
yield from delegates
Expand Down
Loading
Loading