Skip to content
Open
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
32 changes: 32 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,35 @@ jobs:
INFURA_KEY: ${{ secrets.INFURA_KEY }}
DUNE_API_KEY: ${{ secrets.DUNE_API_KEY }}
PROPOSER_PK: ${{ secrets.PROPOSER_PK }}

rust:
runs-on: ubuntu-latest
defaults:
run:
working-directory: rs
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Cache Cargo registry and target directory
uses: actions/cache@v4
with:
path: |
rs/.cargo/registry
rs/.cargo/git
rs/target
key: ${{ runner.os }}-cargo-${{ hashFiles('rs/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Check formatting
run: cargo fmt --all -- --check

- name: Lint with Clippy
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Run tests
run: cargo test --all

- name: Build
run: cargo build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target/
*.pyc
venv
__pycache__
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
# SubSafe Commander for Safe Transaction Batching

Control your Fleet of safes from the command line! Claim your $SAFE token airdrop in style.
Control your Fleet of safes from the command line! Claim your $SAFE token airdrop in style.

## TLDR;
## 🦀 Rust Implementation Available!

This project is now available in **both Python and Rust**!

- **Python** (original): Stable, battle-tested implementation in the root directory
- **Rust** (new): High-performance, type-safe implementation in the `rs/` directory

For the Rust version, see [`rs/README.md`](rs/README.md) for detailed instructions.

**Quick start with Rust:**
```bash
cd rs
cargo build --release
./target/release/subsafe-commander --help
```

---

## TLDR; (Python Version)

To Claim $SAFE Airdrop on behalf of a family of sub-safes (with signing threshold 1) all of which
are owned by a single "parent" safe, do this:
Expand Down
14 changes: 14 additions & 0 deletions rs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Rust build artifacts
/target/
Cargo.lock

# IDE
.idea/
.vscode/
*.swp
*.swo
*~

# OS
.DS_Store

44 changes: 44 additions & 0 deletions rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = "subsafe-commander"
version = "0.1.0"
edition = "2024"
authors = ["bh2smith"]
description = "Control your Fleet of safes from the command line!"

[[bin]]
name = "subsafe-commander"
path = "src/main.rs"

[dependencies]
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clap = { version = "4.4", features = ["derive", "env"] }
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false }
duners = "0.0.2"
anyhow = "1.0"
dotenvy = "0.15"
log = "0.4"
env_logger = "0.11"
chrono = "0.4"
hex = "0.4"
alloy = { version = "1.0.42", default-features = false, features = [
"providers",
"signer-local",
"network",
"rpc-client",
"reqwest",
"dyn-abi",
"sol-types",
"contract",
] }

[dev-dependencies]
tokio-test = "0.4"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true

33 changes: 33 additions & 0 deletions rs/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Multi-stage Dockerfile for SubSafe Commander (Rust)

# Build stage
FROM rustlang/rust:nightly AS builder

WORKDIR /app

# Copy Cargo files
COPY Cargo.toml ./

# Copy source code
COPY src ./src

# Build the application in release mode
RUN cargo build --release

# Runtime stage
FROM debian:bookworm-slim

# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates && \
rm -rf /var/lib/apt/lists/*

# Copy the binary from builder
COPY --from=builder /app/target/release/subsafe-commander /usr/local/bin/

# Set the entrypoint
ENTRYPOINT ["subsafe-commander"]

# Default command (can be overridden)
CMD ["--help"]

183 changes: 183 additions & 0 deletions rs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# SubSafe Commander (Rust)

Control your Fleet of Safes from the command line! Claim your $SAFE token airdrop in style.

This is the Rust implementation of the SubSafe Commander, providing the same functionality as the Python version with improved performance and type safety.

## Features

- ✅ Claim $SAFE token airdrops on behalf of sub-safes
- ✅ Add owners to multiple sub-safes
- ✅ Set/clear Snapshot delegates for governance
- ✅ Multisend transaction batching
- ✅ Integration with Dune Analytics
- ✅ Safe transaction encoding

## Installation

### Prerequisites

- Rust 1.70+ (install from [rustup.rs](https://rustup.rs/))

### Build from Source

```bash
cd rs
cargo build --release
```

The binary will be available at `target/release/subsafe-commander`.

## Usage

### Environment Variables

Create a `.env` file in the project root:

```bash
NODE_URL=https://rpc.ankr.com/eth
PARENT_SAFE=0x...
PROPOSER_PK=0x... # Private key of Parent Owner
DUNE_API_KEY=your_dune_api_key
```

### Commands

#### Claim Airdrop

```bash
./target/release/subsafe-commander \
--command claim \
--parent 0xYourParentSafe \
--sub-safes 0xChild1,0xChild2,0xChild3
```

Or fetch children from Dune:

```bash
./target/release/subsafe-commander \
--command claim \
--parent 0xYourParentSafe \
--index-from 0 \
--num-safes 10
```

#### Add Owner

```bash
./target/release/subsafe-commander \
--command add-owner \
--parent 0xYourParentSafe \
--sub-safes 0xChild1,0xChild2 \
--new-owner 0xNewOwnerAddress \
--threshold 1
```

#### Set Snapshot Delegate

```bash
./target/release/subsafe-commander \
--command set-delegate \
--parent 0xYourParentSafe \
--sub-safes 0xChild1,0xChild2 \
--delegate 0xDelegateAddress
```

If `--delegate` is not provided, it defaults to the parent safe.

#### Clear Snapshot Delegate

```bash
./target/release/subsafe-commander \
--command clear-delegate \
--parent 0xYourParentSafe \
--sub-safes 0xChild1,0xChild2
```

## Development

### Run Tests

```bash
cargo test
```

### Run with Logging

```bash
RUST_LOG=info cargo run -- --command claim --parent 0x...
```

### Format Code

```bash
cargo fmt
```

### Lint

```bash
cargo clippy
```

## Architecture

The Rust implementation follows a modular architecture:

- `main.rs` - CLI entry point and command routing
- `environment.rs` - Environment configuration
- `safe.rs` - Safe operations and transaction encoding
- `multisend.rs` - Multisend transaction batching
- `airdrop/` - Airdrop-specific logic
- `allocation.rs` - Fetching allocation data
- `encode.rs` - Encoding claim transactions
- `tx.rs` - Transaction building
- `snapshot/` - Snapshot delegation
- `delegate_registry.rs` - Delegation ID handling
- `tx.rs` - Delegate transaction building
- `add_owner.rs` - Add owner functionality
- `token_transfer.rs` - ERC20 and native token transfers
- `dune.rs` - Dune Analytics integration using [`duners`](https://crates.io/crates/duners) crate
- `utils.rs` - Utility functions

## Differences from Python Version

1. **Type Safety**: Rust's strong type system prevents many runtime errors
2. **Performance**: Faster execution, especially for large batches
3. **Memory Safety**: No garbage collector, deterministic memory usage
4. **Async/Await**: Built-in async support with Tokio
5. **Error Handling**: Explicit error handling with `Result` types

## Docker Support

### Build Docker Image

```bash
docker build -f rs/Dockerfile -t subsafe-commander:rust .
```

### Run with Docker

```bash
docker run --rm --env-file .env \
subsafe-commander:rust \
--command claim \
--parent $PARENT_SAFE
```

## Contributing

Contributions are welcome! Please ensure:

1. All tests pass: `cargo test`
2. Code is formatted: `cargo fmt`
3. No clippy warnings: `cargo clippy`

## License

Same as the parent project.

## Acknowledgments

This is a Rust port of the original Python implementation. The core logic and algorithms remain the same, with adaptations for Rust's type system and best practices.

Loading
Loading