Skip to content

Feature/add safe multisig tool#591

Open
invocamanman wants to merge 19 commits intodevelopfrom
feature/addSafeMultisigTool
Open

Feature/add safe multisig tool#591
invocamanman wants to merge 19 commits intodevelopfrom
feature/addSafeMultisigTool

Conversation

@invocamanman
Copy link
Collaborator

  • add safe multisig tool
    • Manage a safe offchain, allowing udpating the owners and sign any transaction ( in case projects want to use a multisig without gnosis UI)
    • Especifically give roleAdmin of the timelock to another address (In case projects want to receive the admin role and manage it themselves)
  • add ledger support
  • Add utils to easily deploy gnosis safes

@krlosMata krlosMata requested a review from Copilot January 21, 2026 10:53
@krlosMata
Copy link
Member

@codex review

@chatgpt-codex-connector
Copy link

To use Codex here, create a Codex account and connect to github.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds comprehensive tooling for managing Gnosis Safe multisig wallets, with specific focus on granting timelock roles and general Safe operations. The PR includes Ledger hardware wallet support and utilities for deploying Safes.

Changes:

  • Adds Safe multisig transaction signing and execution tools with EIP-712 support
  • Integrates Ledger hardware wallet support via hardhat configuration and patches
  • Provides utilities for managing Safe owners, granting timelock roles, and deploying Safes

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tools/safeMultisigGrantRole/* Tools for granting timelock roles via Safe multisig
tools/safeMultisig/* General Safe multisig transaction management utilities
tools/ledgerGrantRoles/* Tool for self-granting PROPOSER and EXECUTOR roles
tools/deploySafe/* Utility for deploying Safes to multiple networks
tools/replayTx/* Transaction replay utility
hardhat.config.ts Ledger integration in network configurations
package.json Added hardhat-ledger and patch-package dependencies
patches/* Patch for hardhat-ledger to fix eth_accounts deprecation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@krlosMata
Copy link
Member

@codex review this PR

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed6ea20c7e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@krlosMata krlosMata requested a review from Copilot February 6, 2026 11:49
@krlosMata
Copy link
Member

@codex review

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 26 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +43 to +71
interface SafeTransaction {
to: string;
value: number;
data: string;
operation: number;
safeTxGas: number;
baseGas: number;
gasPrice: number;
gasToken: string;
refundReceiver: string;
nonce: number;
}

interface SafeSignature {
signer: string;
data: string;
}

interface SignedTransactionData {
safeTx: SafeTransaction;
signatures: SafeSignature[];
txHash: string;
chainId?: number;
}

/* //////////////////////////////////////////////////////////////
HELPERS
////////////////////////////////////////////////////////////// */

Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value field in SafeTransaction should be string type to handle large uint256 values safely, not number. JavaScript numbers can only safely represent integers up to 2^53-1, while Ethereum values can be much larger.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +375
git clone https://github.com/agglayer/agglayer-contracts.git
cd agglayer-contracts
```

### 2. Checkout the Feature Branch

```bash
git checkout feature/addSafeMultisigTool
```

### 3. Install Dependencies

```bash
npm install
```

## ⚙️ Environment Configuration

### 1. Create Environment File

```bash
cp .env.example .env
```

### 2. Configure `.env` File

Open `.env` and set the following variables:

```env
# RPC Provider URL
CUSTOM_PROVIDER=your_rpc_here

