dexsnake 0.1.0__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from . import uniswap_v2 as uniswap_v2
2
+ from . import uniswap_v3 as uniswap_v3
3
+ from .erc20_token import ERC20Token as ERC20Token
@@ -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
dexsnake/amounts.py ADDED
@@ -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
@@ -0,0 +1,3 @@
1
+ from .factory import UniswapV2Factory as UniswapV2Factory
2
+ from .pair import UniswapV2Pair as UniswapV2Pair
3
+ from .router import UniswapV2Router as UniswapV2Router