Skip to content

Factory

safe_kit.factory

SafeFactory

Factory class to deploy new Safe contracts.

Source code in safe_kit/factory.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
class SafeFactory:
    """
    Factory class to deploy new Safe contracts.
    """

    def __init__(
        self,
        eth_adapter: EthAdapter,
        safe_singleton_address: str,
        safe_proxy_factory_address: str,
    ):
        self.eth_adapter = eth_adapter
        self.safe_singleton_address = safe_singleton_address
        self.safe_proxy_factory_address = safe_proxy_factory_address
        self.proxy_factory_contract = self.eth_adapter.get_contract(
            address=safe_proxy_factory_address, abi=SAFE_PROXY_FACTORY_ABI
        )

    def _get_initializer_data(self, config: SafeAccountConfig) -> bytes:
        safe_singleton = self.eth_adapter.get_safe_contract(self.safe_singleton_address)
        return cast(
            bytes,
            safe_singleton.encodeABI(
                fn_name="setup",
                args=[
                    config.owners,
                    config.threshold,
                    config.to,
                    HexBytes(config.data),
                    config.fallback_handler,
                    config.payment_token,
                    config.payment,
                    config.payment_receiver,
                ],
            ),
        )

    def predict_safe_address(
        self, config: SafeAccountConfig, salt_nonce: int = 0
    ) -> str:
        """
        Predicts the address of the Safe that would be deployed with the given
        configuration.
        """
        initializer = self._get_initializer_data(config)
        params: SafeProxyFactoryCreateProxyWithNonceParams = {
            "_singleton": self.safe_singleton_address,
            "initializer": initializer,
            "saltNonce": salt_nonce,
        }
        return cast(
            str,
            self.proxy_factory_contract.functions.createProxyWithNonce(**params).call(),
        )

    def predict_safe_address_v1_4_1(
        self, config: SafeAccountConfig, salt_nonce: int = 0
    ) -> str:
        """
        Predicts the address of the Safe (v1.4.1) that would be deployed.
        Uses createChainSpecificProxyWithNonce.
        """
        initializer = self._get_initializer_data(config)
        params: SafeProxyFactoryCreateChainSpecificProxyWithNonceParams = {
            "_singleton": self.safe_singleton_address,
            "initializer": initializer,
            "saltNonce": salt_nonce,
        }
        return cast(
            str,
            self.proxy_factory_contract.functions.createChainSpecificProxyWithNonce(
                **params
            ).call(),
        )

    def deploy_safe(
        self,
        config: SafeAccountConfig,
        salt_nonce: int = 0,
        wait_for_deployment: bool = False,
    ) -> Safe:
        """
        Deploys a new Safe contract.
        Returns a Safe instance with the predicted address.

        Args:
            config: The Safe account configuration.
            salt_nonce: The salt nonce for address prediction.
            wait_for_deployment: If True, wait for the deployment transaction
                to be mined.

        Returns:
            A Safe instance pointing to the deployed Safe.
        """
        signer = self.eth_adapter.get_signer_address()
        if not signer:
            raise ValueError("No signer configured in the adapter")

        initializer = self._get_initializer_data(config)
        safe_address = self.predict_safe_address(config, salt_nonce)

        try:
            params: SafeProxyFactoryCreateProxyWithNonceParams = {
                "_singleton": self.safe_singleton_address,
                "initializer": initializer,
                "saltNonce": salt_nonce,
            }
            tx_hash = self.proxy_factory_contract.functions.createProxyWithNonce(
                **params
            ).transact({"from": signer})

            if wait_for_deployment:
                self.eth_adapter.wait_for_transaction_receipt(tx_hash.hex())
        except Exception as e:
            raise handle_contract_error(e) from e

        return Safe(self.eth_adapter, safe_address)

    def deploy_safe_v1_4_1(
        self,
        config: SafeAccountConfig,
        salt_nonce: int = 0,
        wait_for_deployment: bool = False,
    ) -> Safe:
        """
        Deploys a new Safe contract (v1.4.1).
        Returns a Safe instance with the predicted address.

        Args:
            config: The Safe account configuration.
            salt_nonce: The salt nonce for address prediction.
            wait_for_deployment: If True, wait for the deployment transaction
                to be mined.

        Returns:
            A Safe instance pointing to the deployed Safe.
        """
        signer = self.eth_adapter.get_signer_address()
        if not signer:
            raise ValueError("No signer configured in the adapter")

        initializer = self._get_initializer_data(config)
        safe_address = self.predict_safe_address_v1_4_1(config, salt_nonce)

        try:
            params: SafeProxyFactoryCreateChainSpecificProxyWithNonceParams = {
                "_singleton": self.safe_singleton_address,
                "initializer": initializer,
                "saltNonce": salt_nonce,
            }
            create_fn = self.proxy_factory_contract.functions
            tx_hash = create_fn.createChainSpecificProxyWithNonce(
                **params
            ).transact({"from": signer})

            if wait_for_deployment:
                self.eth_adapter.wait_for_transaction_receipt(tx_hash.hex())
        except Exception as e:
            raise handle_contract_error(e) from e

        return Safe(self.eth_adapter, safe_address)

deploy_safe(config, salt_nonce=0, wait_for_deployment=False)

