micro-web3 0.1.0__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.
- micro_web3-0.1.0/LICENSE +21 -0
- micro_web3-0.1.0/PKG-INFO +155 -0
- micro_web3-0.1.0/README.md +134 -0
- micro_web3-0.1.0/pyproject.toml +78 -0
- micro_web3-0.1.0/pyproject.toml.orig +59 -0
- micro_web3-0.1.0/src/micro_web3/__init__.py +58 -0
- micro_web3-0.1.0/src/micro_web3/_keccak.py +14 -0
- micro_web3-0.1.0/src/micro_web3/abi.py +629 -0
- micro_web3-0.1.0/src/micro_web3/addresses.py +62 -0
- micro_web3-0.1.0/src/micro_web3/contract.py +135 -0
- micro_web3-0.1.0/src/micro_web3/errors.py +48 -0
- micro_web3-0.1.0/src/micro_web3/methods.py +114 -0
- micro_web3-0.1.0/src/micro_web3/multicall.py +63 -0
- micro_web3-0.1.0/src/micro_web3/py.typed +0 -0
- micro_web3-0.1.0/src/micro_web3/rpc.py +143 -0
micro_web3-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yabir Benchakhtir
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: micro-web3
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Minimal Ethereum JSON-RPC and ABI client
|
|
5
|
+
Keywords: ethereum,web3,json-rpc,abi,keccak,multicall
|
|
6
|
+
Author: Yabir Benchakhtir
|
|
7
|
+
Author-email: Yabir Benchakhtir <git@yabirgb.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: pycryptodome>=3.20,<4
|
|
18
|
+
Requires-Dist: urllib3>=2,<3
|
|
19
|
+
Requires-Python: >=3.13
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# micro-web3
|
|
23
|
+
|
|
24
|
+
A small Ethereum client: JSON-RPC over urllib3, a hand-rolled
|
|
25
|
+
Solidity ABI codec, EIP-55 checksums, and Multicall3 batching.
|
|
26
|
+
|
|
27
|
+
Built to replace the slice of web3.py that a backend actually uses, without
|
|
28
|
+
pulling in web3, eth-abi, eth-utils, eth-typing, or their transitive
|
|
29
|
+
dependencies. Two runtime dependencies: `pycryptodome` for Keccak-256 and
|
|
30
|
+
`urllib3` for HTTP.
|
|
31
|
+
|
|
32
|
+
- Python 3.13+
|
|
33
|
+
- HTTP via `urllib3` with one connection pool per node; no retries, no redirects,
|
|
34
|
+
and proxy environment variables are not read
|
|
35
|
+
- No ABI/type/hex-byte libraries
|
|
36
|
+
- `py.typed`
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```console
|
|
41
|
+
uv add micro-web3
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from micro_web3 import Contract, RpcNode
|
|
48
|
+
|
|
49
|
+
node = RpcNode("https://ethereum-rpc.publicnode.com", timeout=4)
|
|
50
|
+
node.endpoint_uri # 'https://ethereum-rpc.publicnode.com'
|
|
51
|
+
node.is_connected() # web3_clientVersion probe; False instead of raising
|
|
52
|
+
node.close() # optional: release pooled connections (pool size: RpcNode(uri, pool_maxsize=10))
|
|
53
|
+
|
|
54
|
+
erc20 = Contract("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", abi)
|
|
55
|
+
erc20.call(node, "decimals") # 6
|
|
56
|
+
erc20.call(node, "balanceOf", [holder]) # int
|
|
57
|
+
erc20.encode("balanceOf", [holder]) # '0x70a08231...' (hex, selector included)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`Contract.call` performs one `eth_call` and decodes the result: a single
|
|
61
|
+
output is unwrapped, multiple outputs come back as a tuple.
|
|
62
|
+
|
|
63
|
+
### Logs
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from micro_web3 import get_logs
|
|
67
|
+
|
|
68
|
+
logs = get_logs(node, address=token, topics=[transfer_topic], from_block=0, to_block="latest")
|
|
69
|
+
logs[0]["blockNumber"] # '0x...' — raw JSON-RPC, never normalised
|
|
70
|
+
logs[0]["data"] # '0x...'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Raw hex is deliberate: it matches the Etherscan/Blockscout shapes, so one
|
|
74
|
+
consumer can handle every indexer identically. Block filters accept `int`,
|
|
75
|
+
decimal strings, hex strings or tags; they are emitted as JSON-RPC quantities.
|
|
76
|
+
|
|
77
|
+
### Multicall
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from micro_web3 import try_aggregate
|
|
81
|
+
|
|
82
|
+
results = try_aggregate(
|
|
83
|
+
node,
|
|
84
|
+
[
|
|
85
|
+
(token_a, erc20.encode("balanceOf", [holder])),
|
|
86
|
+
(token_b, erc20.encode("balanceOf", [holder])),
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
# [b'...', None] — one entry per call, None where that call reverted
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`try_aggregate` uses Multicall3's `tryAggregate(false, calls)`.
|
|
93
|
+
|
|
94
|
+
### ABI
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from micro_web3 import decode_outputs, encode_call, to_checksum_address
|
|
98
|
+
|
|
99
|
+
data = encode_call(abi, "transfer", [to, amount])
|
|
100
|
+
values = decode_outputs(["uint256"], "0x" + "00" * 31 + "2a")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Supported types: `address`, `bool`, `uintN`, `intN`, `bytesN`, `bytes`,
|
|
104
|
+
`string`, `function`, fixed and dynamic arrays, tuples, and nested
|
|
105
|
+
combinations (`(bool,bytes)[]`, `uint256[2][]`, ...).
|
|
106
|
+
|
|
107
|
+
Decoding matches eth_abi 6: addresses are lower-case hex strings, arrays and
|
|
108
|
+
tuples are tuples, `bytesN` is `bytes`. Use `to_checksum_address` when you need
|
|
109
|
+
EIP-55 form.
|
|
110
|
+
|
|
111
|
+
## Errors
|
|
112
|
+
|
|
113
|
+
Every failure derives from `RpcError`:
|
|
114
|
+
|
|
115
|
+
| Class | Raised for |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `RpcConnectionError` | unreachable node, timeout, non-JSON HTTP response |
|
|
118
|
+
| `RpcResponseError` | JSON-RPC `error` object; `.code`, `.message`, `.data` |
|
|
119
|
+
| `AbiError` | unknown type, bad value, undecodable payload |
|
|
120
|
+
|
|
121
|
+
Reverts arrive as `RpcResponseError` with `execution reverted` in the message
|
|
122
|
+
and the revert payload in `.data`. Looking up a function that the ABI does not
|
|
123
|
+
contain raises `AttributeError`, which callers can use to fall back to another
|
|
124
|
+
indexer.
|
|
125
|
+
|
|
126
|
+
## What replaces what
|
|
127
|
+
|
|
128
|
+
| web3.py | micro-web3 |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `Web3(Web3.HTTPProvider(uri, request_kwargs={'timeout': 4}))` | `RpcNode(uri, timeout=4)` |
|
|
131
|
+
| `node.is_connected()` | `node.is_connected()` |
|
|
132
|
+
| `web3.manager.request_blocking(method, params)` | `node.request(method, params)` |
|
|
133
|
+
| `contract.encode_abi(name, args=)` | `contract.encode(name, args)` |
|
|
134
|
+
| `contract._find_matching_fn_abi(name, *args)` | `contract.function(name, args)` |
|
|
135
|
+
| `contract.caller().fn(*args)` | `contract.call(node, name, args)` |
|
|
136
|
+
| `web3.codec.decode(types, data)` | `decode_outputs(types, data)` |
|
|
137
|
+
| `node.eth.get_logs(options)` | `get_logs(node, ...)` |
|
|
138
|
+
| `node.eth.get_block('latest')['number']` | `get_block_number(node)` |
|
|
139
|
+
| `Web3.to_checksum_address` | `to_checksum_address` |
|
|
140
|
+
| `web3.exceptions.Web3Exception` | `RpcError` |
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
```console
|
|
145
|
+
uv sync --all-groups
|
|
146
|
+
uv run pytest
|
|
147
|
+
MICRON_RPC_URL=https://ethereum-rpc.publicnode.com uv run pytest tests/test_live.py
|
|
148
|
+
uv run ruff check .
|
|
149
|
+
uv run ruff format --check .
|
|
150
|
+
uv run pyrefly check
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The ABI tests run against vectors generated with eth_abi 6 (`tests/vectors.json`),
|
|
154
|
+
so the hand-rolled codec is checked against the reference implementation
|
|
155
|
+
without depending on it.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# micro-web3
|
|
2
|
+
|
|
3
|
+
A small Ethereum client: JSON-RPC over urllib3, a hand-rolled
|
|
4
|
+
Solidity ABI codec, EIP-55 checksums, and Multicall3 batching.
|
|
5
|
+
|
|
6
|
+
Built to replace the slice of web3.py that a backend actually uses, without
|
|
7
|
+
pulling in web3, eth-abi, eth-utils, eth-typing, or their transitive
|
|
8
|
+
dependencies. Two runtime dependencies: `pycryptodome` for Keccak-256 and
|
|
9
|
+
`urllib3` for HTTP.
|
|
10
|
+
|
|
11
|
+
- Python 3.13+
|
|
12
|
+
- HTTP via `urllib3` with one connection pool per node; no retries, no redirects,
|
|
13
|
+
and proxy environment variables are not read
|
|
14
|
+
- No ABI/type/hex-byte libraries
|
|
15
|
+
- `py.typed`
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```console
|
|
20
|
+
uv add micro-web3
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from micro_web3 import Contract, RpcNode
|
|
27
|
+
|
|
28
|
+
node = RpcNode("https://ethereum-rpc.publicnode.com", timeout=4)
|
|
29
|
+
node.endpoint_uri # 'https://ethereum-rpc.publicnode.com'
|
|
30
|
+
node.is_connected() # web3_clientVersion probe; False instead of raising
|
|
31
|
+
node.close() # optional: release pooled connections (pool size: RpcNode(uri, pool_maxsize=10))
|
|
32
|
+
|
|
33
|
+
erc20 = Contract("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", abi)
|
|
34
|
+
erc20.call(node, "decimals") # 6
|
|
35
|
+
erc20.call(node, "balanceOf", [holder]) # int
|
|
36
|
+
erc20.encode("balanceOf", [holder]) # '0x70a08231...' (hex, selector included)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`Contract.call` performs one `eth_call` and decodes the result: a single
|
|
40
|
+
output is unwrapped, multiple outputs come back as a tuple.
|
|
41
|
+
|
|
42
|
+
### Logs
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from micro_web3 import get_logs
|
|
46
|
+
|
|
47
|
+
logs = get_logs(node, address=token, topics=[transfer_topic], from_block=0, to_block="latest")
|
|
48
|
+
logs[0]["blockNumber"] # '0x...' — raw JSON-RPC, never normalised
|
|
49
|
+
logs[0]["data"] # '0x...'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Raw hex is deliberate: it matches the Etherscan/Blockscout shapes, so one
|
|
53
|
+
consumer can handle every indexer identically. Block filters accept `int`,
|
|
54
|
+
decimal strings, hex strings or tags; they are emitted as JSON-RPC quantities.
|
|
55
|
+
|
|
56
|
+
### Multicall
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from micro_web3 import try_aggregate
|
|
60
|
+
|
|
61
|
+
results = try_aggregate(
|
|
62
|
+
node,
|
|
63
|
+
[
|
|
64
|
+
(token_a, erc20.encode("balanceOf", [holder])),
|
|
65
|
+
(token_b, erc20.encode("balanceOf", [holder])),
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
# [b'...', None] — one entry per call, None where that call reverted
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`try_aggregate` uses Multicall3's `tryAggregate(false, calls)`.
|
|
72
|
+
|
|
73
|
+
### ABI
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from micro_web3 import decode_outputs, encode_call, to_checksum_address
|
|
77
|
+
|
|
78
|
+
data = encode_call(abi, "transfer", [to, amount])
|
|
79
|
+
values = decode_outputs(["uint256"], "0x" + "00" * 31 + "2a")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Supported types: `address`, `bool`, `uintN`, `intN`, `bytesN`, `bytes`,
|
|
83
|
+
`string`, `function`, fixed and dynamic arrays, tuples, and nested
|
|
84
|
+
combinations (`(bool,bytes)[]`, `uint256[2][]`, ...).
|
|
85
|
+
|
|
86
|
+
Decoding matches eth_abi 6: addresses are lower-case hex strings, arrays and
|
|
87
|
+
tuples are tuples, `bytesN` is `bytes`. Use `to_checksum_address` when you need
|
|
88
|
+
EIP-55 form.
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
Every failure derives from `RpcError`:
|
|
93
|
+
|
|
94
|
+
| Class | Raised for |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `RpcConnectionError` | unreachable node, timeout, non-JSON HTTP response |
|
|
97
|
+
| `RpcResponseError` | JSON-RPC `error` object; `.code`, `.message`, `.data` |
|
|
98
|
+
| `AbiError` | unknown type, bad value, undecodable payload |
|
|
99
|
+
|
|
100
|
+
Reverts arrive as `RpcResponseError` with `execution reverted` in the message
|
|
101
|
+
and the revert payload in `.data`. Looking up a function that the ABI does not
|
|
102
|
+
contain raises `AttributeError`, which callers can use to fall back to another
|
|
103
|
+
indexer.
|
|
104
|
+
|
|
105
|
+
## What replaces what
|
|
106
|
+
|
|
107
|
+
| web3.py | micro-web3 |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `Web3(Web3.HTTPProvider(uri, request_kwargs={'timeout': 4}))` | `RpcNode(uri, timeout=4)` |
|
|
110
|
+
| `node.is_connected()` | `node.is_connected()` |
|
|
111
|
+
| `web3.manager.request_blocking(method, params)` | `node.request(method, params)` |
|
|
112
|
+
| `contract.encode_abi(name, args=)` | `contract.encode(name, args)` |
|
|
113
|
+
| `contract._find_matching_fn_abi(name, *args)` | `contract.function(name, args)` |
|
|
114
|
+
| `contract.caller().fn(*args)` | `contract.call(node, name, args)` |
|
|
115
|
+
| `web3.codec.decode(types, data)` | `decode_outputs(types, data)` |
|
|
116
|
+
| `node.eth.get_logs(options)` | `get_logs(node, ...)` |
|
|
117
|
+
| `node.eth.get_block('latest')['number']` | `get_block_number(node)` |
|
|
118
|
+
| `Web3.to_checksum_address` | `to_checksum_address` |
|
|
119
|
+
| `web3.exceptions.Web3Exception` | `RpcError` |
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
```console
|
|
124
|
+
uv sync --all-groups
|
|
125
|
+
uv run pytest
|
|
126
|
+
MICRON_RPC_URL=https://ethereum-rpc.publicnode.com uv run pytest tests/test_live.py
|
|
127
|
+
uv run ruff check .
|
|
128
|
+
uv run ruff format --check .
|
|
129
|
+
uv run pyrefly check
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The ABI tests run against vectors generated with eth_abi 6 (`tests/vectors.json`),
|
|
133
|
+
so the hand-rolled codec is checked against the reference implementation
|
|
134
|
+
without depending on it.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "micro-web3"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Minimal Ethereum JSON-RPC and ABI client"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.13"
|
|
9
|
+
keywords = [
|
|
10
|
+
"ethereum",
|
|
11
|
+
"web3",
|
|
12
|
+
"json-rpc",
|
|
13
|
+
"abi",
|
|
14
|
+
"keccak",
|
|
15
|
+
"multicall",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"pycryptodome>=3.20,<4",
|
|
28
|
+
"urllib3>=2,<3",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[[project.authors]]
|
|
32
|
+
name = "Yabir Benchakhtir"
|
|
33
|
+
email = "git@yabirgb.com"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pyrefly>=1.4.0",
|
|
38
|
+
"pytest>=8.3",
|
|
39
|
+
"ruff>=0.9",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
44
|
+
build-backend = "uv_build"
|
|
45
|
+
|
|
46
|
+
[tool.uv.build-backend]
|
|
47
|
+
module-name = "micro_web3"
|
|
48
|
+
module-root = "src"
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
52
|
+
addopts = "-q"
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 100
|
|
56
|
+
target-version = "py313"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = [
|
|
60
|
+
"E",
|
|
61
|
+
"F",
|
|
62
|
+
"I",
|
|
63
|
+
"UP",
|
|
64
|
+
"B",
|
|
65
|
+
"SIM",
|
|
66
|
+
"RUF",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint.isort]
|
|
70
|
+
known-first-party = ["micro_web3"]
|
|
71
|
+
|
|
72
|
+
[tool.pyrefly]
|
|
73
|
+
python-version = "3.13"
|
|
74
|
+
project-includes = [
|
|
75
|
+
"src/**/*.py",
|
|
76
|
+
"tests/**/*.py",
|
|
77
|
+
]
|
|
78
|
+
search-path = ["src"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "micro-web3"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Minimal Ethereum JSON-RPC and ABI client"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Yabir Benchakhtir", email = "git@yabirgb.com" }
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.13"
|
|
12
|
+
keywords = ["ethereum", "web3", "json-rpc", "abi", "keccak", "multicall"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pycryptodome>=3.20,<4",
|
|
24
|
+
"urllib3>=2,<3",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"pyrefly>=1.4.0",
|
|
30
|
+
"pytest>=8.3",
|
|
31
|
+
"ruff>=0.9",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
36
|
+
build-backend = "uv_build"
|
|
37
|
+
|
|
38
|
+
[tool.uv.build-backend]
|
|
39
|
+
module-name = "micro_web3"
|
|
40
|
+
module-root = "src"
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
addopts = "-q"
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py313"
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint.isort]
|
|
54
|
+
known-first-party = ["micro_web3"]
|
|
55
|
+
|
|
56
|
+
[tool.pyrefly]
|
|
57
|
+
python-version = "3.13"
|
|
58
|
+
project-includes = ["src/**/*.py", "tests/**/*.py"]
|
|
59
|
+
search-path = ["src"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""micro-web3: a small Ethereum JSON-RPC + ABI client.
|
|
2
|
+
|
|
3
|
+
Replaces the web3.py surface used by the rotki web backend: JSON-RPC transport,
|
|
4
|
+
calldata encoding, output decoding, log queries and Multicall3 batching.
|
|
5
|
+
|
|
6
|
+
>>> from micro_web3 import Contract, RpcNode
|
|
7
|
+
>>> node = RpcNode('https://ethereum-rpc.publicnode.com', timeout=4)
|
|
8
|
+
>>> node.is_connected()
|
|
9
|
+
True
|
|
10
|
+
>>> contract = Contract('0xA0b8...eB48', erc20_abi)
|
|
11
|
+
>>> contract.call(node, 'decimals')
|
|
12
|
+
6
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .abi import (
|
|
16
|
+
AbiType,
|
|
17
|
+
canonical,
|
|
18
|
+
decode,
|
|
19
|
+
decode_outputs,
|
|
20
|
+
encode,
|
|
21
|
+
encode_call,
|
|
22
|
+
find_function,
|
|
23
|
+
parse_type,
|
|
24
|
+
)
|
|
25
|
+
from .addresses import is_address, to_checksum_address
|
|
26
|
+
from .contract import Contract
|
|
27
|
+
from .errors import AbiError, RpcConnectionError, RpcError, RpcResponseError
|
|
28
|
+
from .methods import eth_call, get_block_number, get_logs, to_quantity
|
|
29
|
+
from .multicall import MULTICALL3_ADDRESS, try_aggregate
|
|
30
|
+
from .rpc import RpcNode
|
|
31
|
+
|
|
32
|
+
__version__ = "0.1.0"
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"MULTICALL3_ADDRESS",
|
|
36
|
+
"AbiError",
|
|
37
|
+
"AbiType",
|
|
38
|
+
"Contract",
|
|
39
|
+
"RpcConnectionError",
|
|
40
|
+
"RpcError",
|
|
41
|
+
"RpcNode",
|
|
42
|
+
"RpcResponseError",
|
|
43
|
+
"__version__",
|
|
44
|
+
"canonical",
|
|
45
|
+
"decode",
|
|
46
|
+
"decode_outputs",
|
|
47
|
+
"encode",
|
|
48
|
+
"encode_call",
|
|
49
|
+
"eth_call",
|
|
50
|
+
"find_function",
|
|
51
|
+
"get_block_number",
|
|
52
|
+
"get_logs",
|
|
53
|
+
"is_address",
|
|
54
|
+
"parse_type",
|
|
55
|
+
"to_checksum_address",
|
|
56
|
+
"to_quantity",
|
|
57
|
+
"try_aggregate",
|
|
58
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Keccak-256.
|
|
2
|
+
|
|
3
|
+
Not SHA3-256: Ethereum uses the original Keccak padding, which the standard
|
|
4
|
+
library's ``hashlib.sha3_256`` does not implement. pycryptodome provides the C
|
|
5
|
+
implementation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from Crypto.Hash import keccak
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def keccak256(data: bytes) -> bytes:
|
|
12
|
+
digest = keccak.new(digest_bits=256)
|
|
13
|
+
digest.update(data)
|
|
14
|
+
return digest.digest()
|