# Your Ledger wallet address
LEDGER_ACCOUNT=0xYourLedgerAddress
```

| Variable | Description |
|----------|-------------|
| `CUSTOM_PROVIDER` | RPC URL for the target network |
| `LEDGER_ACCOUNT` | Your Ledger wallet's Ethereum address |

---

## 📝 Step 1: Sign Safe Transaction (All Owners)

> **⚠️ Each Safe owner must complete this step**

### 1.1 Navigate to Tool Directory

```bash
cd tools/safeMultisigGrantRole
```

### 1.2 Create Parameters File

```bash
cp parameters.json.example parameters.json
```

### 1.3 Configure `parameters.json`

Edit the file with your specific addresses:

```json
{
"safeAddress": "0x242daE44F5d8fb54B198D03a94dA45B5a4413e21",
"timelockAddress": "0xBBa0935Fa93Eb23de7990b47F0D96a8f75766d13",
"roleReceiver": "0xAddressToReceiveTheRole",
"roleName": "TIMELOCK_ADMIN_ROLE"
}
```

| Parameter | Description |
|-----------|-------------|
| `safeAddress` | Address of the Safe Multisig wallet |
| `timelockAddress` | Address of the Timelock contract |
| `roleReceiver` | Address that will receive `TIMELOCK_ADMIN_ROLE` |
| `roleName` | Role to grant (use `TIMELOCK_ADMIN_ROLE`) |

### 1.4 Sign the Transaction

Each Safe owner runs this command from the **repository root**:

```bash
npx hardhat run tools/safeMultisigGrantRole/signSafeTransaction.ts --network custom
```

**What happens:**
1. Connects to your Ledger
2. Displays transaction details
3. Prompts you to confirm on Ledger device
4. Saves signature to `signedTransactions.json`

### 1.5 Share Signatures

After each owner signs:
- The `signedTransactions.json` file is updated with the new signature
- **Share this file** with other owners or the executor
- Continue until threshold is reached

```
┌────────────────────────────────────────────────────────────────┐
│ Signer 1 runs signSafeTransaction.ts │
│ └─▶ signedTransactions.json created (1/3 signatures) │
├────────────────────────────────────────────────────────────────┤
│ Signer 2 runs signSafeTransaction.ts │
│ └─▶ signedTransactions.json updated (2/3 signatures) │
├────────────────────────────────────────────────────────────────┤
│ Signer 3 runs signSafeTransaction.ts │
│ └─▶ signedTransactions.json updated (3/3 signatures) ✅ │
└────────────────────────────────────────────────────────────────┘
```

### 1.6 Using Different Signer Index (Optional)

If you have multiple accounts configured:

```bash
SIGNER_INDEX=1 npx hardhat run tools/safeMultisigGrantRole/signSafeTransaction.ts --network custom
```

---

## ⚡ Step 2: Execute Safe Transaction

> **Once sufficient signatures are collected (>= threshold)**

### Option A: Execute with Ledger

If your Ledger account has ETH for gas:

```bash
npx hardhat run tools/safeMultisigGrantRole/executeSafeTransaction.ts --network custom
```

### Option B: Execute with Private Key / Mnemonic

If using a different account with ETH:

1. Update `.env` to remove or comment out `LEDGER_ACCOUNT`:
```env
# LEDGER_ACCOUNT=0xYourLedgerAddress
MNEMONIC=your twelve word mnemonic phrase here
```

2. Run the execute script:
```bash
npx hardhat run tools/safeMultisigGrantRole/executeSafeTransaction.ts --network custom
```

### Successful Output

```
╔══════════════════════════════════════════════════════════════╗
║ ✅ TRANSACTION SUCCESS ║
╚══════════════════════════════════════════════════════════════╝

