opinion-clob-sdk 0.1.10__py3-none-any.whl → 0.1.11__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.
Potentially problematic release.
This version of opinion-clob-sdk might be problematic. Click here for more details.
- opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +1 -1
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +8 -22
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/__init__.py +26 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contract_caller.py +390 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/conditional_tokens.py +707 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/contracts/erc20.py +111 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/exception.py +11 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/base_builder.py +41 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/exception.py +2 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder.py +90 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/builders/order_builder_test.py +40 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/constants.py +2 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order.py +254 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/order_type.py +9 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/sides.py +8 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/model/signatures.py +8 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/signer.py +20 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/py_order_utils/utils.py +125 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/constants.py +19 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/eip712/__init__.py +176 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/enums.py +6 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/exceptions.py +94 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/multisend.py +347 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe.py +141 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/__init__.py +0 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/compatibility_fallback_handler_v1_3_0.py +327 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/multisend_v1_3_0.py +22 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/safe_v1_3_0.py +1035 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_contracts/utils.py +26 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_signature.py +364 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_test.py +37 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/safe_tx.py +437 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/signatures.py +63 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/typing.py +17 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/chain/safe/utils.py +218 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/config.py +4 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/model.py +19 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/sdk.py +957 -0
- opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/opinion_clob_sdk/verify_api_calls.py +135 -0
- {opinion_clob_sdk-0.1.10.dist-info → opinion_clob_sdk-0.1.11.dist-info}/METADATA +1 -1
- {opinion_clob_sdk-0.1.10.dist-info → opinion_clob_sdk-0.1.11.dist-info}/RECORD +67 -24
- {opinion_clob_sdk-0.1.10.dist-info → opinion_clob_sdk-0.1.11.dist-info}/WHEEL +0 -0
- {opinion_clob_sdk-0.1.10.dist-info → opinion_clob_sdk-0.1.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
import logging
|
|
4
|
+
from typing import List, Optional, Union, Literal
|
|
5
|
+
|
|
6
|
+
from eth_typing import ChecksumAddress, HexAddress, HexStr, BlockNumber, Hash32
|
|
7
|
+
from hexbytes import HexBytes
|
|
8
|
+
from web3 import Web3
|
|
9
|
+
from web3.types import TxParams, StateOverrideParams
|
|
10
|
+
from .enums import SafeOperationEnum
|
|
11
|
+
|
|
12
|
+
from .safe_contracts.utils import (
|
|
13
|
+
get_multi_send_contract,
|
|
14
|
+
)
|
|
15
|
+
from .typing import EthereumData
|
|
16
|
+
from .utils import (
|
|
17
|
+
fast_bytes_to_checksum_address,
|
|
18
|
+
fast_is_checksum_address,
|
|
19
|
+
get_empty_tx_params,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MultiSendOperation(Enum):
|
|
26
|
+
CALL = 0
|
|
27
|
+
DELEGATE_CALL = 1
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MultiSendTx:
|
|
31
|
+
"""
|
|
32
|
+
Wrapper for a single MultiSendTx
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
operation: MultiSendOperation,
|
|
38
|
+
to: ChecksumAddress,
|
|
39
|
+
value: int,
|
|
40
|
+
data: EthereumData,
|
|
41
|
+
old_encoding: bool = False,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
:param operation: MultisendOperation, CALL or DELEGATE_CALL
|
|
45
|
+
:param to: Address
|
|
46
|
+
:param value: Value in Wei
|
|
47
|
+
:param data: data as hex string or bytes
|
|
48
|
+
:param old_encoding: True if using old multisend ABI Encoded data, False otherwise
|
|
49
|
+
"""
|
|
50
|
+
self.operation = operation
|
|
51
|
+
self.to = to
|
|
52
|
+
self.value = value
|
|
53
|
+
self.data = HexBytes(data) if data else b""
|
|
54
|
+
self.old_encoding = old_encoding
|
|
55
|
+
|
|
56
|
+
def __eq__(self, other):
|
|
57
|
+
if not isinstance(other, MultiSendTx):
|
|
58
|
+
return NotImplemented
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
self.operation == other.operation
|
|
62
|
+
and self.to == other.to
|
|
63
|
+
and self.value == other.value
|
|
64
|
+
and self.data == other.data
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def __len__(self):
|
|
68
|
+
"""
|
|
69
|
+
:return: Size on bytes of the tx
|
|
70
|
+
"""
|
|
71
|
+
return 21 + 32 * 2 + self.data_length
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
data = self.data[:4].hex() + ("..." if len(self.data) > 4 else "")
|
|
75
|
+
return (
|
|
76
|
+
f"MultisendTx operation={self.operation.name} to={self.to} value={self.value} "
|
|
77
|
+
f"data={data}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def data_length(self) -> int:
|
|
82
|
+
return len(self.data)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def encoded_data(self):
|
|
86
|
+
operation = HexBytes("{:0>2x}".format(self.operation)) # Operation 1 byte
|
|
87
|
+
to = HexBytes("{:0>40x}".format(int(self.to, 16))) # Address 20 bytes
|
|
88
|
+
value = HexBytes("{:0>64x}".format(self.value)) # Value 32 bytes
|
|
89
|
+
data_length = HexBytes(
|
|
90
|
+
"{:0>64x}".format(self.data_length)
|
|
91
|
+
) # Data length 32 bytes
|
|
92
|
+
return operation + to + value + data_length + self.data
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_bytes(cls, encoded_multisend_tx: Union[str, bytes]) -> "MultiSendTx":
|
|
96
|
+
"""
|
|
97
|
+
Decoded one MultiSend transaction. ABI must be used to get the `transactions` parameter and use that data
|
|
98
|
+
for this function
|
|
99
|
+
:param encoded_multisend_tx:
|
|
100
|
+
:return:
|
|
101
|
+
"""
|
|
102
|
+
encoded_multisend_tx = HexBytes(encoded_multisend_tx)
|
|
103
|
+
try:
|
|
104
|
+
return cls._decode_multisend_data(encoded_multisend_tx)
|
|
105
|
+
except ValueError:
|
|
106
|
+
# Try using the old decoding method
|
|
107
|
+
return cls._decode_multisend_old_transaction(encoded_multisend_tx)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def _decode_multisend_data(cls, encoded_multisend_tx: Union[str, bytes]):
|
|
111
|
+
"""
|
|
112
|
+
Decodes one Multisend transaction. If there's more data after `data` it's ignored. Fallbacks to the old
|
|
113
|
+
multisend structure if this structure cannot be decoded.
|
|
114
|
+
https://etherscan.io/address/0x8D29bE29923b68abfDD21e541b9374737B49cdAD#code
|
|
115
|
+
Structure:
|
|
116
|
+
- operation -> MultiSendOperation 1 byte
|
|
117
|
+
- to -> ethereum address 20 bytes
|
|
118
|
+
- value -> tx value 32 bytes
|
|
119
|
+
- data_length -> 32 bytes
|
|
120
|
+
- data -> `data_length` bytes
|
|
121
|
+
:param encoded_multisend_tx: 1 multisend transaction encoded
|
|
122
|
+
:return: Tx as a MultisendTx
|
|
123
|
+
"""
|
|
124
|
+
encoded_multisend_tx = HexBytes(encoded_multisend_tx)
|
|
125
|
+
operation = MultiSendOperation(encoded_multisend_tx[0])
|
|
126
|
+
to = fast_bytes_to_checksum_address(encoded_multisend_tx[1 : 1 + 20])
|
|
127
|
+
value = int.from_bytes(encoded_multisend_tx[21 : 21 + 32], byteorder="big")
|
|
128
|
+
data_length = int.from_bytes(
|
|
129
|
+
encoded_multisend_tx[21 + 32 : 21 + 32 * 2], byteorder="big"
|
|
130
|
+
)
|
|
131
|
+
data = encoded_multisend_tx[21 + 32 * 2 : 21 + 32 * 2 + data_length]
|
|
132
|
+
len_data = len(data)
|
|
133
|
+
if len_data != data_length:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f"Data length {data_length} is different from len(data) {len_data}"
|
|
136
|
+
)
|
|
137
|
+
return cls(operation, to, value, data, old_encoding=False)
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def _decode_multisend_old_transaction(
|
|
141
|
+
cls, encoded_multisend_tx: Union[str, bytes]
|
|
142
|
+
) -> "MultiSendTx":
|
|
143
|
+
"""
|
|
144
|
+
Decodes one old multisend transaction. If there's more data after `data` it's ignored. The difference with
|
|
145
|
+
the new MultiSend is that every value but `data` is padded to 32 bytes, wasting a lot of bytes.
|
|
146
|
+
https://etherscan.io/address/0xE74d6AF1670FB6560dd61EE29eB57C7Bc027Ce4E#code
|
|
147
|
+
Structure:
|
|
148
|
+
- operation -> MultiSendOperation 32 byte
|
|
149
|
+
- to -> ethereum address 32 bytes
|
|
150
|
+
- value -> tx value 32 bytes
|
|
151
|
+
- data_length -> 32 bytes
|
|
152
|
+
- data -> `data_length` bytes
|
|
153
|
+
:param encoded_multisend_tx: 1 multisend transaction encoded
|
|
154
|
+
:return: Tx as a MultisendTx
|
|
155
|
+
"""
|
|
156
|
+
encoded_multisend_tx = HexBytes(encoded_multisend_tx)
|
|
157
|
+
operation = MultiSendOperation(
|
|
158
|
+
int.from_bytes(encoded_multisend_tx[:32], byteorder="big")
|
|
159
|
+
)
|
|
160
|
+
to = fast_bytes_to_checksum_address(encoded_multisend_tx[32:64][-20:])
|
|
161
|
+
value = int.from_bytes(encoded_multisend_tx[64:96], byteorder="big")
|
|
162
|
+
data_length = int.from_bytes(encoded_multisend_tx[128:160], byteorder="big")
|
|
163
|
+
data = encoded_multisend_tx[160 : 160 + data_length]
|
|
164
|
+
len_data = len(data)
|
|
165
|
+
if len_data != data_length:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"Data length {data_length} is different from len(data) {len_data}"
|
|
168
|
+
)
|
|
169
|
+
return cls(operation, to, value, data, old_encoding=True)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class MultiSend:
|
|
173
|
+
dummy_w3 = Web3()
|
|
174
|
+
MULTISEND_ADDRESSES = (
|
|
175
|
+
"0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761", # MultiSend v1.3.0
|
|
176
|
+
"0x998739BFdAAdde7C933B942a68053933098f9EDa", # MultiSend v1.3.0 (EIP-155)
|
|
177
|
+
)
|
|
178
|
+
MULTISEND_CALL_ONLY_ADDRESSES = (
|
|
179
|
+
"0x40A2aCCbd92BCA938b02010E17A5b8929b49130D", # MultiSend Call Only v1.3.0
|
|
180
|
+
"0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B", # MultiSend Call Only v1.3.0 (EIP-155)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def __init__(
|
|
184
|
+
self,
|
|
185
|
+
w3: Optional[Web3] = None,
|
|
186
|
+
address: Optional[ChecksumAddress] = None,
|
|
187
|
+
call_only: bool = True,
|
|
188
|
+
):
|
|
189
|
+
"""
|
|
190
|
+
:param w3: Required for detecting the address in the network.
|
|
191
|
+
:param address: If not provided, will try to detect it from the hardcoded addresses using `ethereum_client`.
|
|
192
|
+
:param call_only: If `True` use `call only` MultiSend, otherwise use regular one.
|
|
193
|
+
Only if `address` not provided
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
self.address = address
|
|
197
|
+
self.w3 = w3 if w3 is not None else self.dummy_w3
|
|
198
|
+
self.call_only = call_only
|
|
199
|
+
multi_send_addresses = (
|
|
200
|
+
self.MULTISEND_CALL_ONLY_ADDRESSES
|
|
201
|
+
if call_only
|
|
202
|
+
else self.MULTISEND_ADDRESSES
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if address:
|
|
206
|
+
assert fast_is_checksum_address(address), (
|
|
207
|
+
"%s proxy factory address not valid" % address
|
|
208
|
+
)
|
|
209
|
+
elif w3:
|
|
210
|
+
# Try to detect MultiSend address if not provided
|
|
211
|
+
for multi_send_address in multi_send_addresses:
|
|
212
|
+
multi_send_address_checksum = ChecksumAddress(
|
|
213
|
+
HexAddress(HexStr(multi_send_address))
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Check if the address is contract
|
|
217
|
+
if len(w3.eth.get_code(multi_send_address_checksum)) != 0:
|
|
218
|
+
self.address = multi_send_address_checksum
|
|
219
|
+
break
|
|
220
|
+
else:
|
|
221
|
+
self.address = ChecksumAddress(HexAddress(HexStr(multi_send_addresses[0])))
|
|
222
|
+
|
|
223
|
+
if not self.address:
|
|
224
|
+
chain_id = (
|
|
225
|
+
self.w3.eth.chain_id if self.w3 != self.dummy_w3 else "N/A"
|
|
226
|
+
)
|
|
227
|
+
raise ValueError(f"Cannot find a MultiSend contract for chainId={chain_id}")
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def from_bytes(cls, encoded_multisend_txs: Union[str, bytes]) -> List[MultiSendTx]:
|
|
231
|
+
"""
|
|
232
|
+
Decodes one or more multisend transactions from `bytes transactions` (Abi decoded)
|
|
233
|
+
|
|
234
|
+
:param encoded_multisend_txs:
|
|
235
|
+
:return: List of MultiSendTxs
|
|
236
|
+
"""
|
|
237
|
+
if not encoded_multisend_txs:
|
|
238
|
+
return []
|
|
239
|
+
|
|
240
|
+
encoded_multisend_txs = HexBytes(encoded_multisend_txs)
|
|
241
|
+
multisend_tx = MultiSendTx.from_bytes(encoded_multisend_txs)
|
|
242
|
+
multisend_tx_size = len(multisend_tx)
|
|
243
|
+
|
|
244
|
+
assert (
|
|
245
|
+
multisend_tx_size > 0
|
|
246
|
+
), "Multisend tx cannot be empty" # This should never happen, just in case
|
|
247
|
+
if multisend_tx.old_encoding:
|
|
248
|
+
next_data_position = (
|
|
249
|
+
(multisend_tx.data_length + 0x1F) // 0x20 * 0x20
|
|
250
|
+
) + 0xA0
|
|
251
|
+
else:
|
|
252
|
+
next_data_position = multisend_tx_size
|
|
253
|
+
remaining_data = encoded_multisend_txs[next_data_position:]
|
|
254
|
+
|
|
255
|
+
return [multisend_tx] + cls.from_bytes(remaining_data)
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def from_transaction_data(
|
|
259
|
+
cls, multisend_data: Union[str, bytes]
|
|
260
|
+
) -> List[MultiSendTx]:
|
|
261
|
+
"""
|
|
262
|
+
Decodes multisend transactions from transaction data (ABI encoded with selector)
|
|
263
|
+
|
|
264
|
+
:return:
|
|
265
|
+
"""
|
|
266
|
+
try:
|
|
267
|
+
_, data = get_multi_send_contract(cls.dummy_w3).decode_function_input(
|
|
268
|
+
multisend_data
|
|
269
|
+
)
|
|
270
|
+
return cls.from_bytes(data["transactions"])
|
|
271
|
+
except ValueError:
|
|
272
|
+
return []
|
|
273
|
+
|
|
274
|
+
def get_contract(self):
|
|
275
|
+
return get_multi_send_contract(self.w3, self.address)
|
|
276
|
+
|
|
277
|
+
def build_tx(
|
|
278
|
+
self, multi_send_txs: List[MultiSendTx], tx_params: Optional[TxParams] = None
|
|
279
|
+
) -> TxParams:
|
|
280
|
+
"""
|
|
281
|
+
Txs don't need to be valid to get through
|
|
282
|
+
|
|
283
|
+
:param multi_send_txs:
|
|
284
|
+
:param tx_params:
|
|
285
|
+
:return:
|
|
286
|
+
"""
|
|
287
|
+
multisend_contract = self.get_contract()
|
|
288
|
+
encoded_multisend_data = b"".join([x.encoded_data for x in multi_send_txs])
|
|
289
|
+
return multisend_contract.functions.multiSend(
|
|
290
|
+
encoded_multisend_data
|
|
291
|
+
).build_transaction(tx_params or {})
|
|
292
|
+
|
|
293
|
+
def build_tx_data(self, multi_send_txs: List[MultiSendTx]) -> HexBytes:
|
|
294
|
+
"""
|
|
295
|
+
Txs don't need to be valid to get through
|
|
296
|
+
|
|
297
|
+
:param multi_send_txs:
|
|
298
|
+
:return:
|
|
299
|
+
"""
|
|
300
|
+
return HexBytes(
|
|
301
|
+
self.build_tx(multi_send_txs, tx_params=get_empty_tx_params())["data"]
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def estimate_gas(
|
|
305
|
+
self,
|
|
306
|
+
multi_send_txs: List[MultiSendTx],
|
|
307
|
+
safe_address: ChecksumAddress,
|
|
308
|
+
safe_operation=SafeOperationEnum.DELEGATE_CALL.value,
|
|
309
|
+
block_identifier: Union[Literal["latest", "earliest", "pending", "safe", "finalized"], BlockNumber, Hash32, HexStr, HexBytes, int, None] = None,
|
|
310
|
+
state_override: Optional[dict[ChecksumAddress, StateOverrideParams]] = None
|
|
311
|
+
) -> int:
|
|
312
|
+
"""
|
|
313
|
+
Only support safe_operation == DELEGATE_CALL and multisend_operation == CALL for now.
|
|
314
|
+
:param multi_send_txs:
|
|
315
|
+
:param safe_address:
|
|
316
|
+
:param safe_operation:
|
|
317
|
+
:param block_identifier:
|
|
318
|
+
:param state_override:
|
|
319
|
+
:return:
|
|
320
|
+
"""
|
|
321
|
+
if self.w3 == self.dummy_w3:
|
|
322
|
+
raise Exception("invalid web3 instance")
|
|
323
|
+
if safe_operation is not SafeOperationEnum.DELEGATE_CALL.value:
|
|
324
|
+
raise NotImplemented
|
|
325
|
+
|
|
326
|
+
from_address = safe_address
|
|
327
|
+
|
|
328
|
+
safe_gas = 0
|
|
329
|
+
for tx in multi_send_txs:
|
|
330
|
+
if tx.operation == MultiSendOperation.DELEGATE_CALL:
|
|
331
|
+
raise NotImplemented
|
|
332
|
+
|
|
333
|
+
tx_params = {
|
|
334
|
+
"from": from_address,
|
|
335
|
+
"to": tx.to,
|
|
336
|
+
"value": tx.value,
|
|
337
|
+
"data": '0x'+tx.data.hex(),
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
logging.debug(f'multisend.estimate_gas, tx_params: {tx_params}')
|
|
341
|
+
gas = self.w3.eth.estimate_gas(tx_params, block_identifier=block_identifier, state_override=state_override)
|
|
342
|
+
safe_gas += gas
|
|
343
|
+
|
|
344
|
+
# To protect safe transaction execution from network gas volatility
|
|
345
|
+
safe_gas *= 1.5
|
|
346
|
+
|
|
347
|
+
return safe_gas
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from eth_account import Account
|
|
2
|
+
from web3 import Web3
|
|
3
|
+
from eth_typing import ChecksumAddress, HexStr
|
|
4
|
+
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from web3.middleware import SignAndSendRawMiddlewareBuilder
|
|
8
|
+
|
|
9
|
+
from .enums import SafeOperationEnum
|
|
10
|
+
from .constants import NULL_ADDRESS
|
|
11
|
+
from .safe_tx import SafeTx
|
|
12
|
+
from functools import cached_property
|
|
13
|
+
from web3.types import BlockIdentifier, TxParams, Wei
|
|
14
|
+
|
|
15
|
+
from .multisend import MultiSendTx, MultiSend
|
|
16
|
+
from .safe_contracts.utils import get_safe_contract
|
|
17
|
+
|
|
18
|
+
VERSION = 'v1.3.0'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Safe:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
w3: Web3,
|
|
25
|
+
private_key: HexStr,
|
|
26
|
+
address: ChecksumAddress,
|
|
27
|
+
multisend_address: ChecksumAddress
|
|
28
|
+
):
|
|
29
|
+
account = Account.from_key(private_key)
|
|
30
|
+
w3.middleware_onion.inject(SignAndSendRawMiddlewareBuilder.build(account), layer=0)
|
|
31
|
+
self.w3 = w3
|
|
32
|
+
|
|
33
|
+
self.private_key = private_key
|
|
34
|
+
self.address = address
|
|
35
|
+
self.multisend_address = multisend_address
|
|
36
|
+
self.multisend = MultiSend(self.w3, self.multisend_address, False)
|
|
37
|
+
self.contract = get_safe_contract(self.w3, self.address)
|
|
38
|
+
|
|
39
|
+
@cached_property
|
|
40
|
+
def chain_id(self) -> int:
|
|
41
|
+
return self.w3.eth.chain_id
|
|
42
|
+
|
|
43
|
+
def get_version(self) -> str:
|
|
44
|
+
"""
|
|
45
|
+
:return: String with Safe Master Copy semantic version, must match `retrieve_version()`
|
|
46
|
+
"""
|
|
47
|
+
return VERSION
|
|
48
|
+
|
|
49
|
+
def retrieve_nonce(
|
|
50
|
+
self, block_identifier: Optional[BlockIdentifier] = "latest"
|
|
51
|
+
) -> int:
|
|
52
|
+
return self.contract.functions.nonce().call(
|
|
53
|
+
block_identifier=block_identifier or "latest"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def build_multisend_tx(self,
|
|
57
|
+
multi_send_txs: List[MultiSendTx],
|
|
58
|
+
safe_nonce: Optional[int] = None,
|
|
59
|
+
) -> SafeTx:
|
|
60
|
+
safe_gas = self.multisend.estimate_gas(multi_send_txs, self.address)
|
|
61
|
+
data = self.multisend.build_tx_data(multi_send_txs)
|
|
62
|
+
safe_tx = self.build_multisig_tx(
|
|
63
|
+
to=self.multisend_address,
|
|
64
|
+
value=0,
|
|
65
|
+
data=data,
|
|
66
|
+
operation=SafeOperationEnum.DELEGATE_CALL.value,
|
|
67
|
+
safe_tx_gas=safe_gas,
|
|
68
|
+
safe_nonce=safe_nonce,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return safe_tx
|
|
72
|
+
|
|
73
|
+
def execute_multisend(self, multi_send_txs: List[MultiSendTx], safe_nonce: Optional[int] = None):
|
|
74
|
+
safe_tx = self.build_multisend_tx(multi_send_txs, safe_nonce)
|
|
75
|
+
safe_tx.sign(self.private_key)
|
|
76
|
+
safe_tx_gas = safe_tx.recommended_gas()
|
|
77
|
+
tx_params = {
|
|
78
|
+
"from": Account.from_key(self.private_key).address,
|
|
79
|
+
"gas": round(safe_tx_gas*1.2),
|
|
80
|
+
}
|
|
81
|
+
logging.debug(f'execute_multisend call with params: {tx_params}')
|
|
82
|
+
|
|
83
|
+
return_value = safe_tx.w3_tx.call(tx_params)
|
|
84
|
+
# return_value = None
|
|
85
|
+
logging.debug(f'execute_multisend return value: {return_value} for safe_nonce {safe_tx.safe_nonce}, w3_tx: {safe_tx.w3_tx}')
|
|
86
|
+
tx_hash = safe_tx.w3_tx.transact(tx_params)
|
|
87
|
+
|
|
88
|
+
return tx_hash, safe_tx.safe_tx_hash, return_value
|
|
89
|
+
|
|
90
|
+
def build_multisig_tx(
|
|
91
|
+
self,
|
|
92
|
+
to: ChecksumAddress,
|
|
93
|
+
value: int,
|
|
94
|
+
data: bytes,
|
|
95
|
+
operation: int = SafeOperationEnum.CALL.value,
|
|
96
|
+
safe_tx_gas: int = 0,
|
|
97
|
+
base_gas: int = 0,
|
|
98
|
+
gas_price: int = 0,
|
|
99
|
+
gas_token: ChecksumAddress = NULL_ADDRESS,
|
|
100
|
+
refund_receiver: ChecksumAddress = NULL_ADDRESS,
|
|
101
|
+
signatures: bytes = b"",
|
|
102
|
+
safe_nonce: Optional[int] = None,
|
|
103
|
+
) -> SafeTx:
|
|
104
|
+
"""
|
|
105
|
+
Allows to execute a Safe transaction confirmed by required number of owners and then pays the account
|
|
106
|
+
that submitted the transaction. The fees are always transfered, even if the user transaction fails
|
|
107
|
+
|
|
108
|
+
:param to: Destination address of Safe transaction
|
|
109
|
+
:param value: Ether value of Safe transaction
|
|
110
|
+
:param data: Data payload of Safe transaction
|
|
111
|
+
:param operation: Operation type of Safe transaction
|
|
112
|
+
:param safe_tx_gas: Gas that should be used for the Safe transaction
|
|
113
|
+
:param base_gas: Gas costs for that are independent of the transaction execution
|
|
114
|
+
(e.g. base transaction fee, signature check, payment of the refund)
|
|
115
|
+
:param gas_price: Gas price that should be used for the payment calculation
|
|
116
|
+
:param gas_token: Token address (or `0x000..000` if ETH) that is used for the payment
|
|
117
|
+
:param refund_receiver: Address of receiver of gas payment (or `0x000..000` if tx.origin).
|
|
118
|
+
:param signatures: Packed signature data ({bytes32 r}{bytes32 s}{uint8 v})
|
|
119
|
+
:param safe_nonce: Nonce of the safe (to calculate hash)
|
|
120
|
+
:return: SafeTx
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
if safe_nonce is None:
|
|
124
|
+
safe_nonce = self.retrieve_nonce()
|
|
125
|
+
return SafeTx(
|
|
126
|
+
self.w3,
|
|
127
|
+
self.address,
|
|
128
|
+
to,
|
|
129
|
+
value,
|
|
130
|
+
data,
|
|
131
|
+
operation,
|
|
132
|
+
safe_tx_gas,
|
|
133
|
+
base_gas,
|
|
134
|
+
gas_price,
|
|
135
|
+
gas_token,
|
|
136
|
+
refund_receiver,
|
|
137
|
+
signatures=signatures,
|
|
138
|
+
safe_nonce=safe_nonce,
|
|
139
|
+
safe_version=self.get_version(),
|
|
140
|
+
chain_id=self.chain_id,
|
|
141
|
+
)
|