Skip to content

Commit

Permalink
Add parameter to generate vanity addresses (#493)
Browse files Browse the repository at this point in the history
- Closes #457



---------

Co-authored-by: Uxio Fuentefria <6909403+Uxio0@users.noreply.github.com>
  • Loading branch information
Uxio0 and Uxio0 authored Jan 30, 2025
1 parent 255e7d0 commit 86fc06d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 36 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,9 @@ It is possible to use the environment variable `SAFE_CLI_INTERACTIVE=0` to avoid
### Safe-Creator
```bash
usage: safe-creator [-h] [-v] [--no-confirm] [--threshold THRESHOLD] [--owners OWNERS [OWNERS ...]] [--safe-contract SAFE_CONTRACT] [--proxy-factory PROXY_FACTORY] [--callback-handler CALLBACK_HANDLER] [--salt-nonce SALT_NONCE] [--without-events] [--generate-vanity-addresses] node_url private_key
usage:
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
Example:
safe-creator https://sepolia.drpc.org 0000000000000000000000000000000000000000000000000000000000000000
Example: safe-creator https://sepolia.drpc.org 0000000000000000000000000000000000000000000000000000000000000000
positional arguments:
node_url Ethereum node url
Expand All @@ -113,6 +109,7 @@ positional arguments:
options:
-h, --help show this help message and exit
-v, --version Show program's version number and exit.
--no-confirm Bypass any and all “Yes or no?” messages
--threshold THRESHOLD
Number of owners required to execute transactions on the created Safe. It mustbe greater than 0 and less or equal than the number of owners
--owners OWNERS [OWNERS ...]
Expand All @@ -126,8 +123,13 @@ options:
--salt-nonce SALT_NONCE
Use a custom nonce for the deployment. Same nonce with same deployment configuration will lead to the same Safe address
--without-events Use non events deployment of the Safe instead of the regular one. Recommended for mainnet to save gas costs when using the Safe
--generate-vanity-addresses
Don't deploy the Safe, only generate addresses
```

#### Generate vanity addresses
```bash
safe-creator https://sepolia.drpc.org $PRIVATE_KEY --generate-vanity-addresses --no-confirm
```
## Safe{Core} API/Protocol
Expand Down
82 changes: 55 additions & 27 deletions src/safe_cli/safe_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,15 @@
)


def get_usage_msg():
def get_epilog_msg():
return """
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
Example:
safe-creator https://sepolia.drpc.org 0000000000000000000000000000000000000000000000000000000000000000
"""


def setup_argument_parser():
parser = argparse.ArgumentParser(usage=get_usage_msg())
parser = argparse.ArgumentParser(description=get_epilog_msg())
parser.add_argument(
"-v",
"--version",
Expand All @@ -53,6 +51,12 @@ def setup_argument_parser():
parser.add_argument(
"private_key", help="Deployer private_key", type=check_private_key
)
parser.add_argument(
"--no-confirm",
help="Bypass any and all “Yes or no?” messages",
default=False,
action="store_true",
)
parser.add_argument(
"--threshold",
help="Number of owners required to execute transactions on the created Safe. It must"
Expand Down Expand Up @@ -92,25 +96,35 @@ def setup_argument_parser():
default=secrets.randbits(256),
type=int,
)

parser.add_argument(
"--without-events",
help="Use non events deployment of the Safe instead of the regular one. Recommended for mainnet to save gas costs when using the Safe",
default=False,
action="store_true",
)
parser.add_argument(
"--generate-vanity-addresses",
help="Don't deploy the Safe, only generate addresses",
default=False,
action="store_true",
)

return parser


def main(*args, **kwargs) -> EthereumTxSent:
def main(*args, **kwargs) -> EthereumTxSent | None:
print_formatted_text(text2art("Safe Creator")) # Print fancy text

parser = setup_argument_parser()
args = parser.parse_args()
print_formatted_text(text2art("Safe Creator")) # Print fancy text
node_url: URI = args.node_url
account: LocalAccount = Account.from_key(args.private_key)
no_confirm: bool = args.no_confirm
owners: List[str] = args.owners if args.owners else [account.address]
threshold: int = args.threshold
salt_nonce: int = args.salt_nonce
without_events: bool = args.without_events
generate_vanity_addresses: bool = args.generate_vanity_addresses
to = NULL_ADDRESS
data = b""
payment_token = NULL_ADDRESS
Expand All @@ -123,12 +137,17 @@ def main(*args, **kwargs) -> EthereumTxSent:
)
sys.exit(1)