Block: 12345678
Gas Used: 85000
Tx Hash: 0x...
```

---

## 🎖️ Step 3: Self-Grant Operational Roles

> **This step is performed by the account that just received `TIMELOCK_ADMIN_ROLE`**

Now the new admin can grant themselves the operational roles (`PROPOSER_ROLE` and `EXECUTOR_ROLE`).

### 3.1 Navigate to Tool Directory

```bash
cd tools/ledgerGrantRoles
```

### 3.2 Create Parameters File

```bash
cp parameters.json.example parameters.json
```

### 3.3 Configure `parameters.json`

```json
{
"timelockAddress": "0xBBa0935Fa93Eb23de7990b47F0D96a8f75766d13"
}
```

| Parameter | Description |
|-----------|-------------|
| `timelockAddress` | Address of the Timelock contract |

### 3.4 Ensure `.env` is Configured

Make sure your `.env` has the new admin's Ledger address:

```env
CUSTOM_PROVIDER=your_rpc_here
LEDGER_ACCOUNT=0xYourNewAdminLedgerAddress
```

### 3.5 Run the Grant Roles Script

From the **repository root**:

```bash
npx hardhat run tools/ledgerGrantRoles/ledgerGrantRoles.ts --network custom
```

### Successful Output

```
╔══════════════════════════════════════════════════════════════╗
║ LEDGER - GRANT PROPOSER & EXECUTOR ROLES ║
╚══════════════════════════════════════════════════════════════╝

Checking signer roles...
Has TIMELOCK_ADMIN_ROLE: ✅ Yes
Has PROPOSER_ROLE: ❌ No
Has EXECUTOR_ROLE: ❌ No

Granting PROPOSER_ROLE...
Transaction sent: 0x...
✅ PROPOSER_ROLE granted!

Granting EXECUTOR_ROLE...
Transaction sent: 0x...
✅ EXECUTOR_ROLE granted!

╔══════════════════════════════════════════════════════════════╗
║ SUCCESS ║
╚══════════════════════════════════════════════════════════════╝

✅ Account 0x... now has:
- PROPOSER_ROLE (can schedule transactions)
- EXECUTOR_ROLE (can execute ready transactions)
```

---

## 🔧 Troubleshooting

### Ledger Connection Issues

| Issue | Solution |
|-------|----------|
| Ledger not detected | Reconnect USB cable, ensure device is unlocked |
| Ethereum app not found | Open Ethereum app on Ledger before running script |
| Transaction rejected | Enable "Blind signing" in Ledger Ethereum app settings |

### Signature Issues

| Issue | Solution |
|-------|----------|
| "Signer is not a Safe owner" | Verify `LEDGER_ACCOUNT` is a Safe owner |
| "Already signed" | This signer has already signed; another owner needs to sign |
| "Nonce mismatch" | Another transaction was executed; delete `signedTransactions.json` and re-sign |

### Execution Issues

| Issue | Solution |
|-------|----------|
| "Insufficient signatures" | More owners need to run `signSafeTransaction.ts` |
| "Gas estimation failed" | Check signatures are valid; try `FORCE_EXECUTE=true` |
| "Nonce mismatch" | Use `FORCE_EXECUTE=true` or re-collect signatures |

### Role Issues

| Issue | Solution |
|-------|----------|
| "Does not have TIMELOCK_ADMIN_ROLE" | Complete Steps 1 & 2 first to grant the admin role |

---

## 📚 Supported Roles Reference

| Role | Description |
|------|-------------|
| `TIMELOCK_ADMIN_ROLE` | Admin of the timelock (can grant/revoke roles) |
| `PROPOSER_ROLE` | Can propose/schedule transactions |
| `EXECUTOR_ROLE` | Can execute ready (delayed) transactions |
| `CANCELLER_ROLE` | Can cancel pending transactions |

---

## 📁 File Reference

| Path | Description |
|------|-------------|
| `.env` | Environment configuration |
| `tools/safeMultisigGrantRole/parameters.json` | Safe multisig grant role config |
| `tools/safeMultisigGrantRole/signedTransactions.json` | Accumulated signatures (auto-generated) |
| `tools/ledgerGrantRoles/parameters.json` | Self-grant roles config |

---

## 🔗 Quick Command Reference

```bash
# 1. Setup
git clone https://github.com/0xPolygonHermez/zkevm-contracts.git
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The discrepancy in GUIDE_SAFE_MULTISIG_GRANT_ROLE.md references clone URLs from different repositories. Line 73 uses 'agglayer-contracts' while line 375 uses '0xPolygonHermez/zkevm-contracts'. These should be consistent and point to the correct repository.

