dexsnake 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.
- dexsnake-0.1.0/LICENSE +21 -0
- dexsnake-0.1.0/PKG-INFO +52 -0
- dexsnake-0.1.0/README.rst +36 -0
- dexsnake-0.1.0/dexsnake/__init__.py +3 -0
- dexsnake-0.1.0/dexsnake/abi/ERC20Token.json +295 -0
- dexsnake-0.1.0/dexsnake/abi/__init__.py +0 -0
- dexsnake-0.1.0/dexsnake/amounts.py +23 -0
- dexsnake-0.1.0/dexsnake/erc20_token.py +184 -0
- dexsnake-0.1.0/dexsnake/transactions.py +31 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/__init__.py +3 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/abi/UniswapV2Factory.json +181 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/abi/UniswapV2Pair.json +707 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/abi/UniswapV2Router02.json +953 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/abi/__init__.py +0 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/config.py +38 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/factory.py +46 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/pair.py +92 -0
- dexsnake-0.1.0/dexsnake/uniswap_v2/router.py +150 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/__init__.py +3 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/abi/UniswapV3Factory.json +236 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/abi/UniswapV3Pool.json +988 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/abi/UniswapV3SwapRouter02.json +1063 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/abi/__init__.py +1 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/config.py +50 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/factory.py +49 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/pool.py +92 -0
- dexsnake-0.1.0/dexsnake/uniswap_v3/router.py +167 -0
- dexsnake-0.1.0/dexsnake.egg-info/PKG-INFO +52 -0
- dexsnake-0.1.0/dexsnake.egg-info/SOURCES.txt +38 -0
- dexsnake-0.1.0/dexsnake.egg-info/dependency_links.txt +1 -0
- dexsnake-0.1.0/dexsnake.egg-info/requires.txt +1 -0
- dexsnake-0.1.0/dexsnake.egg-info/top_level.txt +1 -0
- dexsnake-0.1.0/pyproject.toml +35 -0
- dexsnake-0.1.0/setup.cfg +4 -0
- dexsnake-0.1.0/tests/test_erc20_amounts.py +75 -0
- dexsnake-0.1.0/tests/test_fork.py +274 -0
- dexsnake-0.1.0/tests/test_pool_prices.py +65 -0
- dexsnake-0.1.0/tests/test_router_amounts.py +149 -0
- dexsnake-0.1.0/tests/test_transaction_broadcast.py +157 -0
- dexsnake-0.1.0/tests/test_v3_deadline.py +67 -0
dexsnake-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Leevi Kerkelä
|
|
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.
|
dexsnake-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dexsnake
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python package for trading on decentralized exchanges like Uniswap.
|
|
5
|
+
Author-email: Leevi Kerkelä <leevi.kerkela@protonmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kerkelae/dexsnake
|
|
8
|
+
Project-URL: Documentation, https://dexsnake.readthedocs.io/
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/x-rst
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: web3<8,>=7
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
Dexsnake
|
|
18
|
+
========
|
|
19
|
+
|
|
20
|
+
Dexsnake is a lightweight Python package that makes it easy to make trades on
|
|
21
|
+
decentralized exchanges (DEXs) like `Uniswap <https://uniswap.org/>`_. The package is
|
|
22
|
+
designed for Python developers, quants, data scientists, algorithmic traders, and others
|
|
23
|
+
who are used to working in Python but may not have in-depth knowledge about blockchains
|
|
24
|
+
and decentralized apps. The code is organized to closely follow the structure of the
|
|
25
|
+
smart contracts, ensuring that by using Dexsnake, users also gain an understanding of
|
|
26
|
+
the underlying protocols.
|
|
27
|
+
|
|
28
|
+
Installation
|
|
29
|
+
############
|
|
30
|
+
|
|
31
|
+
.. note::
|
|
32
|
+
|
|
33
|
+
Dexsnake is under active development. Its API may change before version 1.0.
|
|
34
|
+
Review transaction parameters carefully before trading with real funds.
|
|
35
|
+
|
|
36
|
+
The most recent release can be installed with `pip <https://pip.pypa.io/>`_:
|
|
37
|
+
|
|
38
|
+
.. code-block::
|
|
39
|
+
|
|
40
|
+
pip install dexsnake
|
|
41
|
+
|
|
42
|
+
The development version can be installed from GitHub:
|
|
43
|
+
|
|
44
|
+
.. code-block::
|
|
45
|
+
|
|
46
|
+
pip install git+https://github.com/kerkelae/dexsnake.git
|
|
47
|
+
|
|
48
|
+
Contributing
|
|
49
|
+
############
|
|
50
|
+
|
|
51
|
+
See the `Contributing guide <https://dexsnake.readthedocs.io/en/latest/contributing.html>`_
|
|
52
|
+
for development setup and tests.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Dexsnake
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
Dexsnake is a lightweight Python package that makes it easy to make trades on
|
|
5
|
+
decentralized exchanges (DEXs) like `Uniswap <https://uniswap.org/>`_. The package is
|
|
6
|
+
designed for Python developers, quants, data scientists, algorithmic traders, and others
|
|
7
|
+
who are used to working in Python but may not have in-depth knowledge about blockchains
|
|
8
|
+
and decentralized apps. The code is organized to closely follow the structure of the
|
|
9
|
+
smart contracts, ensuring that by using Dexsnake, users also gain an understanding of
|
|
10
|
+
the underlying protocols.
|
|
11
|
+
|
|
12
|
+
Installation
|
|
13
|
+
############
|
|
14
|
+
|
|
15
|
+
.. note::
|
|
16
|
+
|
|
17
|
+
Dexsnake is under active development. Its API may change before version 1.0.
|
|
18
|
+
Review transaction parameters carefully before trading with real funds.
|
|
19
|
+
|
|
20
|
+
The most recent release can be installed with `pip <https://pip.pypa.io/>`_:
|
|
21
|
+
|
|
22
|
+
.. code-block::
|
|
23
|
+
|
|
24
|
+
pip install dexsnake
|
|
25
|
+
|
|
26
|
+
The development version can be installed from GitHub:
|
|
27
|
+
|
|
28
|
+
.. code-block::
|
|
29
|
+
|
|
30
|
+
pip install git+https://github.com/kerkelae/dexsnake.git
|
|
31
|
+
|
|
32
|
+
Contributing
|
|
33
|
+
############
|
|
34
|
+
|
|
35
|
+
See the `Contributing guide <https://dexsnake.readthedocs.io/en/latest/contributing.html>`_
|
|
36
|
+
for development setup and tests.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"constant": true,
|
|
4
|
+
"inputs": [],
|
|
5
|
+
"name": "name",
|
|
6
|
+
"outputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "",
|
|
9
|
+
"type": "string"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"payable": false,
|
|
13
|
+
"stateMutability": "view",
|
|
14
|
+
"type": "function"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"constant": false,
|
|
18
|
+
"inputs": [
|
|
19
|
+
{
|
|
20
|
+
"name": "_spender",
|
|
21
|
+
"type": "address"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "_value",
|
|
25
|
+
"type": "uint256"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"name": "approve",
|
|
29
|
+
"outputs": [
|
|
30
|
+
{
|
|
31
|
+
"name": "success",
|
|
32
|
+
"type": "bool"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"payable": false,
|
|
36
|
+
"stateMutability": "nonpayable",
|
|
37
|
+
"type": "function"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"constant": true,
|
|
41
|
+
"inputs": [],
|
|
42
|
+
"name": "totalSupply",
|
|
43
|
+
"outputs": [
|
|
44
|
+
{
|
|
45
|
+
"name": "",
|
|
46
|
+
"type": "uint256"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"payable": false,
|
|
50
|
+
"stateMutability": "view",
|
|
51
|
+
"type": "function"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"constant": false,
|
|
55
|
+
"inputs": [
|
|
56
|
+
{
|
|
57
|
+
"name": "_from",
|
|
58
|
+
"type": "address"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "_to",
|
|
62
|
+
"type": "address"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "_value",
|
|
66
|
+
"type": "uint256"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"name": "transferFrom",
|
|
70
|
+
"outputs": [
|
|
71
|
+
{
|
|
72
|
+
"name": "success",
|
|
73
|
+
"type": "bool"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"payable": false,
|
|
77
|
+
"stateMutability": "nonpayable",
|
|
78
|
+
"type": "function"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"constant": true,
|
|
82
|
+
"inputs": [],
|
|
83
|
+
"name": "decimals",
|
|
84
|
+
"outputs": [
|
|
85
|
+
{
|
|
86
|
+
"name": "",
|
|
87
|
+
"type": "uint8"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"payable": false,
|
|
91
|
+
"stateMutability": "view",
|
|
92
|
+
"type": "function"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"constant": false,
|
|
96
|
+
"inputs": [
|
|
97
|
+
{
|
|
98
|
+
"name": "_value",
|
|
99
|
+
"type": "uint256"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"name": "burn",
|
|
103
|
+
"outputs": [
|
|
104
|
+
{
|
|
105
|
+
"name": "success",
|
|
106
|
+
"type": "bool"
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"payable": false,
|
|
110
|
+
"stateMutability": "nonpayable",
|
|
111
|
+
"type": "function"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"constant": true,
|
|
115
|
+
"inputs": [
|
|
116
|
+
{
|
|
117
|
+
"name": "",
|
|
118
|
+
"type": "address"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"name": "balanceOf",
|
|
122
|
+
"outputs": [
|
|
123
|
+
{
|
|
124
|
+
"name": "",
|
|
125
|
+
"type": "uint256"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"payable": false,
|
|
129
|
+
"stateMutability": "view",
|
|
130
|
+
"type": "function"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"constant": false,
|
|
134
|
+
"inputs": [
|
|
135
|
+
{
|
|
136
|
+
"name": "_from",
|
|
137
|
+
"type": "address"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "_value",
|
|
141
|
+
"type": "uint256"
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"name": "burnFrom",
|
|
145
|
+
"outputs": [
|
|
146
|
+
{
|
|
147
|
+
"name": "success",
|
|
148
|
+
"type": "bool"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"payable": false,
|
|
152
|
+
"stateMutability": "nonpayable",
|
|
153
|
+
"type": "function"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"constant": true,
|
|
157
|
+
"inputs": [],
|
|
158
|
+
"name": "symbol",
|
|
159
|
+
"outputs": [
|
|
160
|
+
{
|
|
161
|
+
"name": "",
|
|
162
|
+
"type": "string"
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"payable": false,
|
|
166
|
+
"stateMutability": "view",
|
|
167
|
+
"type": "function"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"constant": false,
|
|
171
|
+
"inputs": [
|
|
172
|
+
{
|
|
173
|
+
"name": "_to",
|
|
174
|
+
"type": "address"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "_value",
|
|
178
|
+
"type": "uint256"
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
"name": "transfer",
|
|
182
|
+
"outputs": [],
|
|
183
|
+
"payable": false,
|
|
184
|
+
"stateMutability": "nonpayable",
|
|
185
|
+
"type": "function"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"constant": false,
|
|
189
|
+
"inputs": [
|
|
190
|
+
{
|
|
191
|
+
"name": "_spender",
|
|
192
|
+
"type": "address"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "_value",
|
|
196
|
+
"type": "uint256"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"name": "_extraData",
|
|
200
|
+
"type": "bytes"
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"name": "approveAndCall",
|
|
204
|
+
"outputs": [
|
|
205
|
+
{
|
|
206
|
+
"name": "success",
|
|
207
|
+
"type": "bool"
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"payable": false,
|
|
211
|
+
"stateMutability": "nonpayable",
|
|
212
|
+
"type": "function"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"constant": true,
|
|
216
|
+
"inputs": [
|
|
217
|
+
{
|
|
218
|
+
"name": "",
|
|
219
|
+
"type": "address"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "",
|
|
223
|
+
"type": "address"
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
"name": "allowance",
|
|
227
|
+
"outputs": [
|
|
228
|
+
{
|
|
229
|
+
"name": "",
|
|
230
|
+
"type": "uint256"
|
|
231
|
+
}
|
|
232
|
+
],
|
|
233
|
+
"payable": false,
|
|
234
|
+
"stateMutability": "view",
|
|
235
|
+
"type": "function"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"inputs": [
|
|
239
|
+
{
|
|
240
|
+
"name": "initialSupply",
|
|
241
|
+
"type": "uint256"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"name": "tokenName",
|
|
245
|
+
"type": "string"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"name": "tokenSymbol",
|
|
249
|
+
"type": "string"
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
"payable": false,
|
|
253
|
+
"stateMutability": "nonpayable",
|
|
254
|
+
"type": "constructor"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"anonymous": false,
|
|
258
|
+
"inputs": [
|
|
259
|
+
{
|
|
260
|
+
"indexed": true,
|
|
261
|
+
"name": "from",
|
|
262
|
+
"type": "address"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"indexed": true,
|
|
266
|
+
"name": "to",
|
|
267
|
+
"type": "address"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"indexed": false,
|
|
271
|
+
"name": "value",
|
|
272
|
+
"type": "uint256"
|
|
273
|
+
}
|
|
274
|
+
],
|
|
275
|
+
"name": "Transfer",
|
|
276
|
+
"type": "event"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"anonymous": false,
|
|
280
|
+
"inputs": [
|
|
281
|
+
{
|
|
282
|
+
"indexed": true,
|
|
283
|
+
"name": "from",
|
|
284
|
+
"type": "address"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"indexed": false,
|
|
288
|
+
"name": "value",
|
|
289
|
+
"type": "uint256"
|
|
290
|
+
}
|
|
291
|
+
],
|
|
292
|
+
"name": "Burn",
|
|
293
|
+
"type": "event"
|
|
294
|
+
}
|
|
295
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _from_base_units(units: int, decimals: int) -> Decimal:
|
|
6
|
+
return Decimal(
|
|
7
|
+
f"{units}e-{decimals}"
|
|
8
|
+
) # to avoid rounding large amounts to the active Decimal precision
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _to_base_units(value: Union[Decimal, int], decimals: int) -> int:
|
|
12
|
+
if isinstance(value, bool) or not isinstance(value, (Decimal, int)):
|
|
13
|
+
raise TypeError("Token amount must be a Decimal or integer")
|
|
14
|
+
value = Decimal(value)
|
|
15
|
+
if not value.is_finite() or value < 0:
|
|
16
|
+
raise ValueError("Token amount must be finite and non-negative")
|
|
17
|
+
numerator, denominator = value.as_integer_ratio()
|
|
18
|
+
units, remainder = divmod(numerator * 10**decimals, denominator)
|
|
19
|
+
if remainder:
|
|
20
|
+
raise ValueError(f"Token amount exceeds {decimals} decimal places")
|
|
21
|
+
if units >= 2**256:
|
|
22
|
+
raise ValueError("Token amount exceeds uint256 range")
|
|
23
|
+
return units
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from web3 import Web3
|
|
7
|
+
from web3.contract import Contract
|
|
8
|
+
from web3.types import TxReceipt
|
|
9
|
+
|
|
10
|
+
from .amounts import _from_base_units, _to_base_units
|
|
11
|
+
from .transactions import _send_transaction
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ERC20Token:
|
|
15
|
+
def __init__(self, web3: Web3, address: str):
|
|
16
|
+
"""
|
|
17
|
+
Initializes a new instance of the ``ERC20Token`` class.
|
|
18
|
+
|
|
19
|
+
For details about the ERC20 standard, see https://eips.ethereum.org/EIPS/eip-20.
|
|
20
|
+
|
|
21
|
+
:param web3: A ``Web3`` instance connected to a blockchain node.
|
|
22
|
+
:type web3: ``Web3``
|
|
23
|
+
:param address: The address of the ERC20 token contract.
|
|
24
|
+
:type address: str
|
|
25
|
+
"""
|
|
26
|
+
self.web3: Web3 = web3
|
|
27
|
+
address_checksum = self.web3.to_checksum_address(address)
|
|
28
|
+
self.address: str = address_checksum
|
|
29
|
+
abi_path = os.path.join(os.path.dirname(__file__), "abi", "ERC20Token.json")
|
|
30
|
+
with open(abi_path, "r") as file:
|
|
31
|
+
self.contract: Contract = self.web3.eth.contract(
|
|
32
|
+
address=address_checksum, abi=json.load(file)
|
|
33
|
+
)
|
|
34
|
+
self._name: Optional[str] = None
|
|
35
|
+
self._symbol: Optional[str] = None
|
|
36
|
+
self._decimals: Optional[int] = None
|
|
37
|
+
|
|
38
|
+
def allowance(self, owner: str, spender: str) -> Decimal:
|
|
39
|
+
"""
|
|
40
|
+
Returns the amount which ``spender`` is allowed to withdraw from ``owner``.
|
|
41
|
+
|
|
42
|
+
:param owner: The address of the token owner.
|
|
43
|
+
:type owner: str
|
|
44
|
+
:param spender: The address of the spender.
|
|
45
|
+
:type spender: str
|
|
46
|
+
|
|
47
|
+
:return: The remaining allowance of tokens.
|
|
48
|
+
:rtype: ``Decimal``
|
|
49
|
+
"""
|
|
50
|
+
return _from_base_units(
|
|
51
|
+
self.contract.functions.allowance(
|
|
52
|
+
self.web3.to_checksum_address(owner),
|
|
53
|
+
self.web3.to_checksum_address(spender),
|
|
54
|
+
).call(),
|
|
55
|
+
self.decimals,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def approve(
|
|
59
|
+
self,
|
|
60
|
+
spender: str,
|
|
61
|
+
value: Decimal,
|
|
62
|
+
private_key: str,
|
|
63
|
+
gas: Optional[int] = None,
|
|
64
|
+
gas_price: Optional[int] = None,
|
|
65
|
+
) -> TxReceipt:
|
|
66
|
+
"""
|
|
67
|
+
Approves the specified address to spend the specified amount of tokens on behalf
|
|
68
|
+
of the caller.
|
|
69
|
+
|
|
70
|
+
:param spender: The address to approve.
|
|
71
|
+
:type spender: str
|
|
72
|
+
:param value: The amount of tokens to approve.
|
|
73
|
+
:type value: ``Decimal``
|
|
74
|
+
:param private_key: The private key of the account.
|
|
75
|
+
:type private_key: str
|
|
76
|
+
:param gas: The gas limit for the transaction. If not provided, it will be
|
|
77
|
+
estimated automatically.
|
|
78
|
+
:type gas: int, optional
|
|
79
|
+
:param gas_price: The gas price for the transaction in wei. If not provided, the
|
|
80
|
+
current network gas price will be used.
|
|
81
|
+
:type gas_price: int, optional
|
|
82
|
+
|
|
83
|
+
:return: The transaction receipt.
|
|
84
|
+
:rtype: TxReceipt
|
|
85
|
+
"""
|
|
86
|
+
function = self.contract.functions.approve(
|
|
87
|
+
self.web3.to_checksum_address(spender),
|
|
88
|
+
_to_base_units(value, self.decimals),
|
|
89
|
+
)
|
|
90
|
+
return _send_transaction(self.web3, function, private_key, gas, gas_price)
|
|
91
|
+
|
|
92
|
+
def balance_of(self, account: str) -> Decimal:
|
|
93
|
+
"""
|
|
94
|
+
Returns the balance of the specified account.
|
|
95
|
+
|
|
96
|
+
:param account: The address of the account.
|
|
97
|
+
:type account: str
|
|
98
|
+
|
|
99
|
+
:return: The balance of the account.
|
|
100
|
+
:rtype: ``Decimal``
|
|
101
|
+
"""
|
|
102
|
+
return _from_base_units(
|
|
103
|
+
self.contract.functions.balanceOf(
|
|
104
|
+
self.web3.to_checksum_address(account)
|
|
105
|
+
).call(),
|
|
106
|
+
self.decimals,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def decimals(self) -> int:
|
|
111
|
+
"""
|
|
112
|
+
Returns the number of decimals the token uses.
|
|
113
|
+
|
|
114
|
+
:return: The number of decimals.
|
|
115
|
+
"""
|
|
116
|
+
if self._decimals is None:
|
|
117
|
+
self._decimals = self.contract.functions.decimals().call()
|
|
118
|
+
return self._decimals
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def name(self) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Returns the name of the token.
|
|
124
|
+
|
|
125
|
+
:return: The token name.
|
|
126
|
+
"""
|
|
127
|
+
if self._name is None:
|
|
128
|
+
self._name = self.contract.functions.name().call()
|
|
129
|
+
return self._name
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def symbol(self) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Returns the symbol of the token.
|
|
135
|
+
|
|
136
|
+
:return: The token symbol.
|
|
137
|
+
"""
|
|
138
|
+
if self._symbol is None:
|
|
139
|
+
self._symbol = self.contract.functions.symbol().call()
|
|
140
|
+
return self._symbol
|
|
141
|
+
|
|
142
|
+
def total_supply(self) -> Decimal:
|
|
143
|
+
"""
|
|
144
|
+
Returns the total supply of the token.
|
|
145
|
+
|
|
146
|
+
:return: Total token supply.
|
|
147
|
+
:rtype: ``Decimal``
|
|
148
|
+
"""
|
|
149
|
+
return _from_base_units(
|
|
150
|
+
self.contract.functions.totalSupply().call(), self.decimals
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def transfer(
|
|
154
|
+
self,
|
|
155
|
+
to: str,
|
|
156
|
+
value: Decimal,
|
|
157
|
+
private_key: str,
|
|
158
|
+
gas: Optional[int] = None,
|
|
159
|
+
gas_price: Optional[int] = None,
|
|
160
|
+
) -> TxReceipt:
|
|
161
|
+
"""
|
|
162
|
+
Transfers tokens to the specified address.
|
|
163
|
+
|
|
164
|
+
:param to: The address to transfer tokens to.
|
|
165
|
+
:type to: str
|
|
166
|
+
:param value: The amount of tokens to transfer.
|
|
167
|
+
:type value: ``Decimal``
|
|
168
|
+
:param private_key: The private key of the account.
|
|
169
|
+
:type private_key: str
|
|
170
|
+
:param gas: The gas limit for the transaction. If not provided, it will be
|
|
171
|
+
estimated automatically.
|
|
172
|
+
:type gas: int, optional
|
|
173
|
+
:param gas_price: The gas price for the transaction in wei. If not provided, the
|
|
174
|
+
current network gas price will be used.
|
|
175
|
+
:type gas_price: int, optional
|
|
176
|
+
|
|
177
|
+
:return: The transaction receipt.
|
|
178
|
+
:rtype: TxReceipt
|
|
179
|
+
"""
|
|
180
|
+
function = self.contract.functions.transfer(
|
|
181
|
+
self.web3.to_checksum_address(to),
|
|
182
|
+
_to_base_units(value, self.decimals),
|
|
183
|
+
)
|
|
184
|
+
return _send_transaction(self.web3, function, private_key, gas, gas_price)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from web3 import Web3
|
|
4
|
+
from web3.contract.contract import ContractFunction
|
|
5
|
+
from web3.types import TxReceipt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _send_transaction(
|
|
9
|
+
web3: Web3,
|
|
10
|
+
function: ContractFunction,
|
|
11
|
+
private_key: str,
|
|
12
|
+
gas: Optional[int],
|
|
13
|
+
gas_price: Optional[int],
|
|
14
|
+
) -> TxReceipt:
|
|
15
|
+
account = web3.eth.account.from_key(private_key)
|
|
16
|
+
if gas_price is None:
|
|
17
|
+
gas_price = web3.eth.gas_price
|
|
18
|
+
tx_params = {
|
|
19
|
+
"from": account.address,
|
|
20
|
+
"nonce": web3.eth.get_transaction_count(account.address, "pending"),
|
|
21
|
+
"gasPrice": gas_price,
|
|
22
|
+
}
|
|
23
|
+
if gas is not None:
|
|
24
|
+
tx_params["gas"] = gas
|
|
25
|
+
tx = function.build_transaction(tx_params)
|
|
26
|
+
signed_tx = account.sign_transaction(tx)
|
|
27
|
+
tx_hash = web3.eth.send_raw_transaction(signed_tx.raw_transaction)
|
|
28
|
+
receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
|
|
29
|
+
if receipt["status"] == 0:
|
|
30
|
+
raise RuntimeError(f"Transaction {Web3.to_hex(tx_hash)} failed")
|
|
31
|
+
return receipt
|