Skip to content

Extras

ether.extras is subpackage designed for writing applications based on Ethereum smart contracts. It contains the following modules:

ether.extras.tools

Utility functions for working with smart contracts

ContractMap module-attribute

ContractMap = dict[str, Union[AsyncContract, ABI, Contract]]

load_contracts

load_contracts(provider, defi, network, contracts_path, version=None)

Load contract data from JSON and ABI files.

Function requires that snake-cased defi name match with the corresponding folder containing data of contracts.

Folder structure:

contracts/ <--- This path corresponds to contracts_path argument
├── defi1/
│   ├── contracts.json
│   ├── contract1.abi
│   └── contract2.abi
└── defi2/
    ├── contracts.json
    ├── contract1.abi
    └── contract2.abi

If you want to support multiple versions of DeFi protocol, you must have the following folder structure. Sub-folder of DeFi folder must have the format v[VERSION_NUMBER]

contracts/  <--- This path corresponds to contracts_path argument
└── defi/
    ├── v1/
    │   ├── contracts.json
    │   ├── contract1.abi
    │   └── contract2.abi
    └── v2/
        ├── contracts.json
        └── contract1.abi

Structure of contracts.json:

{
  "contract1": {
    "abi": "contract1.abi",
    "address": {
      "Arbitrum": "0xe977Fa8D8AE7D3D6e28c17A868EF04bD301c583f",
      "Optimism": "0xe977Fa8D8AE7D3D6e28c17A868EF04bD301c583f"
    }
  },
  "contract2": {
    "abi": "contract2.abi",
    "address": {
      "Arbitrum": "0xe977Fa8D8AE7D3D6e28c17A868EF04bD301c583f",
      "Optimism": "0x2B4069517957735bE00ceE0fadAE88a26365528f",
    }
  }
}
PARAMETER DESCRIPTION
provider

An instance of AsyncWeb3 or Web3.

TYPE: BaseWeb3

defi

The name of the DeFi.

TYPE: str

network

The name of the built-in Ethereum-based network or custom network configuration

TYPE: Union[Network, str]

contracts_path

The path to the directory containing subfolders with contracts.json and ABI files.

TYPE: Union[str, Path]

version

Optional version number of the DeFi protocol. Specify when DeFi folder contains multiple subfolders for specific versions of DeFi protocol.

TYPE: Optional[int] DEFAULT: None

Example
class _UniswapProxy:
    def __init__(
            self,
            wallet: AsyncWallet,
            defi_name: DefiName,
            router: AsyncContract,
            factory: AsyncContract,
            pool_abi: ABI,
            quoter_v2: Optional[AsyncContract] = None,
            non_fungible_position_manager: Optional[AsyncContract] = None,
            version: ContractVersion = 2,
    ):
        self.__version = version

        match version:
            case 2:
                self.__proxy = UniswapRouterV2(
                    wallet,
                    defi_name,
                    router,
                    factory,
                    pool_abi
                )
            case 3:
                self.__proxy = UniswapRouterV3(
                    wallet,
                    defi_name,
                    router,
                    factory,
                    quoter_v2,
                    non_fungible_position_manager,
                    pool_abi
                )


class Uniswap(_UniswapProxy):
    def __init__(
            self,
            wallet: AsyncWallet,
            version: ContractVersion = 3
    ):
        contracts = load_contracts(
            wallet.provider,
            'Uniswap',
            wallet.network.name,
            CONTRACTS_PATH,
            version
        )

        super().__init__(
            wallet=wallet,
            version=version,
            defi_name='Uniswap',
            **contracts
        )
RETURNS DESCRIPTION
ContractMap

The dictionary mapping contract names to their corresponding contracts.

