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

safe-global / safe-cli / 11784081384

11 Nov 2024 06:22PM CUT coverage: 88.612%. Remained the same
11784081384

push

github

Uxio0
Bump typer from 0.12.5 to 0.13.0

Bumps [typer](https://github.com/fastapi/typer) from 0.12.5 to 0.13.0.
- [Release notes](https://github.com/fastapi/typer/releases)
- [Changelog](https://github.com/fastapi/typer/blob/master/docs/release-notes.md)
- [Commits](https://github.com/fastapi/typer/compare/0.12.5...0.13.0)

---
updated-dependencies:
- dependency-name: typer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

221 of 262 branches covered (84.35%)

Branch coverage included in aggregate %.

2868 of 3224 relevant lines covered (88.96%)

2.67 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
3✔
3
import secrets
3✔
4
import sys
3✔
5
from typing import List
3✔
6

7
from art import text2art
3✔
8
from eth_account import Account
3✔
9
from eth_account.signers.local import LocalAccount
3✔
10
from eth_typing import URI
3✔
11
from hexbytes import HexBytes
3✔
12
from prompt_toolkit import print_formatted_text
3✔
13
from safe_eth.eth import EthereumClient, EthereumTxSent
3✔
14
from safe_eth.eth.constants import NULL_ADDRESS
3✔
15
from safe_eth.eth.contracts import get_safe_V1_4_1_contract
3✔
16
from safe_eth.safe import ProxyFactory, Safe
3✔
17

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

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

33

34
def get_usage_msg():
3✔
35
    return """
3✔
36
        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
37

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

42

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

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

104

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

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

126
    ethereum_client = EthereumClient(node_url)
3✔
127
    ethereum_network = ethereum_client.get_network()
3✔
128

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

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

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

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

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

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

216
        if yes_or_no_question(
3✔
217
            f"Safe will be deployed on {expected_safe_address}, looks good?"
218
        ):
219
            ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
3✔
220
                account, safe_contract_address, safe_creation_tx_data, salt_nonce
221
            )
222
            print_formatted_text(
3✔
223
                f"Sent tx with tx-hash={ethereum_tx_sent.tx_hash.hex()} "
224
                f"Safe={ethereum_tx_sent.contract_address} is being created"
225
            )
226
            print_formatted_text(f"Tx parameters={ethereum_tx_sent.tx}")
3✔
227
            return ethereum_tx_sent
3✔
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