• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

safe-global / safe-cli / 11343896716

15 Oct 2024 10:09AM CUT coverage: 88.64%. Remained the same
11343896716

push

github

Uxio0
Bump flake8 from 7.1.0 to 7.1.1

Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.0 to 7.1.1.
- [Commits](https://github.com/pycqa/flake8/compare/7.1.0...7.1.1)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

221 of 262 branches covered (84.35%)

Branch coverage included in aggregate %.

2869 of 3224 relevant lines covered (88.99%)

3.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.66
/src/safe_cli/safe_creator.py
1
#!/bin/env python3
2
import argparse
4✔
3
import secrets
4✔
4
import sys
4✔
5
from typing import List
4✔
6

7
from art import text2art
4✔
8
from eth_account import Account
4✔
9
from eth_account.signers.local import LocalAccount
4✔
10
from eth_typing import URI
4✔
11
from hexbytes import HexBytes
4✔
12
from prompt_toolkit import print_formatted_text
4✔
13

14
from gnosis.eth import EthereumClient, EthereumTxSent
4✔
15
from gnosis.eth.constants import NULL_ADDRESS
4✔
16
from gnosis.eth.contracts import get_safe_V1_4_1_contract
4✔
17
from gnosis.safe import ProxyFactory, Safe
4✔
18

19
from safe_cli.safe_addresses import (
4✔
20
    get_default_fallback_handler_address,
21
    get_proxy_factory_address,
22
    get_safe_contract_address,
23
    get_safe_l2_contract_address,
24
)
25
from safe_cli.utils import yes_or_no_question
4✔
26

27
from . import VERSION
4✔
28
from .argparse_validators import (
4✔
29
    check_ethereum_address,
30
    check_positive_integer,
31
    check_private_key,
32
)
33

34

35
def get_usage_msg():
4✔
36
    return """
4✔
37
        safe-creator [-h] [-v] [--threshold THRESHOLD] [--owners OWNERS [OWNERS ...]] [--safe-contract SAFE_CONTRACT] [--proxy-factory PROXY_FACTORY] [--callback-handler CALLBACK_HANDLER] [--salt-nonce SALT_NONCE] [--without-events] node_url private_key
38

39
        Example:
40
            safe-creator https://sepolia.drpc.org 0000000000000000000000000000000000000000000000000000000000000000
41
    """
42

43

44
def setup_argument_parser():
4✔
45
    parser = argparse.ArgumentParser(usage=get_usage_msg())
4✔
46
    parser.add_argument(
4✔
47
        "-v",
48
        "--version",
49
        action="version",
50
        version=f"Safe Creator v{VERSION}",
51
        help="Show program's version number and exit.",
52
    )
53
    parser.add_argument("node_url", help="Ethereum node url")
4✔
54
    parser.add_argument(
4✔
55
        "private_key", help="Deployer private_key", type=check_private_key
56
    )
57
    parser.add_argument(
4✔
58
        "--threshold",
59
        help="Number of owners required to execute transactions on the created Safe. It must"
60
        "be greater than 0 and less or equal than the number of owners",
61
        type=check_positive_integer,
62
        default=1,
63
    )
64
    parser.add_argument(
4✔
65
        "--owners",
66
        help="Owners. By default it will be just the deployer",
67
        nargs="+",
68
        type=check_ethereum_address,
69
    )
70
    parser.add_argument(
4✔
71
        "--safe-contract",
72
        help="Use a custom Safe master copy",
73
        default=None,
74
        type=check_ethereum_address,
75
    )
76
    parser.add_argument(
4✔
77
        "--proxy-factory",
78
        help="Use a custom proxy factory",
79
        default=None,
80
        type=check_ethereum_address,
81
    )
82
    parser.add_argument(
4✔
83
        "--callback-handler",
84
        help="Use a custom fallback handler. It is not required for Safe Master Copies "
85
        "with version < 1.1.0",
86
        default=None,
87
        type=check_ethereum_address,
88
    )
89
    parser.add_argument(
4✔
90
        "--salt-nonce",
91
        help="Use a custom nonce for the deployment. Same nonce with same deployment configuration will "
92
        "lead to the same Safe address ",
93
        default=secrets.randbits(256),
94
        type=int,
95
    )
96

97
    parser.add_argument(
4✔
98
        "--without-events",
99
        help="Use non events deployment of the Safe instead of the regular one. Recommended for mainnet to save gas costs when using the Safe",
100
        default=False,
101
        action="store_true",
102
    )
103
    return parser
4✔
104

105

106
def main(*args, **kwargs) -> EthereumTxSent:
4✔
107
    parser = setup_argument_parser()
4✔
108
    args = parser.parse_args()
4✔
109
    print_formatted_text(text2art("Safe Creator"))  # Print fancy text
4✔
110
    node_url: URI = args.node_url
4✔
111
    account: LocalAccount = Account.from_key(args.private_key)
4✔
112
    owners: List[str] = args.owners if args.owners else [account.address]
4✔
113
    threshold: int = args.threshold
4✔
114
    salt_nonce: int = args.salt_nonce
4✔
115
    to = NULL_ADDRESS
4✔
116
    data = b""
4✔
117
    payment_token = NULL_ADDRESS
4✔
118
    payment = 0
4✔
119
    payment_receiver = NULL_ADDRESS
4✔
120

121
    if len(owners) < threshold:
4✔
122
        print_formatted_text(
×
123
            "Threshold cannot be bigger than the number of unique owners"
124
        )
125
        sys.exit(1)
×
126

127
    ethereum_client = EthereumClient(node_url)
4✔
128
    ethereum_network = ethereum_client.get_network()
4✔
129

130
    safe_contract_address = args.safe_contract or (
4✔
131
        get_safe_contract_address(ethereum_client)
132
        if args.without_events
133
        else get_safe_l2_contract_address(ethereum_client)
134
    )
135
    proxy_factory_address = args.proxy_factory or get_proxy_factory_address(
4✔
136
        ethereum_client
137
    )
138
    fallback_handler = args.callback_handler or get_default_fallback_handler_address(
4✔
139
        ethereum_client
140
    )
141

142
    if not ethereum_client.is_contract(safe_contract_address):
4✔
143
        print_formatted_text(
×
144
            f"Safe contract address {safe_contract_address} "
145
            f"does not exist on network {ethereum_network.name}"
146
        )
147
        sys.exit(1)
×
148
    elif not ethereum_client.is_contract(proxy_factory_address):
4✔
149
        print_formatted_text(
×
150
            f"Proxy contract address {proxy_factory_address} "
151
            f"does not exist on network {ethereum_network.name}"
152
        )
153
        sys.exit(1)
×
154
    elif fallback_handler != NULL_ADDRESS and not ethereum_client.is_contract(
4✔
155
        fallback_handler
156
    ):
157
        print_formatted_text(
×
158
            f"Fallback handler address {fallback_handler} "
159
            f"does not exist on network {ethereum_network.name}"
160
        )
161
        sys.exit(1)
×
162

163
    account_balance: int = ethereum_client.get_balance(account.address)
4✔
164
    if not account_balance:
4✔
165
        print_formatted_text(
×
166
            "Client does not have any funds. Let's try anyway in case it's a network without gas costs"
167
        )
168
    else:
169
        ether_account_balance = round(
4✔
170
            ethereum_client.w3.from_wei(account_balance, "ether"), 6
171
        )
172
        print_formatted_text(
4✔
173
            f"Network {ethereum_client.get_network().name} - Sender {account.address} - "
174
            f"Balance: {ether_account_balance}Ξ"
175
        )
176

177
    if not ethereum_client.w3.eth.get_code(
4✔
178
        safe_contract_address
179
    ) or not ethereum_client.w3.eth.get_code(proxy_factory_address):
180
        print_formatted_text("Network not supported")
×
181
        sys.exit(1)
×
182

183
    print_formatted_text(
4✔
184
        f"Creating new Safe with owners={owners} threshold={threshold} salt-nonce={salt_nonce}"
185
    )
186
    safe_version = Safe(safe_contract_address, ethereum_client).retrieve_version()
4✔
187
    print_formatted_text(
4✔
188
        f"Safe-master-copy={safe_contract_address} version={safe_version}\n"
189
        f"Fallback-handler={fallback_handler}\n"
190
        f"Proxy factory={proxy_factory_address}"
191
    )
192
    if yes_or_no_question("Do you want to continue?"):
4✔
193
        safe_contract = get_safe_V1_4_1_contract(
4✔
194
            ethereum_client.w3, safe_contract_address
195
        )
196
        safe_creation_tx_data = HexBytes(
4✔
197
            safe_contract.functions.setup(
198
                owners,
199
                threshold,
200
                to,
201
                data,
202
                fallback_handler,
203
                payment_token,
204
                payment,
205
                payment_receiver,
206
            ).build_transaction({"gas": 1, "gasPrice": 1})["data"]
207
        )
208

209
        proxy_factory = ProxyFactory(proxy_factory_address, ethereum_client)
4✔
210
        expected_safe_address = proxy_factory.calculate_proxy_address(
4✔
211
            safe_contract_address, safe_creation_tx_data, salt_nonce
212
        )
213
        if ethereum_client.is_contract(expected_safe_address):
4✔
214
            print_formatted_text(f"Safe on {expected_safe_address} is already deployed")
4✔
215
            sys.exit(1)
4✔
216

217
        if yes_or_no_question(
4✔
218
            f"Safe will be deployed on {expected_safe_address}, looks good?"
219
        ):
220
            ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
4✔
221
                account, safe_contract_address, safe_creation_tx_data, salt_nonce
222
            )
223
            print_formatted_text(
4✔
224
                f"Sent tx with tx-hash={ethereum_tx_sent.tx_hash.hex()} "
225
                f"Safe={ethereum_tx_sent.contract_address} is being created"
226
            )
227
            print_formatted_text(f"Tx parameters={ethereum_tx_sent.tx}")
4✔
228
            return ethereum_tx_sent
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc