olas-operate-middleware 0.1.0rc59__py3-none-any.whl → 0.13.2__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.
Files changed (98) hide show
  1. olas_operate_middleware-0.13.2.dist-info/METADATA +75 -0
  2. olas_operate_middleware-0.13.2.dist-info/RECORD +101 -0
  3. {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/WHEEL +1 -1
  4. operate/__init__.py +17 -0
  5. operate/account/user.py +35 -9
  6. operate/bridge/bridge_manager.py +470 -0
  7. operate/bridge/providers/lifi_provider.py +377 -0
  8. operate/bridge/providers/native_bridge_provider.py +677 -0
  9. operate/bridge/providers/provider.py +469 -0
  10. operate/bridge/providers/relay_provider.py +457 -0
  11. operate/cli.py +1565 -417
  12. operate/constants.py +60 -12
  13. operate/data/README.md +19 -0
  14. operate/data/contracts/{service_staking_token → dual_staking_token}/__init__.py +2 -2
  15. operate/data/contracts/dual_staking_token/build/DualStakingToken.json +443 -0
  16. operate/data/contracts/dual_staking_token/contract.py +132 -0
  17. operate/data/contracts/dual_staking_token/contract.yaml +23 -0
  18. operate/{ledger/base.py → data/contracts/foreign_omnibridge/__init__.py} +2 -19
  19. operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +1372 -0
  20. operate/data/contracts/foreign_omnibridge/contract.py +130 -0
  21. operate/data/contracts/foreign_omnibridge/contract.yaml +23 -0
  22. operate/{ledger/solana.py → data/contracts/home_omnibridge/__init__.py} +2 -20
  23. operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +1421 -0
  24. operate/data/contracts/home_omnibridge/contract.py +80 -0
  25. operate/data/contracts/home_omnibridge/contract.yaml +23 -0
  26. operate/data/contracts/l1_standard_bridge/__init__.py +20 -0
  27. operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +831 -0
  28. operate/data/contracts/l1_standard_bridge/contract.py +158 -0
  29. operate/data/contracts/l1_standard_bridge/contract.yaml +23 -0
  30. operate/data/contracts/l2_standard_bridge/__init__.py +20 -0
  31. operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +626 -0
  32. operate/data/contracts/l2_standard_bridge/contract.py +130 -0
  33. operate/data/contracts/l2_standard_bridge/contract.yaml +23 -0
  34. operate/data/contracts/mech_activity/__init__.py +20 -0
  35. operate/data/contracts/mech_activity/build/MechActivity.json +111 -0
  36. operate/data/contracts/mech_activity/contract.py +44 -0
  37. operate/data/contracts/mech_activity/contract.yaml +23 -0
  38. operate/data/contracts/optimism_mintable_erc20/__init__.py +20 -0
  39. operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +491 -0
  40. operate/data/contracts/optimism_mintable_erc20/contract.py +45 -0
  41. operate/data/contracts/optimism_mintable_erc20/contract.yaml +23 -0
  42. operate/data/contracts/recovery_module/__init__.py +20 -0
  43. operate/data/contracts/recovery_module/build/RecoveryModule.json +811 -0
  44. operate/data/contracts/recovery_module/contract.py +61 -0
  45. operate/data/contracts/recovery_module/contract.yaml +23 -0
  46. operate/data/contracts/requester_activity_checker/__init__.py +20 -0
  47. operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +111 -0
  48. operate/data/contracts/requester_activity_checker/contract.py +33 -0
  49. operate/data/contracts/requester_activity_checker/contract.yaml +23 -0
  50. operate/data/contracts/staking_token/__init__.py +20 -0
  51. operate/data/contracts/staking_token/build/StakingToken.json +1336 -0
  52. operate/data/contracts/{service_staking_token → staking_token}/contract.py +27 -13
  53. operate/data/contracts/staking_token/contract.yaml +23 -0
  54. operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -1
  55. operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +20 -0
  56. operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +363 -0
  57. operate/keys.py +118 -33
  58. operate/ledger/__init__.py +159 -56
  59. operate/ledger/profiles.py +321 -18
  60. operate/migration.py +555 -0
  61. operate/{http → operate_http}/__init__.py +3 -2
  62. operate/{http → operate_http}/exceptions.py +6 -4
  63. operate/operate_types.py +544 -0
  64. operate/pearl.py +13 -1
  65. operate/quickstart/analyse_logs.py +118 -0
  66. operate/quickstart/claim_staking_rewards.py +104 -0
  67. operate/quickstart/reset_configs.py +106 -0
  68. operate/quickstart/reset_password.py +70 -0
  69. operate/quickstart/reset_staking.py +145 -0
  70. operate/quickstart/run_service.py +726 -0
  71. operate/quickstart/stop_service.py +72 -0
  72. operate/quickstart/terminate_on_chain_service.py +83 -0
  73. operate/quickstart/utils.py +298 -0
  74. operate/resource.py +62 -3
  75. operate/services/agent_runner.py +202 -0
  76. operate/services/deployment_runner.py +868 -0
  77. operate/services/funding_manager.py +929 -0
  78. operate/services/health_checker.py +280 -0
  79. operate/services/manage.py +2356 -620
  80. operate/services/protocol.py +1246 -340
  81. operate/services/service.py +756 -391
  82. operate/services/utils/mech.py +103 -0
  83. operate/services/utils/tendermint.py +86 -12
  84. operate/settings.py +70 -0
  85. operate/utils/__init__.py +135 -0
  86. operate/utils/gnosis.py +407 -80
  87. operate/utils/single_instance.py +226 -0
  88. operate/utils/ssl.py +133 -0
  89. operate/wallet/master.py +708 -123
  90. operate/wallet/wallet_recovery_manager.py +507 -0
  91. olas_operate_middleware-0.1.0rc59.dist-info/METADATA +0 -304
  92. olas_operate_middleware-0.1.0rc59.dist-info/RECORD +0 -41
  93. operate/data/contracts/service_staking_token/build/ServiceStakingToken.json +0 -1273
  94. operate/data/contracts/service_staking_token/contract.yaml +0 -23
  95. operate/ledger/ethereum.py +0 -48
  96. operate/types.py +0 -260
  97. {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/entry_points.txt +0 -0
  98. {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info/licenses}/LICENSE +0 -0
@@ -17,7 +17,7 @@
17
17
  #
18
18
  # ------------------------------------------------------------------------------
19
19
 
20
- """This module contains the class to connect to the `ServiceStakingTokenMechUsage` contract."""
20
+ """This module contains the class to connect to the `StakingToken` contract."""
21
21
 
22
22
  from enum import Enum
23
23
 
@@ -27,18 +27,10 @@ from aea.contracts.base import Contract
27
27
  from aea.crypto.base import LedgerApi
28
28
 
29
29
 
30
- class StakingState(Enum):
31
- """Staking state enumeration for the staking."""
30
+ class StakingTokenContract(Contract):
31
+ """The Staking Token contract."""
32
32
 
33
- UNSTAKED = 0
34
- STAKED = 1
35
- EVICTED = 2
36
-
37
-
38
- class ServiceStakingTokenContract(Contract):
39
- """The Service Staking contract."""
40
-
41
- contract_id = PublicId.from_str("valory/service_staking_token:0.1.0")
33
+ contract_id = PublicId.from_str("valory/staking_token:0.1.0")
42
34
 
43
35
  @classmethod
44
36
  def get_service_staking_state(
@@ -50,7 +42,7 @@ class ServiceStakingTokenContract(Contract):
50
42
  """Check whether the service is staked."""
51
43
  contract_instance = cls.get_instance(ledger_api, contract_address)
52
44
  res = contract_instance.functions.getStakingState(service_id).call()
53
- return dict(data=StakingState(res))
45
+ return dict(data=res)
54
46
 
55
47
  @classmethod
56
48
  def build_stake_tx(
@@ -121,6 +113,28 @@ class ServiceStakingTokenContract(Contract):
121
113
  ts = contract.functions.getNextRewardCheckpointTimestamp().call()
122
114
  return dict(data=ts)
123
115
 
116
+ @classmethod
117
+ def ts_checkpoint(
118
+ cls,
119
+ ledger_api: LedgerApi,
120
+ contract_address: str,
121
+ ) -> JSONLike:
122
+ """Retrieve the checkpoint's timestamp."""
123
+ contract = cls.get_instance(ledger_api, contract_address)
124
+ ts_checkpoint = contract.functions.tsCheckpoint().call()
125
+ return dict(data=ts_checkpoint)
126
+
127
+ @classmethod
128
+ def liveness_ratio(
129
+ cls,
130
+ ledger_api: LedgerApi,
131
+ contract_address: str,
132
+ ) -> JSONLike:
133
+ """Retrieve the liveness ratio."""
134
+ contract = cls.get_instance(ledger_api, contract_address)
135
+ liveness_ratio = contract.functions.livenessRatio().call()
136
+ return dict(data=liveness_ratio)
137
+
124
138
  @classmethod
125
139
  def get_liveness_period(
126
140
  cls,
@@ -0,0 +1,23 @@
1
+ name: staking_token
2
+ author: valory
3
+ version: 0.1.0
4
+ type: contract
5
+ description: Service staking token contract
6
+ license: Apache-2.0
7
+ aea_version: '>=1.0.0, <2.0.0'
8
+ fingerprint:
9
+ __init__.py: bafybeicmgkagyhgwn2ktcdjbprijalbdyj26cvza4d3b7uvmehvy4mmr3i
10
+ build/StakingToken.json: bafybeibhcwyawq377innrpq4ytpw5kotufjqo7cyd2rjhyit34mnbks5b4
11
+ contract.py: bafybeigjt7a2biiorp4vmj4d3qkm3xbbohnawoetywojye5akhavlvkrxm
12
+ fingerprint_ignore_patterns: []
13
+ contracts: []
14
+ class_name: StakingTokenContract
15
+ contract_interface_paths:
16
+ ethereum: build/StakingToken.json
17
+ dependencies:
18
+ open-aea-ledger-ethereum:
19
+ version: <2,>=1.53.0
20
+ open-aea-test-autonomy:
21
+ version: <1,>=0.14.14.post1
22
+ web3:
23
+ version: <7,>=6.0.0
@@ -8,7 +8,9 @@ aea_version: '>=1.0.0, <2.0.0'
8
8
  fingerprint:
9
9
  __init__.py: bafybeia75xbvf6zogcloppdcu2zrwcl6d46ebj2zmm6dkge6eno3k6ysw4
10
10
  build/IUniswapV2ERC20.json: bafybeid45gy6x5ah5ub5p2obdhph36skpoy5qdhikwwfso7f3655iqnpc4
11
- contract.py: bafybeiarfpumhdmxh24xuiv6jprjq5f6qlrfrlxisz5enbvvbhotafp37e
11
+ contract.py: bafybeidxnb72n5xz4fvvxogbk2xk2vz4jvtsqa3berolr34v7cug3snism
12
+ tests/__init__.py: bafybeigj3ngmfmyy4nw4xipxbqnx4nd42aqidqmmduwpx6hurssjqqkirq
13
+ tests/test_contract.py: bafybeigaf7kyhuy4qn5dww2r62hffhytab3uhmjummsaiazzvqetvomyi4
12
14
  fingerprint_ignore_patterns: []
13
15
  contracts: []
14
16
  class_name: UniswapV2ERC20Contract
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ------------------------------------------------------------------------------
3
+ #
4
+ # Copyright 2021-2022 Valory AG
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ------------------------------------------------------------------------------
19
+
20
+ """Tests package for valory/uniswap_v2_erc20 contract."""
@@ -0,0 +1,363 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ------------------------------------------------------------------------------
3
+ #
4
+ # Copyright 2021-2022 Valory AG
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ------------------------------------------------------------------------------
19
+
20
+ """Tests for valory/uniswap_v2_erc20 contract."""
21
+
22
+ from pathlib import Path
23
+ from typing import Dict
24
+ from unittest import mock
25
+
26
+ from aea.test_tools.test_contract import BaseContractTestCase
27
+
28
+ from operate.data.contracts.uniswap_v2_erc20.contract import UniswapV2ERC20Contract
29
+
30
+ PACKAGE_DIR = Path(__file__).parent.parent
31
+
32
+
33
+ CONTRACT_ADDRESS = "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2"
34
+ ADDRESS_ONE = "0x46F415F7BF30f4227F98def9d2B22ff62738fD68"
35
+ ADDRESS_TWO = "0x7A1236d5195e31f1F573AD618b2b6FEFC85C5Ce6"
36
+ ADDRESS_THREE = "0x7A1236d5195e31f1F573AD618b2b6FEFC85C5Ce6"
37
+ NONCE = 0
38
+ CHAIN_ID = 1
39
+ MAX_ALLOWANCE = 2**256 - 1
40
+
41
+
42
+ class TestUniswapV2ERC20Contract(BaseContractTestCase):
43
+ """Test TestUniswapV2ERC20Contract."""
44
+
45
+ path_to_contract = PACKAGE_DIR
46
+ ledger_identifier = "ethereum"
47
+ contract: UniswapV2ERC20Contract
48
+ owner_address = ADDRESS_ONE
49
+ sender_address = ADDRESS_TWO
50
+ gas_price = 100
51
+
52
+ @classmethod
53
+ def finish_contract_deployment(cls) -> str:
54
+ """Finish the contract deployment."""
55
+ contract_address = CONTRACT_ADDRESS
56
+ return contract_address
57
+
58
+ @classmethod
59
+ def _deploy_contract(cls, contract, ledger_api, deployer_crypto, gas) -> Dict: # type: ignore
60
+ """Deploy contract."""
61
+ return {}
62
+
63
+ def test_approve(self) -> None:
64
+ """Test approve."""
65
+ eth_value = 0
66
+ gas = 100
67
+ spender_address = ADDRESS_THREE
68
+ approval_value = 100
69
+ data = self.contract.get_instance(
70
+ self.ledger_api, self.contract_address
71
+ ).encodeABI(fn_name="approve", args=[spender_address, approval_value])
72
+ with mock.patch.object(
73
+ self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
74
+ ):
75
+ with mock.patch.object(
76
+ self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
77
+ ):
78
+ result = self.contract.approve(
79
+ self.ledger_api,
80
+ self.contract_address,
81
+ spender_address,
82
+ approval_value,
83
+ sender_address=self.sender_address,
84
+ gas=gas,
85
+ gasPrice=self.gas_price,
86
+ )
87
+ assert result == {
88
+ "chainId": CHAIN_ID,
89
+ "data": data,
90
+ "gas": gas,
91
+ "gasPrice": self.gas_price,
92
+ "nonce": NONCE,
93
+ "to": CONTRACT_ADDRESS,
94
+ "value": eth_value,
95
+ }
96
+
97
+ def test_transfer(self) -> None:
98
+ """Test transfer."""
99
+ eth_value = 0
100
+ gas = 100
101
+ spender_address = ADDRESS_THREE
102
+ value = 100
103
+ data = self.contract.get_instance(
104
+ self.ledger_api, self.contract_address
105
+ ).encodeABI(fn_name="transfer", args=[spender_address, value])
106
+ with mock.patch.object(
107
+ self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
108
+ ):
109
+ with mock.patch.object(
110
+ self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
111
+ ):
112
+ result = self.contract.transfer(
113
+ self.ledger_api,
114
+ self.contract_address,
115
+ spender_address,
116
+ value,
117
+ sender_address=self.sender_address,
118
+ gas=gas,
119
+ gasPrice=self.gas_price,
120
+ )
121
+ assert result == {
122
+ "chainId": CHAIN_ID,
123
+ "data": data,
124
+ "gas": gas,
125
+ "gasPrice": self.gas_price,
126
+ "nonce": NONCE,
127
+ "to": CONTRACT_ADDRESS,
128
+ "value": eth_value,
129
+ }
130
+
131
+ def test_transfer_from(self) -> None:
132
+ """Test transfer_from."""
133
+ eth_value = 0
134
+ gas = 100
135
+ from_address = ADDRESS_THREE
136
+ to_address = ADDRESS_ONE
137
+ value = 100
138
+ data = self.contract.get_instance(
139
+ self.ledger_api, self.contract_address
140
+ ).encodeABI(fn_name="transferFrom", args=[from_address, to_address, value])
141
+ with mock.patch.object(
142
+ self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
143
+ ):
144
+ with mock.patch.object(
145
+ self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
146
+ ):
147
+ result = self.contract.transfer_from(
148
+ self.ledger_api,
149
+ self.contract_address,
150
+ from_address,
151
+ to_address,
152
+ value,
153
+ sender_address=self.sender_address,
154
+ gas=gas,
155
+ gasPrice=self.gas_price,
156
+ )
157
+ assert result == {
158
+ "chainId": CHAIN_ID,
159
+ "data": data,
160
+ "gas": gas,
161
+ "gasPrice": self.gas_price,
162
+ "nonce": NONCE,
163
+ "to": CONTRACT_ADDRESS,
164
+ "value": eth_value,
165
+ }
166
+
167
+ def test_permit(self) -> None:
168
+ """Test permit."""
169
+ eth_value = 0
170
+ gas = 100
171
+ owner_address = ADDRESS_THREE
172
+ spender_address = CONTRACT_ADDRESS
173
+ value = 100
174
+ deadline = 10
175
+ v = 10
176
+ r = b""
177
+ s = b""
178
+ data = self.contract.get_instance(
179
+ self.ledger_api, self.contract_address
180
+ ).encodeABI(
181
+ fn_name="permit",
182
+ args=[owner_address, spender_address, value, deadline, v, r, s],
183
+ )
184
+ with mock.patch.object(
185
+ self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
186
+ ):
187
+ with mock.patch.object(
188
+ self.ledger_api.api.manager, "request_blocking", return_value=CHAIN_ID
189
+ ):
190
+ result = self.contract.permit(
191
+ self.ledger_api,
192
+ self.contract_address,
193
+ owner_address,
194
+ spender_address,
195
+ value,
196
+ deadline,
197
+ v,
198
+ r,
199
+ s,
200
+ sender_address=self.sender_address,
201
+ gas=gas,
202
+ gasPrice=self.gas_price,
203
+ )
204
+ assert result == {
205
+ "chainId": CHAIN_ID,
206
+ "data": data,
207
+ "gas": gas,
208
+ "gasPrice": self.gas_price,
209
+ "nonce": NONCE,
210
+ "to": CONTRACT_ADDRESS,
211
+ "value": eth_value,
212
+ }
213
+
214
+ def test_allowance(self) -> None:
215
+ """Test allowance."""
216
+ owner_address = ADDRESS_ONE
217
+ spender_address = ADDRESS_THREE
218
+ with mock.patch.object(
219
+ self.ledger_api.api.manager,
220
+ "request_blocking",
221
+ return_value="0x0000000000000000000000000000000000000000000000000000000000000000",
222
+ ):
223
+ result = self.contract.allowance(
224
+ self.ledger_api,
225
+ self.contract_address,
226
+ owner_address,
227
+ spender_address,
228
+ )
229
+ assert result == 0
230
+
231
+ def test_balance_of(self) -> None:
232
+ """Test balance_of."""
233
+ owner_address = ADDRESS_THREE
234
+ with mock.patch.object(
235
+ self.ledger_api.api.manager,
236
+ "request_blocking",
237
+ return_value="0x0000000000000000000000000000000000000000000000000000000000000000",
238
+ ):
239
+ result = self.contract.balance_of(
240
+ self.ledger_api, self.contract_address, owner_address
241
+ )
242
+ assert result == 0
243
+
244
+ def test_get_approve_data(self) -> None:
245
+ """Test get_method_data with approve."""
246
+
247
+ result = self.contract.get_method_data(
248
+ ledger_api=self.ledger_api,
249
+ contract_address=self.contract_address,
250
+ method_name="approve",
251
+ spender=ADDRESS_ONE,
252
+ value=MAX_ALLOWANCE,
253
+ )
254
+ assert result == {
255
+ "data": b"\t^\xa7\xb3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
256
+ b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
257
+ b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
258
+ b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
259
+ }
260
+
261
+ def test_get_transfer_data(self) -> None:
262
+ """Test get_method_data with transfer."""
263
+
264
+ result = self.contract.get_method_data(
265
+ ledger_api=self.ledger_api,
266
+ contract_address=self.contract_address,
267
+ method_name="transfer",
268
+ to=ADDRESS_ONE,
269
+ value=1,
270
+ )
271
+ assert result == {
272
+ "data": b"\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
273
+ b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
274
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
275
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
276
+ }
277
+
278
+ def test_get_transfer_from_data(self) -> None:
279
+ """Test get_method_data with transfer_from."""
280
+
281
+ # "from" is a reserved word, so must be passed as kwargs
282
+ kwargs = {"from": ADDRESS_TWO, "to": ADDRESS_ONE, "value": 1}
283
+
284
+ result = self.contract.get_method_data(
285
+ ledger_api=self.ledger_api,
286
+ contract_address=self.contract_address,
287
+ method_name="transfer_from",
288
+ **kwargs
289
+ )
290
+ assert result == {
291
+ "data": b"#\xb8r\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x126\xd5"
292
+ b"\x19^1\xf1\xf5s\xada\x8b+o\xef\xc8\\\\\xe6\x00\x00\x00\x00"
293
+ b'\x00\x00\x00\x00\x00\x00\x00\x00F\xf4\x15\xf7\xbf0\xf4"'
294
+ b"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh\x00\x00\x00\x00\x00\x00\x00\x00"
295
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
296
+ b"\x00\x00\x00\x00\x00\x00\x00\x01"
297
+ }
298
+
299
+ def test_get_permit_data(self) -> None:
300
+ """Test get_method_data with permit."""
301
+
302
+ result = self.contract.get_method_data(
303
+ ledger_api=self.ledger_api,
304
+ contract_address=self.contract_address,
305
+ method_name="permit",
306
+ owner=ADDRESS_ONE,
307
+ spender=ADDRESS_TWO,
308
+ value=1,
309
+ deadline=300,
310
+ v=0,
311
+ r=b"0",
312
+ s=b"0",
313
+ )
314
+
315
+ assert result == {
316
+ "data": b"\xd5\x05\xac\xcf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
317
+ b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
318
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x126\xd5\x19^1\xf1"
319
+ b"\xf5s\xada\x8b+o\xef\xc8\\\\\xe6\x00\x00\x00\x00\x00\x00\x00\x00"
320
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
321
+ b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
322
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
323
+ b"\x00\x00\x00\x00\x00\x00\x01,\x00\x00\x00\x00\x00\x00\x00\x00"
324
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
325
+ b"\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00"
326
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
327
+ b"\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00"
328
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
329
+ b"\x00\x00\x00\x00\x00\x00\x00\x00"
330
+ }
331
+
332
+ def test_get_allowance_data(self) -> None:
333
+ """Test get_method_data with allowance."""
334
+
335
+ result = self.contract.get_method_data(
336
+ ledger_api=self.ledger_api,
337
+ contract_address=self.contract_address,
338
+ method_name="allowance",
339
+ owner=ADDRESS_ONE,
340
+ spender=ADDRESS_TWO,
341
+ )
342
+
343
+ assert result == {
344
+ "data": b"\xddb\xed>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
345
+ b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
346
+ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x126\xd5\x19^1\xf1"
347
+ b"\xf5s\xada\x8b+o\xef\xc8\\\\\xe6"
348
+ }
349
+
350
+ def test_get_balance_of_data(self) -> None:
351
+ """Test get_method_data with balance_of."""
352
+
353
+ result = self.contract.get_method_data(
354
+ ledger_api=self.ledger_api,
355
+ contract_address=self.contract_address,
356
+ method_name="balance_of",
357
+ owner=ADDRESS_ONE,
358
+ )
359
+
360
+ assert result == {
361
+ "data": b"p\xa0\x821\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
362
+ b"F\xf4\x15\xf7\xbf0\xf4\"\x7f\x98\xde\xf9\xd2\xb2/\xf6'8\xfdh"
363
+ }
operate/keys.py CHANGED
@@ -20,17 +20,18 @@
20
20
  """Keys manager."""
21
21
 
22
22
  import json
23
- import logging
24
23
  import os
25
- import typing as t
24
+ import tempfile
26
25
  from dataclasses import dataclass
26
+ from logging import Logger
27
27
  from pathlib import Path
28
+ from typing import Any, Optional
28
29
 
29
- from aea.helpers.logging import setup_logger
30
30
  from aea_ledger_ethereum.ethereum import EthereumCrypto
31
31
 
32
+ from operate.operate_types import LedgerType
32
33
  from operate.resource import LocalResource
33
- from operate.types import LedgerType
34
+ from operate.utils import unrecoverable_delete
34
35
 
35
36
 
36
37
  @dataclass
@@ -41,35 +42,66 @@ class Key(LocalResource):
41
42
  address: str
42
43
  private_key: str
43
44
 
45
+ def get_decrypted_json(self, password: str) -> dict:
46
+ """Get decrypted key json."""
47
+ return {
48
+ "ledger": self.ledger.value,
49
+ "address": self.address,
50
+ "private_key": "0x"
51
+ + EthereumCrypto.decrypt(self.private_key, password=password),
52
+ }
53
+
44
54
  @classmethod
45
55
  def load(cls, path: Path) -> "Key":
46
56
  """Load a service"""
47
57
  return super().load(path) # type: ignore
48
58
 
49
59
 
50
- Keys = t.List[Key]
51
-
52
-
53
60
  class KeysManager:
54
61
  """Keys manager."""
55
62
 
56
- def __init__(
57
- self,
58
- path: Path,
59
- logger: t.Optional[logging.Logger] = None,
60
- ) -> None:
63
+ def __init__(self, **kwargs: Any) -> None:
61
64
  """
62
65
  Initialize keys manager
63
66
 
64
67
  :param path: Path to keys storage.
65
68
  :param logger: logging.Logger object.
66
69
  """
67
- self.path = path
68
- self.logger = logger or setup_logger(name="operate.keys")
69
-
70
- def setup(self) -> None:
71
- """Setup service manager."""
72
- self.path.mkdir(exist_ok=True)
70
+ if "path" not in kwargs:
71
+ raise ValueError("Path must be provided for KeysManager")
72
+
73
+ self.path: Path = kwargs["path"]
74
+ self.logger: Logger = kwargs["logger"]
75
+ self.password: Optional[str] = kwargs.get("password")
76
+ self.path.mkdir(exist_ok=True, parents=True)
77
+
78
+ def private_key_to_crypto(
79
+ self, private_key: str, password: Optional[str]
80
+ ) -> EthereumCrypto:
81
+ """Convert private key string to EthereumCrypto instance."""
82
+ with tempfile.NamedTemporaryFile(
83
+ dir=self.path,
84
+ mode="w",
85
+ suffix=".txt",
86
+ delete=False, # Handle cleanup manually
87
+ ) as temp_file:
88
+ temp_file_name = temp_file.name
89
+ temp_file.write(private_key)
90
+ temp_file.flush()
91
+ temp_file.close() # Close the file before reading
92
+
93
+ # Set proper file permissions (readable by owner only)
94
+ os.chmod(temp_file_name, 0o600)
95
+ crypto = EthereumCrypto(private_key_path=temp_file_name, password=password)
96
+
97
+ try:
98
+ unrecoverable_delete(
99
+ Path(temp_file.name)
100
+ ) # Clean up the temporary file
101
+ except OSError as e:
102
+ self.logger.error(f"Failed to delete temp file {temp_file.name}: {e}")
103
+
104
+ return crypto
73
105
 
74
106
  def get(self, key: str) -> Key:
75
107
  """Get key object."""
@@ -81,26 +113,79 @@ class KeysManager:
81
113
  )
82
114
  )
83
115
 
116
+ def get_decrypted(self, key: str) -> dict:
117
+ """Get key json."""
118
+ if self.password is not None:
119
+ return self.get(key).get_decrypted_json(self.password)
120
+ return self.get(key).json
121
+
122
+ def get_private_key_file(self, address: str) -> Path:
123
+ """Get the path to the private key file for the given address."""
124
+ path = self.path / f"{address}_private_key"
125
+
126
+ if path.is_file():
127
+ return path
128
+
129
+ key = self.get(address)
130
+ private_key = key.private_key
131
+ path.write_text(private_key, encoding="utf-8")
132
+ os.chmod(path, 0o600)
133
+ return path
134
+
135
+ def get_crypto_instance(self, address: str) -> EthereumCrypto:
136
+ """Get EthereumCrypto instance for the given address."""
137
+ key: Key = self.get(address)
138
+ return self.private_key_to_crypto(key.private_key, self.password)
139
+
84
140
  def create(self) -> str:
85
141
  """Creates new key."""
86
- crypto = EthereumCrypto()
87
- path = self.path / crypto.address
88
- if path.is_file():
89
- return crypto.address
90
-
91
- path.write_text(
92
- json.dumps(
93
- Key(
94
- ledger=LedgerType.ETHEREUM,
95
- address=crypto.address,
96
- private_key=crypto.private_key,
97
- ).json,
98
- indent=4,
99
- ),
100
- encoding="utf-8",
142
+ self.path.mkdir(exist_ok=True, parents=True)
143
+ crypto = EthereumCrypto(password=self.password)
144
+ key = Key(
145
+ ledger=LedgerType.ETHEREUM,
146
+ address=crypto.address,
147
+ private_key=crypto.encrypt(password=self.password)
148
+ if self.password is not None
149
+ else crypto.private_key,
101
150
  )
151
+ for path in (
152
+ self.path / f"{crypto.address}.bak",
153
+ self.path / crypto.address,
154
+ ):
155
+ if path.is_file():
156
+ continue
157
+
158
+ path.write_text(
159
+ json.dumps(
160
+ key.json,
161
+ indent=2,
162
+ ),
163
+ encoding="utf-8",
164
+ )
165
+
102
166
  return crypto.address
103
167
 
104
168
  def delete(self, key: str) -> None:
105
169
  """Delete key."""
106
170
  os.remove(self.path / key)
171
+
172
+ def update_password(self, new_password: str) -> None:
173
+ """Update password for all keys."""
174
+ for key_file in self.path.iterdir():
175
+ if not key_file.is_file() or key_file.suffix == ".bak":
176
+ continue
177
+
178
+ key = self.get(key_file.name)
179
+ crypto = self.get_crypto_instance(key_file.name)
180
+ encrypted_private_key = crypto.encrypt(password=new_password)
181
+ key.private_key = encrypted_private_key
182
+ key.path = self.path / key_file.name
183
+ key.store()
184
+
185
+ backup_path = self.path / f"{key.address}.bak"
186
+ backup_path.write_text(
187
+ json.dumps(key.json, indent=2),
188
+ encoding="utf-8",
189
+ )
190
+
191
+ self.password = new_password