Deploys a new Safe contract. Returns a Safe instance with the predicted address.

Parameters:

Name Type Description Default
config SafeAccountConfig

The Safe account configuration.

required
salt_nonce int

The salt nonce for address prediction.

0
wait_for_deployment bool

If True, wait for the deployment transaction to be mined.

False

Returns:

Type Description
Safe

A Safe instance pointing to the deployed Safe.

Source code in safe_kit/factory.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def deploy_safe(
    self,
    config: SafeAccountConfig,
    salt_nonce: int = 0,
    wait_for_deployment: bool = False,
) -> Safe:
    """
    Deploys a new Safe contract.
    Returns a Safe instance with the predicted address.

    Args:
        config: The Safe account configuration.
        salt_nonce: The salt nonce for address prediction.
        wait_for_deployment: If True, wait for the deployment transaction
            to be mined.

    Returns:
        A Safe instance pointing to the deployed Safe.
    """
    signer = self.eth_adapter.get_signer_address()
    if not signer:
        raise ValueError("No signer configured in the adapter")

    initializer = self._get_initializer_data(config)
    safe_address = self.predict_safe_address(config, salt_nonce)

    try:
        params: SafeProxyFactoryCreateProxyWithNonceParams = {
            "_singleton": self.safe_singleton_address,
            "initializer": initializer,
            "saltNonce": salt_nonce,
        }
        tx_hash = self.proxy_factory_contract.functions.createProxyWithNonce(
            **params
        ).transact({"from": signer})

        if wait_for_deployment:
            self.eth_adapter.wait_for_transaction_receipt(tx_hash.hex())
    except Exception as e:
        raise handle_contract_error(e) from e

    return Safe(self.eth_adapter, safe_address)

deploy_safe_v1_4_1(config, salt_nonce=0, wait_for_deployment=False)

Deploys a new Safe contract (v1.4.1). Returns a Safe instance with the predicted address.

Parameters:

Name Type Description Default
config SafeAccountConfig

The Safe account configuration.

required
salt_nonce int

The salt nonce for address prediction.

0
wait_for_deployment bool

If True, wait for the deployment transaction to be mined.

False

Returns:

Type Description
Safe

A Safe instance pointing to the deployed Safe.

Source code in safe_kit/factory.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def deploy_safe_v1_4_1(
    self,
    config: SafeAccountConfig,
    salt_nonce: int = 0,
    wait_for_deployment: bool = False,
) -> Safe:
    """
    Deploys a new Safe contract (v1.4.1).
    Returns a Safe instance with the predicted address.

    Args:
        config: The Safe account configuration.
        salt_nonce: The salt nonce for address prediction.
        wait_for_deployment: If True, wait for the deployment transaction
            to be mined.

    Returns:
        A Safe instance pointing to the deployed Safe.
    """
    signer = self.eth_adapter.get_signer_address()
    if not signer:
        raise ValueError("No signer configured in the adapter")

    initializer = self._get_initializer_data(config)
    safe_address = self.predict_safe_address_v1_4_1(config, salt_nonce)

    try:
        params: SafeProxyFactoryCreateChainSpecificProxyWithNonceParams = {
            "_singleton": self.safe_singleton_address,
            "initializer": initializer,
            "saltNonce": salt_nonce,
        }
        create_fn = self.proxy_factory_contract.functions
        tx_hash = create_fn.createChainSpecificProxyWithNonce(
            **params
        ).transact({"from": signer})

        if wait_for_deployment:
            self.eth_adapter.wait_for_transaction_receipt(tx_hash.hex())
    except Exception as e:
        raise handle_contract_error(e) from e

    return Safe(self.eth_adapter, safe_address)

predict_safe_address(config, salt_nonce=0)

Predicts the address of the Safe that would be deployed with the given configuration.

Source code in safe_kit/factory.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def predict_safe_address(
    self, config: SafeAccountConfig, salt_nonce: int = 0
) -> str:
    """
    Predicts the address of the Safe that would be deployed with the given
    configuration.
    """
    initializer = self._get_initializer_data(config)
    params: SafeProxyFactoryCreateProxyWithNonceParams = {
        "_singleton": self.safe_singleton_address,
        "initializer": initializer,
        "saltNonce": salt_nonce,
    }
    return cast(
        str,
        self.proxy_factory_contract.functions.createProxyWithNonce(**params).call(),
    )

predict_safe_address_v1_4_1(config, salt_nonce=0)

Predicts the address of the Safe (v1.4.1) that would be deployed. Uses createChainSpecificProxyWithNonce.

Source code in safe_kit/factory.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def predict_safe_address_v1_4_1(
    self, config: SafeAccountConfig, salt_nonce: int = 0
) -> str:
    """
    Predicts the address of the Safe (v1.4.1) that would be deployed.
    Uses createChainSpecificProxyWithNonce.
    """
    initializer = self._get_initializer_data(config)
    params: SafeProxyFactoryCreateChainSpecificProxyWithNonceParams = {
        "_singleton": self.safe_singleton_address,
        "initializer": initializer,
        "saltNonce": salt_nonce,
    }
    return cast(
        str,
        self.proxy_factory_contract.functions.createChainSpecificProxyWithNonce(
            **params
        ).call(),
    )