Source code in ether/extras/tools.py
 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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def load_contracts(
        provider: BaseWeb3,
        defi: str,
        network: Union[Network, str],
        contracts_path: Union[str, Path],
        version: Optional[int] = None
) -> ContractMap:
    """
    Load contract data from JSON and ABI files.

    Function requires that snake-cased defi name match with the corresponding folder containing data of contracts.

    Folder structure:

    ```text
    contracts/ <--- This path corresponds to contracts_path argument

    ├── defi1/
    │   ├── contracts.json
    │   ├── contract1.abi
    │   └── contract2.abi

    └── defi2/
        ├── contracts.json
        ├── contract1.abi
        └── contract2.abi
    ```

    If you want to support multiple versions of DeFi protocol, you must have the following folder structure. Sub-folder
    of DeFi folder must have the format `v[VERSION_NUMBER]`

    ```text
    contracts/  <--- This path corresponds to contracts_path argument

    └── defi/
        ├── v1/
        │   ├── contracts.json
        │   ├── contract1.abi
        │   └── contract2.abi

        └── v2/
            ├── contracts.json
            └── contract1.abi
    ```

    Structure of contracts.json:

    ```json
    {
      "contract1": {
        "abi": "contract1.abi",
        "address": {
          "Arbitrum": "0xe977Fa8D8AE7D3D6e28c17A868EF04bD301c583f",
          "Optimism": "0xe977Fa8D8AE7D3D6e28c17A868EF04bD301c583f"
        }
      },
      "contract2": {
        "abi": "contract2.abi",
        "address": {
          "Arbitrum": "0xe977Fa8D8AE7D3D6e28c17A868EF04bD301c583f",
          "Optimism": "0x2B4069517957735bE00ceE0fadAE88a26365528f",
        }
      }
    }
    ```

    Args:
        provider: An instance of AsyncWeb3 or Web3.
        defi: The name of the DeFi.
        network: The name of the built-in Ethereum-based network or custom network configuration
        contracts_path: The path to the directory containing subfolders with contracts.json and ABI files.
        version: Optional version number of the DeFi protocol. Specify when DeFi folder contains multiple subfolders
                    for specific versions of DeFi protocol.

    Example:
        ```python
        class _UniswapProxy:
            def __init__(
                    self,
                    wallet: AsyncWallet,
                    defi_name: DefiName,
                    router: AsyncContract,
                    factory: AsyncContract,
                    pool_abi: ABI,
                    quoter_v2: Optional[AsyncContract] = None,
                    non_fungible_position_manager: Optional[AsyncContract] = None,
                    version: ContractVersion = 2,
            ):
                self.__version = version

                match version:
                    case 2:
                        self.__proxy = UniswapRouterV2(
                            wallet,
                            defi_name,
                            router,
                            factory,
                            pool_abi
                        )
                    case 3:
                        self.__proxy = UniswapRouterV3(
                            wallet,
                            defi_name,
                            router,
                            factory,
                            quoter_v2,
                            non_fungible_position_manager,
                            pool_abi
                        )


        class Uniswap(_UniswapProxy):
            def __init__(
                    self,
                    wallet: AsyncWallet,
                    version: ContractVersion = 3
            ):
                contracts = load_contracts(
                    wallet.provider,
                    'Uniswap',
                    wallet.network.name,
                    CONTRACTS_PATH,
                    version
                )

                super().__init__(
                    wallet=wallet,
                    version=version,
                    defi_name='Uniswap',
                    **contracts
                )
        ```

    Returns:
           The dictionary mapping contract names to their corresponding contracts.
    """
    folder_name = _snake_case(defi)
    path = Path(contracts_path) / Path(folder_name)

    if isinstance(network, Network):
        network = network.name

    if version:
        path /= Path(f'v{version}')

    contract_data = {}
    with open(path / Path("contracts.json")) as file:
        content = json.load(file)
        contract_names = content.keys()

        for name in contract_names:
            contract_content = content[name]
            if 'address' in contract_content:
                addresses = content[name]['address']
                if network not in addresses:
                    raise ContractNotFound(defi, network, addresses.keys())

                contract_data[name] = {'address': addresses[network]}

    for name in contract_names:
        with open(path / Path(f'{name}.abi')) as file:
            abi = json.load(file)
            if name in contract_data:
                contract_data[name]['abi'] = abi
            else:
                contract_data[f"{name}_abi"] = {'abi': abi}

    contracts = {}
    for key, value in contract_data.items():
        abi = value['abi']

        address = value.get('address')
        contracts[key] = provider.eth.contract(address=address, abi=abi) if address else abi

    return contracts

change_network

change_network(contracts_path)

Decorator to reload contracts of DeFi before executing method if wallet instance changed the network.

PARAMETER DESCRIPTION
contracts_path

The path to the directory containing subfolders with contracts.json and ABI files.

TYPE: Union[Path, str]

Example
from pathlib import Path

CONTRACTS_PATH = Path(__file__).parent / Path('contracts')

class Uniswap(Defi):
    def __init__(
            self,
            wallet: AsyncWallet,
    ):
        super().__init__(wallet=wallet, name='Uniswap', version=3)

    @change_network(CONTRACTS_PATH)
    async def swap(
            self,
            input_token: Token,
            output_token: Token,
            amount_in: TokenAmount,
            slippage: float = 0.5,
            from_wei: bool = True
    ) -> HexBytes:
        ...
Source code in ether/extras/tools.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
def change_network(contracts_path: Union[Path, str]):
    """
    Decorator to reload contracts of DeFi before executing method if wallet instance changed the network.

    Args:
        contracts_path: The path to the directory containing subfolders with contracts.json and ABI files.

    Example:
        ```python
        from pathlib import Path

        CONTRACTS_PATH = Path(__file__).parent / Path('contracts')

        class Uniswap(Defi):
            def __init__(
                    self,
                    wallet: AsyncWallet,
            ):
                super().__init__(wallet=wallet, name='Uniswap', version=3)

            @change_network(CONTRACTS_PATH)
            async def swap(
                    self,
                    input_token: Token,
                    output_token: Token,
                    amount_in: TokenAmount,
                    slippage: float = 0.5,
                    from_wei: bool = True
            ) -> HexBytes:
                ...
        ```
    """
    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            network = self.network
            wallet: Union[Wallet, AsyncWallet] = self.wallet

            if network != wallet.network:
                defi = self._name or self.__class__.__name__

                try:
                    version = self.version
                except AttributeError:
                    version = None

                contracts = load_contracts(wallet.provider, defi, wallet.network.name, contracts_path, version)

                for key, value in contracts.items():
                    setattr(self, f'_{key}', value)

                self._network = wallet.network
                self._provider = wallet.provider

            return func(self, *args, **kwargs)

        return wrapper
    return decorator

ether.extras.defi

Defi

Defi(wallet, name, version=None)

Bases: ABC

Abstract base class for implementing classes interacting decentralized finance (DeFi) protocols.

PARAMETER DESCRIPTION
wallet

An instance of ether.AsyncWallet or ether.Wallet.

TYPE: WalletT

name

Name of the specific DeFi.

TYPE: str

version

Optional version number of the DeFi protocol

TYPE: Optional[int] DEFAULT: None

Source code in ether/extras/defi.py
21
22
23
24
25
26
27
28
29
30
def __init__(
        self,
        wallet: WalletT,
        name: str,
        version: Optional[int] = None
):
    self.__wallet = wallet
    self._network = wallet.network
    self._name = name
    self._version = version

wallet property

wallet

The wallet instance associated with this DeFi instance.

network property

network

Current network of Defi represented as Network instance

provider property

provider

Gets the AsyncWeb3/Web3 provider associated with the wallet instance.

version property

version

The version number of the DeFi protocol.