def yes_or_no(prompt: str) -> bool:
if no_confirm:
return True
return yes_or_no_question(prompt)

ethereum_client = EthereumClient(node_url)
ethereum_network = ethereum_client.get_network()

safe_contract_address = args.safe_contract or (
get_safe_contract_address(ethereum_client)
if args.without_events
if without_events
else get_safe_l2_contract_address(ethereum_client)
)
proxy_factory_address = args.proxy_factory or get_proxy_factory_address(
Expand Down Expand Up @@ -188,7 +207,7 @@ def main(*args, **kwargs) -> EthereumTxSent:
f"Fallback-handler={fallback_handler}\n"
f"Proxy factory={proxy_factory_address}"
)
if yes_or_no_question("Do you want to continue?"):
if yes_or_no("Do you want to continue?"):
safe_contract = get_safe_V1_4_1_contract(
ethereum_client.w3, safe_contract_address
)
Expand All @@ -206,22 +225,31 @@ def main(*args, **kwargs) -> EthereumTxSent:
)

proxy_factory = ProxyFactory(proxy_factory_address, ethereum_client)
expected_safe_address = proxy_factory.calculate_proxy_address(
safe_contract_address, safe_creation_tx_data, salt_nonce
)
if ethereum_client.is_contract(expected_safe_address):
print_formatted_text(f"Safe on {expected_safe_address} is already deployed")
sys.exit(1)

if yes_or_no_question(
f"Safe will be deployed on {expected_safe_address}, looks good?"
):
ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
account, safe_contract_address, safe_creation_tx_data, salt_nonce
)
print_formatted_text(
f"Sent tx with tx-hash={ethereum_tx_sent.tx_hash.hex()} "
f"Safe={ethereum_tx_sent.contract_address} is being created"
if generate_vanity_addresses:
for vanity_salt_nonce in range(2**256):
expected_safe_address = proxy_factory.calculate_proxy_address(
safe_contract_address, safe_creation_tx_data, vanity_salt_nonce
)
print_formatted_text(f"{expected_safe_address} {vanity_salt_nonce}")
else:
expected_safe_address = proxy_factory.calculate_proxy_address(
safe_contract_address, safe_creation_tx_data, salt_nonce
)
print_formatted_text(f"Tx parameters={ethereum_tx_sent.tx}")
return ethereum_tx_sent
if ethereum_client.is_contract(expected_safe_address):
print_formatted_text(
f"Safe on {expected_safe_address} is already deployed"
)
sys.exit(1)

if yes_or_no(
f"Safe will be deployed on {expected_safe_address}, would you like to proceed?"
):
ethereum_tx_sent = proxy_factory.deploy_proxy_contract_with_nonce(
account, safe_contract_address, safe_creation_tx_data, salt_nonce
)
print_formatted_text(
f"Sent tx with tx-hash={ethereum_tx_sent.tx_hash.hex()} "
f"Safe={ethereum_tx_sent.contract_address} is being created"
)
print_formatted_text(f"Tx parameters={ethereum_tx_sent.tx}")
return ethereum_tx_sent
7 changes: 5 additions & 2 deletions tests/test_safe_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ def test_main(self, mock_parse_args: MagicMock):
owners = [Account.create().address]
threshold = 1
mock_parse_args.return_value = argparse.Namespace(
node_url=self.ethereum_node_url,
private_key=owner_account.key.hex(),
no_confirm=False,
owners=owners,
threshold=threshold,
salt_nonce=4815,
node_url=self.ethereum_node_url,
private_key=owner_account.key.hex(),
safe_contract=self.safe_contract.address,
proxy_factory=self.proxy_factory_contract.address,
callback_handler=self.compatibility_fallback_handler.address,
without_events=False,
generate_vanity_addresses=False,
)

safe_address = main().contract_address
Expand Down

0 comments on commit 86fc06d

Please sign in to comment.