solver-multirpc 3.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solver_multirpc-3.1.4/PKG-INFO +241 -0
- solver_multirpc-3.1.4/README.md +221 -0
- solver_multirpc-3.1.4/pyproject.toml +21 -0
- solver_multirpc-3.1.4/src/__init__.py +0 -0
- solver_multirpc-3.1.4/src/multirpc/__init__.py +1 -0
- solver_multirpc-3.1.4/src/multirpc/async_multi_rpc_interface.py +125 -0
- solver_multirpc-3.1.4/src/multirpc/base_multi_rpc_interface.py +640 -0
- solver_multirpc-3.1.4/src/multirpc/constants.py +52 -0
- solver_multirpc-3.1.4/src/multirpc/exceptions.py +77 -0
- solver_multirpc-3.1.4/src/multirpc/gas_estimation.py +151 -0
- solver_multirpc-3.1.4/src/multirpc/sync_multi_rpc_interface.py +135 -0
- solver_multirpc-3.1.4/src/multirpc/tx_trace.py +153 -0
- solver_multirpc-3.1.4/src/multirpc/utils.py +265 -0
- solver_multirpc-3.1.4/src/tests/__init__.py +0 -0
- solver_multirpc-3.1.4/src/tests/abi.json +34 -0
- solver_multirpc-3.1.4/src/tests/constants.py +78 -0
- solver_multirpc-3.1.4/src/tests/contract.sol +16 -0
- solver_multirpc-3.1.4/src/tests/test.py +138 -0
- solver_multirpc-3.1.4/src/tests/test_settings.py.example +7 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: solver-multirpc
|
|
3
|
+
Version: 3.1.4
|
|
4
|
+
Summary: A robust Python library for interacting with Ethereum smart contracts via multiple RPC endpoints, ensuring reliability, availability, and load distribution with automatic retries on failure.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: rorschach
|
|
7
|
+
Author-email: rorschach45001@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: eth-account (>=0.13.0)
|
|
16
|
+
Requires-Dist: multicallable (>=6.0.0)
|
|
17
|
+
Requires-Dist: web3 (>7.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# solver-MultiRpc: Reliable Ethereum Interactions with Multiple RPCs
|
|
21
|
+
|
|
22
|
+
`solver-MultiRpc` is a robust library designed to interact with Ethereum smart contracts
|
|
23
|
+
using multiple RPC endpoints. This ensures reliability and availability
|
|
24
|
+
by distributing the load across various endpoints and retrying operations on failure.
|
|
25
|
+
The library provides both asynchronous (`AsyncMultiRpc`) and
|
|
26
|
+
synchronous (`MultiRpc`) interfaces to suit different use cases.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Multiple RPC Support**: Seamlessly switch between different RPCs to ensure uninterrupted interactions.
|
|
31
|
+
- **Gas Management**: Fetch gas prices from multiple sources to ensure transactions are sent with an appropriate fee.
|
|
32
|
+
- **Robust Error Handling**: Designed to handle failures gracefully, increasing the reliability of your applications.
|
|
33
|
+
- **Easy-to-use API**: Interact with Ethereum smart contracts using a simple and intuitive API.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Install `solver-MultiRpc` using pip:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install solver-multiRPC
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
Here's a quick example to get you started:
|
|
46
|
+
|
|
47
|
+
### Asynchronous Usage
|
|
48
|
+
|
|
49
|
+
Below is an example of how to use the AsyncMultiRpc class for asynchronous operations:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
import json
|
|
54
|
+
from src.multirpc.utils import NestedDict
|
|
55
|
+
from multirpc import AsyncMultiRpc
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def main():
|
|
59
|
+
rpcs = NestedDict({
|
|
60
|
+
"view": {
|
|
61
|
+
1: ['https://1rpc.io/ftm', 'https://rpcapi.fantom.network', 'https://rpc3.fantom.network'],
|
|
62
|
+
2: ['https://rpc.fantom.network', 'https://rpc2.fantom.network', ],
|
|
63
|
+
3: ['https://rpc.ankr.com/fantom'],
|
|
64
|
+
},
|
|
65
|
+
"transaction": {
|
|
66
|
+
1: ['https://1rpc.io/ftm', 'https://rpcapi.fantom.network', 'https://rpc3.fantom.network'],
|
|
67
|
+
2: ['https://rpc.fantom.network', 'https://rpc2.fantom.network', ],
|
|
68
|
+
3: ['https://rpc.ankr.com/fantom'],
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
with open("abi.json", "r") as f:
|
|
72
|
+
abi = json.load(f)
|
|
73
|
+
multi_rpc = AsyncMultiRpc(rpcs, 'YOUR_CONTRACT_ADDRESS', contract_abi=abi, enable_estimate_gas_limit=True)
|
|
74
|
+
multi_rpc.set_account("YOUR_PUBLIC_ADDRESS", "YOUR_PRIVATE_KEY")
|
|
75
|
+
|
|
76
|
+
result = await multi_rpc.functions.YOUR_FUNCTION().call()
|
|
77
|
+
print(result)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Synchronous Usage
|
|
84
|
+
|
|
85
|
+
Below is an example of how to use the MultiRpc class for synchronous operations:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from multirpc import MultiRpc
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main():
|
|
92
|
+
multi_rpc = MultiRpc(rpcs, 'YOUR_CONTRACT_ADDRESS', contract_abi=abi, enable_estimate_gas_limit=True)
|
|
93
|
+
multi_rpc.set_account("YOUR_PUBLIC_ADDRESS", "YOUR_PRIVATE_KEY")
|
|
94
|
+
|
|
95
|
+
result = multi_rpc.functions.YOUR_FUNCTION().call()
|
|
96
|
+
print(result)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
main()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Replace placeholders like `YOUR_CONTRACT_ADDRESS`, `YOUR_PUBLIC_ADDRESS`, `YOUR_PRIVATE_KEY`, and `YOUR_FUNCTION` with
|
|
103
|
+
appropriate values.
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
### Initialization
|
|
108
|
+
|
|
109
|
+
Initialize the `MultiRpc` class with your RPC URLs, contract address, and contract ABI:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
multi_rpc = MultiRpc(rpcs, contract_address='YOUR_CONTRACT_ADDRESS', contract_abi=abi)
|
|
113
|
+
```
|
|
114
|
+
- `enable_estimate_gas_limit=True` will check if tx can be done successfully without paying fee,
|
|
115
|
+
and also calculate gas limit for tx
|
|
116
|
+
- You can pass a list of RPCs to `rpcs_supporting_tx_trace=[]` to identify which of the provided RPCs (`rpcs`) support `tx_trace`.
|
|
117
|
+
Then, when a transaction fails, you can retrieve the trace of the transaction.
|
|
118
|
+
|
|
119
|
+
### Setting Account
|
|
120
|
+
|
|
121
|
+
Set the Ethereum account details (address and private key) for sending transactions:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
multi_rpc.set_account("YOUR_PUBLIC_ADDRESS", "YOUR_PRIVATE_KEY")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Calling Contract Functions
|
|
128
|
+
|
|
129
|
+
Call a function from your contract:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
result = await multi_rpc.functions.YOUR_FUNCTION().call()
|
|
133
|
+
```
|
|
134
|
+
By default we return tx_receipt(wait for 90 second).
|
|
135
|
+
if you don't want to return tx_receipt, pass `wait_for_receipt=0` to `call()`
|
|
136
|
+
|
|
137
|
+
### Calling Contract with another Private Key
|
|
138
|
+
|
|
139
|
+
You can call a transaction function with a different private key by passing the
|
|
140
|
+
`private_key`, `address` parameter to the `call()` method. Here’s an example:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
result = await multi_rpc.functions.YOUR_FUNCTION().call(address=PublicKey, private_key=PrivateKey)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Using Block Identifier in Calls
|
|
147
|
+
|
|
148
|
+
You can specify a block identifier when calling view functions to get the state of the
|
|
149
|
+
contract at a specific block. Here's an example:
|
|
150
|
+
|
|
151
|
+
_Note that the majority of free RPCs only support querying blocks up to 10 minutes earlier._
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# You can use 'latest', 'earliest', or a specific block number
|
|
155
|
+
result = multi_rpc.functions.yourViewFunction().call(block_identifier='latest')
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Using multicall for view function Calls
|
|
159
|
+
|
|
160
|
+
you can also use `mutlicall()` for calling a view function multiple time with different parameters. Here's an example:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
results = multi_rpc.functions.yourViewFunction([(param1, params2), (param1, params2)]).multicall()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Passing View Policy
|
|
167
|
+
|
|
168
|
+
You can specify a view policy to determine how view function calls are handled.
|
|
169
|
+
The available view policies are `MostUpdated` and `FirstSuccess`. Here’s an example:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
multi_rpc = MultiRpc(rpc_urls, contract_address, contract_abi, view_policy=ViewPolicy.FirstSuccess)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Passing Gas Estimation to MultiRpc
|
|
176
|
+
|
|
177
|
+
You can pass a `GasEstimation` object to the `MultiRpc` or `AsyncMultiRpc` class
|
|
178
|
+
to configure how gas prices are estimated. Here is an example of how to do this:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from multirpc import MultiRpc, GasEstimation, GasEstimationMethod
|
|
182
|
+
|
|
183
|
+
gas_estimation = GasEstimation(
|
|
184
|
+
chain_id=1, # Mainnet
|
|
185
|
+
providers=[], # List of AsyncWeb3 providers
|
|
186
|
+
default_method=GasEstimationMethod.GAS_API_PROVIDER,
|
|
187
|
+
gas_api_provider='https://gasstation-mainnet.matic.network' # Replace with your API provider
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Pass the GasEstimation object to MultiRpc
|
|
191
|
+
multi_rpc = MultiRpc(rpc_urls, contract_address, contract_abi, gas_estimation=gas_estimation)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The `GasEstimation` class allows you to implement a custom gas estimation method.
|
|
195
|
+
You need to extend the `GasEstimation` class and override the _`custom_gas_estimation` method with your custom logic.
|
|
196
|
+
Here is an example:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from multirpc import GasEstimation, TxPriority, GasEstimationMethod
|
|
200
|
+
from web3 import Web3
|
|
201
|
+
from web3.types import Wei
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class CustomGasEstimation(GasEstimation):
|
|
205
|
+
|
|
206
|
+
async def _custom_gas_estimation(self, priority: TxPriority, gas_upper_bound: float) -> dict:
|
|
207
|
+
# Your custom gas estimation logic here
|
|
208
|
+
custom_gas_price = 50 # Replace with your custom logic
|
|
209
|
+
if custom_gas_price > gas_upper_bound:
|
|
210
|
+
raise OutOfRangeTransactionFee(f"Custom gas price {custom_gas_price} exceeds upper bound {gas_upper_bound}")
|
|
211
|
+
return {
|
|
212
|
+
"gasPrice": Web3.to_wei(custom_gas_price, 'gwei')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# Create an instance of your custom gas estimation
|
|
217
|
+
custom_gas_estimation = CustomGasEstimation(
|
|
218
|
+
chain_id=1,
|
|
219
|
+
providers=[],
|
|
220
|
+
default_method=GasEstimationMethod.CUSTOM,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Use it with MultiRpc
|
|
224
|
+
multi_rpc = MultiRpc(rpc_urls, contract_address, contract_abi, gas_estimation=custom_gas_estimation)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
You can specify which gas estimation method in `call()` method. Here's an example:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
tx_hash = multi_rpc.functions.yourTransactionFunction().call(
|
|
231
|
+
gas_estimation_method=GasEstimationMethod.RPC, # Specify the gas estimation method
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
By default, we check all possible ways(api, rpc, fixed, custom) to get `gasPrice`.
|
|
236
|
+
but, if you pass method we only use passed method
|
|
237
|
+
|
|
238
|
+
## Contributing
|
|
239
|
+
|
|
240
|
+
Contributions are welcome! Please open an issue or submit a pull request on our GitHub repository.
|
|
241
|
+
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# solver-MultiRpc: Reliable Ethereum Interactions with Multiple RPCs
|
|
2
|
+
|
|
3
|
+
`solver-MultiRpc` is a robust library designed to interact with Ethereum smart contracts
|
|
4
|
+
using multiple RPC endpoints. This ensures reliability and availability
|
|
5
|
+
by distributing the load across various endpoints and retrying operations on failure.
|
|
6
|
+
The library provides both asynchronous (`AsyncMultiRpc`) and
|
|
7
|
+
synchronous (`MultiRpc`) interfaces to suit different use cases.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multiple RPC Support**: Seamlessly switch between different RPCs to ensure uninterrupted interactions.
|
|
12
|
+
- **Gas Management**: Fetch gas prices from multiple sources to ensure transactions are sent with an appropriate fee.
|
|
13
|
+
- **Robust Error Handling**: Designed to handle failures gracefully, increasing the reliability of your applications.
|
|
14
|
+
- **Easy-to-use API**: Interact with Ethereum smart contracts using a simple and intuitive API.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Install `solver-MultiRpc` using pip:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install solver-multiRPC
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
Here's a quick example to get you started:
|
|
27
|
+
|
|
28
|
+
### Asynchronous Usage
|
|
29
|
+
|
|
30
|
+
Below is an example of how to use the AsyncMultiRpc class for asynchronous operations:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import asyncio
|
|
34
|
+
import json
|
|
35
|
+
from src.multirpc.utils import NestedDict
|
|
36
|
+
from multirpc import AsyncMultiRpc
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def main():
|
|
40
|
+
rpcs = NestedDict({
|
|
41
|
+
"view": {
|
|
42
|
+
1: ['https://1rpc.io/ftm', 'https://rpcapi.fantom.network', 'https://rpc3.fantom.network'],
|
|
43
|
+
2: ['https://rpc.fantom.network', 'https://rpc2.fantom.network', ],
|
|
44
|
+
3: ['https://rpc.ankr.com/fantom'],
|
|
45
|
+
},
|
|
46
|
+
"transaction": {
|
|
47
|
+
1: ['https://1rpc.io/ftm', 'https://rpcapi.fantom.network', 'https://rpc3.fantom.network'],
|
|
48
|
+
2: ['https://rpc.fantom.network', 'https://rpc2.fantom.network', ],
|
|
49
|
+
3: ['https://rpc.ankr.com/fantom'],
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
with open("abi.json", "r") as f:
|
|
53
|
+
abi = json.load(f)
|
|
54
|
+
multi_rpc = AsyncMultiRpc(rpcs, 'YOUR_CONTRACT_ADDRESS', contract_abi=abi, enable_estimate_gas_limit=True)
|
|
55
|
+
multi_rpc.set_account("YOUR_PUBLIC_ADDRESS", "YOUR_PRIVATE_KEY")
|
|
56
|
+
|
|
57
|
+
result = await multi_rpc.functions.YOUR_FUNCTION().call()
|
|
58
|
+
print(result)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Synchronous Usage
|
|
65
|
+
|
|
66
|
+
Below is an example of how to use the MultiRpc class for synchronous operations:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from multirpc import MultiRpc
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main():
|
|
73
|
+
multi_rpc = MultiRpc(rpcs, 'YOUR_CONTRACT_ADDRESS', contract_abi=abi, enable_estimate_gas_limit=True)
|
|
74
|
+
multi_rpc.set_account("YOUR_PUBLIC_ADDRESS", "YOUR_PRIVATE_KEY")
|
|
75
|
+
|
|
76
|
+
result = multi_rpc.functions.YOUR_FUNCTION().call()
|
|
77
|
+
print(result)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
main()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Replace placeholders like `YOUR_CONTRACT_ADDRESS`, `YOUR_PUBLIC_ADDRESS`, `YOUR_PRIVATE_KEY`, and `YOUR_FUNCTION` with
|
|
84
|
+
appropriate values.
|
|
85
|
+
|
|
86
|
+
## Documentation
|
|
87
|
+
|
|
88
|
+
### Initialization
|
|
89
|
+
|
|
90
|
+
Initialize the `MultiRpc` class with your RPC URLs, contract address, and contract ABI:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
multi_rpc = MultiRpc(rpcs, contract_address='YOUR_CONTRACT_ADDRESS', contract_abi=abi)
|
|
94
|
+
```
|
|
95
|
+
- `enable_estimate_gas_limit=True` will check if tx can be done successfully without paying fee,
|
|
96
|
+
and also calculate gas limit for tx
|
|
97
|
+
- You can pass a list of RPCs to `rpcs_supporting_tx_trace=[]` to identify which of the provided RPCs (`rpcs`) support `tx_trace`.
|
|
98
|
+
Then, when a transaction fails, you can retrieve the trace of the transaction.
|
|
99
|
+
|
|
100
|
+
### Setting Account
|
|
101
|
+
|
|
102
|
+
Set the Ethereum account details (address and private key) for sending transactions:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
multi_rpc.set_account("YOUR_PUBLIC_ADDRESS", "YOUR_PRIVATE_KEY")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Calling Contract Functions
|
|
109
|
+
|
|
110
|
+
Call a function from your contract:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
result = await multi_rpc.functions.YOUR_FUNCTION().call()
|
|
114
|
+
```
|
|
115
|
+
By default we return tx_receipt(wait for 90 second).
|
|
116
|
+
if you don't want to return tx_receipt, pass `wait_for_receipt=0` to `call()`
|
|
117
|
+
|
|
118
|
+
### Calling Contract with another Private Key
|
|
119
|
+
|
|
120
|
+
You can call a transaction function with a different private key by passing the
|
|
121
|
+
`private_key`, `address` parameter to the `call()` method. Here’s an example:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
result = await multi_rpc.functions.YOUR_FUNCTION().call(address=PublicKey, private_key=PrivateKey)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Using Block Identifier in Calls
|
|
128
|
+
|
|
129
|
+
You can specify a block identifier when calling view functions to get the state of the
|
|
130
|
+
contract at a specific block. Here's an example:
|
|
131
|
+
|
|
132
|
+
_Note that the majority of free RPCs only support querying blocks up to 10 minutes earlier._
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# You can use 'latest', 'earliest', or a specific block number
|
|
136
|
+
result = multi_rpc.functions.yourViewFunction().call(block_identifier='latest')
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Using multicall for view function Calls
|
|
140
|
+
|
|
141
|
+
you can also use `mutlicall()` for calling a view function multiple time with different parameters. Here's an example:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
results = multi_rpc.functions.yourViewFunction([(param1, params2), (param1, params2)]).multicall()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Passing View Policy
|
|
148
|
+
|
|
149
|
+
You can specify a view policy to determine how view function calls are handled.
|
|
150
|
+
The available view policies are `MostUpdated` and `FirstSuccess`. Here’s an example:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
multi_rpc = MultiRpc(rpc_urls, contract_address, contract_abi, view_policy=ViewPolicy.FirstSuccess)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Passing Gas Estimation to MultiRpc
|
|
157
|
+
|
|
158
|
+
You can pass a `GasEstimation` object to the `MultiRpc` or `AsyncMultiRpc` class
|
|
159
|
+
to configure how gas prices are estimated. Here is an example of how to do this:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from multirpc import MultiRpc, GasEstimation, GasEstimationMethod
|
|
163
|
+
|
|
164
|
+
gas_estimation = GasEstimation(
|
|
165
|
+
chain_id=1, # Mainnet
|
|
166
|
+
providers=[], # List of AsyncWeb3 providers
|
|
167
|
+
default_method=GasEstimationMethod.GAS_API_PROVIDER,
|
|
168
|
+
gas_api_provider='https://gasstation-mainnet.matic.network' # Replace with your API provider
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Pass the GasEstimation object to MultiRpc
|
|
172
|
+
multi_rpc = MultiRpc(rpc_urls, contract_address, contract_abi, gas_estimation=gas_estimation)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The `GasEstimation` class allows you to implement a custom gas estimation method.
|
|
176
|
+
You need to extend the `GasEstimation` class and override the _`custom_gas_estimation` method with your custom logic.
|
|
177
|
+
Here is an example:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from multirpc import GasEstimation, TxPriority, GasEstimationMethod
|
|
181
|
+
from web3 import Web3
|
|
182
|
+
from web3.types import Wei
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class CustomGasEstimation(GasEstimation):
|
|
186
|
+
|
|
187
|
+
async def _custom_gas_estimation(self, priority: TxPriority, gas_upper_bound: float) -> dict:
|
|
188
|
+
# Your custom gas estimation logic here
|
|
189
|
+
custom_gas_price = 50 # Replace with your custom logic
|
|
190
|
+
if custom_gas_price > gas_upper_bound:
|
|
191
|
+
raise OutOfRangeTransactionFee(f"Custom gas price {custom_gas_price} exceeds upper bound {gas_upper_bound}")
|
|
192
|
+
return {
|
|
193
|
+
"gasPrice": Web3.to_wei(custom_gas_price, 'gwei')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# Create an instance of your custom gas estimation
|
|
198
|
+
custom_gas_estimation = CustomGasEstimation(
|
|
199
|
+
chain_id=1,
|
|
200
|
+
providers=[],
|
|
201
|
+
default_method=GasEstimationMethod.CUSTOM,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Use it with MultiRpc
|
|
205
|
+
multi_rpc = MultiRpc(rpc_urls, contract_address, contract_abi, gas_estimation=custom_gas_estimation)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
You can specify which gas estimation method in `call()` method. Here's an example:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
tx_hash = multi_rpc.functions.yourTransactionFunction().call(
|
|
212
|
+
gas_estimation_method=GasEstimationMethod.RPC, # Specify the gas estimation method
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
By default, we check all possible ways(api, rpc, fixed, custom) to get `gasPrice`.
|
|
217
|
+
but, if you pass method we only use passed method
|
|
218
|
+
|
|
219
|
+
## Contributing
|
|
220
|
+
|
|
221
|
+
Contributions are welcome! Please open an issue or submit a pull request on our GitHub repository.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "solver-multirpc"
|
|
3
|
+
version = "3.1.4"
|
|
4
|
+
description = "A robust Python library for interacting with Ethereum smart contracts via multiple RPC endpoints, ensuring reliability, availability, and load distribution with automatic retries on failure."
|
|
5
|
+
authors = ["rorschach <rorschach45001@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [
|
|
9
|
+
{ include = "src" }
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = ">=3.10,<4.0"
|
|
14
|
+
web3 = ">7.0.0"
|
|
15
|
+
multicallable = ">=6.0.0"
|
|
16
|
+
eth-account = ">=0.13.0"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["poetry-core"]
|
|
21
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .base_multi_rpc_interface import BaseMultiRpc
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from eth_typing import Address, ChecksumAddress
|
|
6
|
+
from web3._utils.contracts import encode_transaction_data # noqa
|
|
7
|
+
from web3.types import BlockData, BlockIdentifier, TxReceipt
|
|
8
|
+
|
|
9
|
+
from . import BaseMultiRpc
|
|
10
|
+
from .base_multi_rpc_interface import BaseContractFunction
|
|
11
|
+
from .constants import GasLimit, GasUpperBound, ViewPolicy
|
|
12
|
+
from .exceptions import DontHaveThisRpcType, KwargsNotSupportedInMultiCall, TransactionTypeNotSupportedInMultiCall
|
|
13
|
+
from .gas_estimation import GasEstimation, GasEstimationMethod
|
|
14
|
+
from .utils import ContractFunctionType, NestedDict, TxPriority, thread_safe
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AsyncMultiRpc(BaseMultiRpc):
|
|
18
|
+
"""
|
|
19
|
+
This class is used to be more sure when running web3 view calls and sending transactions by using of multiple RPCs.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@thread_safe
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
rpc_urls: NestedDict,
|
|
26
|
+
contract_address: Union[Address, ChecksumAddress, str],
|
|
27
|
+
contract_abi: Dict,
|
|
28
|
+
rpcs_supporting_tx_trace: Optional[List[str]] = None,
|
|
29
|
+
view_policy: ViewPolicy = ViewPolicy.MostUpdated,
|
|
30
|
+
gas_estimation: Optional[GasEstimation] = None,
|
|
31
|
+
gas_limit: int = GasLimit,
|
|
32
|
+
gas_upper_bound: int = GasUpperBound,
|
|
33
|
+
apm=None,
|
|
34
|
+
enable_estimate_gas_limit: bool = False,
|
|
35
|
+
is_proof_authority: bool = False,
|
|
36
|
+
multicall_custom_address: str = None,
|
|
37
|
+
log_level: logging = logging.WARN,
|
|
38
|
+
setup_on_init: bool = True
|
|
39
|
+
):
|
|
40
|
+
super().__init__(rpc_urls, contract_address, contract_abi, rpcs_supporting_tx_trace,
|
|
41
|
+
view_policy, gas_estimation, gas_limit,
|
|
42
|
+
gas_upper_bound, apm, enable_estimate_gas_limit,
|
|
43
|
+
is_proof_authority, multicall_custom_address, log_level)
|
|
44
|
+
|
|
45
|
+
for func_abi in self.contract_abi:
|
|
46
|
+
if func_abi.get("stateMutability") in ("view", "pure"):
|
|
47
|
+
function_type = ContractFunctionType.View
|
|
48
|
+
elif func_abi.get("type") == "function":
|
|
49
|
+
function_type = ContractFunctionType.Transaction
|
|
50
|
+
else:
|
|
51
|
+
continue
|
|
52
|
+
self.functions.__setattr__(
|
|
53
|
+
func_abi["name"],
|
|
54
|
+
self.ContractFunction(func_abi["name"], func_abi, self, function_type),
|
|
55
|
+
)
|
|
56
|
+
if setup_on_init:
|
|
57
|
+
asyncio.run(self.setup())
|
|
58
|
+
|
|
59
|
+
async def get_nonce(self, address: Union[Address, ChecksumAddress, str]) -> int:
|
|
60
|
+
return await super()._get_nonce(address)
|
|
61
|
+
|
|
62
|
+
async def get_tx_receipt(self, tx_hash) -> TxReceipt:
|
|
63
|
+
return await super().get_tx_receipt(tx_hash)
|
|
64
|
+
|
|
65
|
+
async def get_block(self, block_identifier: BlockIdentifier = 'latest',
|
|
66
|
+
full_transactions: bool = False) -> BlockData:
|
|
67
|
+
return await super().get_block(block_identifier, full_transactions)
|
|
68
|
+
|
|
69
|
+
async def get_block_number(self) -> int:
|
|
70
|
+
return await super().get_block_number()
|
|
71
|
+
|
|
72
|
+
class ContractFunction(BaseContractFunction):
|
|
73
|
+
def __call__(self, *args, **kwargs):
|
|
74
|
+
cf = AsyncMultiRpc.ContractFunction(self.name, self.abi, self.mr, self.typ)
|
|
75
|
+
cf.args = args
|
|
76
|
+
cf.kwargs = kwargs
|
|
77
|
+
return cf
|
|
78
|
+
|
|
79
|
+
async def call(
|
|
80
|
+
self,
|
|
81
|
+
address: str = None,
|
|
82
|
+
private_key: str = None,
|
|
83
|
+
gas_limit: int = None,
|
|
84
|
+
gas_upper_bound: int = None,
|
|
85
|
+
wait_for_receipt: int = 90,
|
|
86
|
+
priority: TxPriority = TxPriority.Low,
|
|
87
|
+
gas_estimation_method: GasEstimationMethod = None,
|
|
88
|
+
block_identifier: Union[str, int] = 'latest',
|
|
89
|
+
enable_estimate_gas_limit: Optional[bool] = None,
|
|
90
|
+
):
|
|
91
|
+
if self.mr.providers.get(self.typ) is None:
|
|
92
|
+
raise DontHaveThisRpcType(f"Doesn't have {self.typ} RPCs")
|
|
93
|
+
if self.typ == ContractFunctionType.View:
|
|
94
|
+
return await self.mr._call_view_function(
|
|
95
|
+
self.name, block_identifier, False, *self.args, **self.kwargs,
|
|
96
|
+
)
|
|
97
|
+
elif self.typ == ContractFunctionType.Transaction:
|
|
98
|
+
return await self.mr._call_tx_function(
|
|
99
|
+
func_name=self.name,
|
|
100
|
+
func_args=self.args,
|
|
101
|
+
func_kwargs=self.kwargs,
|
|
102
|
+
address=address or self.mr.address,
|
|
103
|
+
private_key=private_key or self.mr.private_key,
|
|
104
|
+
gas_limit=gas_limit or self.mr.gas_limit,
|
|
105
|
+
gas_upper_bound=gas_upper_bound or self.mr.gas_upper_bound,
|
|
106
|
+
wait_for_receipt=wait_for_receipt,
|
|
107
|
+
priority=priority,
|
|
108
|
+
gas_estimation_method=gas_estimation_method,
|
|
109
|
+
enable_estimate_gas_limit=enable_estimate_gas_limit
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def multicall(
|
|
113
|
+
self,
|
|
114
|
+
block_identifier: Union[str, int] = 'latest',
|
|
115
|
+
):
|
|
116
|
+
if self.mr.providers.get(self.typ) is None:
|
|
117
|
+
raise DontHaveThisRpcType(f"Doesn't have {self.typ} RPCs")
|
|
118
|
+
if self.kwargs != {}:
|
|
119
|
+
raise KwargsNotSupportedInMultiCall
|
|
120
|
+
if self.typ == ContractFunctionType.View:
|
|
121
|
+
return await self.mr._call_view_function(
|
|
122
|
+
self.name, block_identifier, True, *self.args, **self.kwargs,
|
|
123
|
+
)
|
|
124
|
+
elif self.typ == ContractFunctionType.Transaction:
|
|
125
|
+
raise TransactionTypeNotSupportedInMultiCall
|