Copilot uses AI. Check for mistakes.
Comment on lines +489 to +490
git clone https://github.com/agglayer/agglayer-contracts.git
cd zkevm-contracts
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar repository URL inconsistency - uses '0xPolygonHermez/zkevm-contracts' but should likely be 'agglayer/agglayer-contracts' to match the repository context.

Copilot uses AI. Check for mistakes.

- node version: 16.x
- npm version: 7.x
- node version: 22.x
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node version requirement changed from 16.x/18.x to 22.x which is a major version jump. The package.json and hardhat dependencies should be verified to support Node 22.x. Additionally, the hardhat.config.ts shows EDR now requires "node >= 20" which conflicts with the README stating 22.x.

Suggested change
- node version: 22.x
- node version: >= 20.x (tested with 22.x)

Copilot uses AI. Check for mistakes.
const FALLBACK_HANDLER_ADDRESS = '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4';

// Safe configuration
const OWNERS = [''];
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OWNERS array contains an empty string which will cause the deployment to fail. This should be validated before attempting deployment or documented as requiring configuration.

Copilot uses AI. Check for mistakes.
console.log('='.repeat(60));

// Validate configuration
if (OWNERS.length === 0 || OWNERS.some((o) => o === '0x...')) {
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation check for OWNERS uses a string comparison with '0x...' which won't catch the empty string on line 61. The validation should check for empty strings or consider using ethers.isAddress() to validate all addresses.

Suggested change
if (OWNERS.length === 0 || OWNERS.some((o) => o === '0x...')) {
if (
OWNERS.length === 0 ||
OWNERS.some((o) => !o || o.trim() === '' || !ethers.isAddress(o))
) {

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +70
value: number;
data: string;
operation: number;
safeTxGas: number;
baseGas: number;
gasPrice: number;
gasToken: string;
refundReceiver: string;
nonce: number;
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SafeTransaction interface uses number type for the value field, but Ethereum values can exceed JavaScript's safe integer range (2^53-1). This should be string or bigint to handle large ETH amounts safely. This issue is also present in executeSafeTransaction.ts.

Suggested change
value: number;
data: string;
operation: number;
safeTxGas: number;
baseGas: number;
gasPrice: number;
gasToken: string;
refundReceiver: string;
nonce: number;
value: string;
data: string;
operation: number;
safeTxGas: string;
baseGas: string;
gasPrice: string;
gasToken: string;
refundReceiver: string;
nonce: string;

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 370b752b93

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +137 to +139
// Calculate EIP-712 hash
const txHash = calculateSafeTxHash(safeAddress, safeTx, chainId);
logger.info(`Transaction Hash: ${txHash}`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard against signing on the wrong chain

This script computes the Safe EIP-712 hash using the chainId of the current Hardhat network, but it never checks that the transaction being signed was prepared for the same chain (the prepared transaction includes chainId). If a signer runs this on the wrong --network, it will happily append a signature for a different chainId, and the resulting signatures will never validate when executing on the intended chain. Add a check that transaction.chainId === chainId (or refuse when transaction.chainId is set and differs) before hashing/signing to avoid producing unusable signatures.

Useful? React with 👍 / 👎.

Comment on lines +138 to +142
// Try mnemonic from network config or env
const accounts = networkConfig.accounts as any;
const mnemonic = accounts?.mnemonic || process.env.MNEMONIC;
if (mnemonic) {
return ethers.Wallet.fromPhrase(mnemonic).connect(provider);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Respect mnemonic derivation settings from network config

When accounts is configured with a mnemonic plus path/initialIndex in hardhat.config.ts, this helper ignores those settings and always derives the default m/44'/60'/0'/0/0 account via Wallet.fromPhrase. That means deployments can be signed from a different address than every other Hardhat script on the same network config (and from an address that may not be funded). Consider reading accounts.path and accounts.initialIndex and deriving the correct account to avoid unexpected deployer addresses.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants