iwa 0.0.0__py3-none-any.whl → 0.0.1a2__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.
- conftest.py +22 -0
- iwa/__init__.py +1 -0
- iwa/__main__.py +6 -0
- iwa/core/__init__.py +1 -0
- iwa/core/chain/__init__.py +68 -0
- iwa/core/chain/errors.py +47 -0
- iwa/core/chain/interface.py +514 -0
- iwa/core/chain/manager.py +38 -0
- iwa/core/chain/models.py +128 -0
- iwa/core/chain/rate_limiter.py +193 -0
- iwa/core/cli.py +210 -0
- iwa/core/constants.py +28 -0
- iwa/core/contracts/__init__.py +1 -0
- iwa/core/contracts/contract.py +297 -0
- iwa/core/contracts/erc20.py +79 -0
- iwa/core/contracts/multisend.py +71 -0
- iwa/core/db.py +317 -0
- iwa/core/keys.py +361 -0
- iwa/core/mnemonic.py +385 -0
- iwa/core/models.py +344 -0
- iwa/core/monitor.py +209 -0
- iwa/core/plugins.py +45 -0
- iwa/core/pricing.py +91 -0
- iwa/core/services/__init__.py +17 -0
- iwa/core/services/account.py +57 -0
- iwa/core/services/balance.py +113 -0
- iwa/core/services/plugin.py +88 -0
- iwa/core/services/safe.py +392 -0
- iwa/core/services/transaction.py +172 -0
- iwa/core/services/transfer/__init__.py +166 -0
- iwa/core/services/transfer/base.py +260 -0
- iwa/core/services/transfer/erc20.py +247 -0
- iwa/core/services/transfer/multisend.py +386 -0
- iwa/core/services/transfer/native.py +262 -0
- iwa/core/services/transfer/swap.py +326 -0
- iwa/core/settings.py +95 -0
- iwa/core/tables.py +60 -0
- iwa/core/test.py +27 -0
- iwa/core/tests/test_wallet.py +255 -0
- iwa/core/types.py +59 -0
- iwa/core/ui.py +99 -0
- iwa/core/utils.py +59 -0
- iwa/core/wallet.py +380 -0
- iwa/plugins/__init__.py +1 -0
- iwa/plugins/gnosis/__init__.py +5 -0
- iwa/plugins/gnosis/cow/__init__.py +6 -0
- iwa/plugins/gnosis/cow/quotes.py +148 -0
- iwa/plugins/gnosis/cow/swap.py +403 -0
- iwa/plugins/gnosis/cow/types.py +20 -0
- iwa/plugins/gnosis/cow_utils.py +44 -0
- iwa/plugins/gnosis/plugin.py +68 -0
- iwa/plugins/gnosis/safe.py +157 -0
- iwa/plugins/gnosis/tests/test_cow.py +227 -0
- iwa/plugins/gnosis/tests/test_safe.py +100 -0
- iwa/plugins/olas/__init__.py +5 -0
- iwa/plugins/olas/constants.py +106 -0
- iwa/plugins/olas/contracts/activity_checker.py +93 -0
- iwa/plugins/olas/contracts/base.py +10 -0
- iwa/plugins/olas/contracts/mech.py +49 -0
- iwa/plugins/olas/contracts/mech_marketplace.py +43 -0
- iwa/plugins/olas/contracts/service.py +215 -0
- iwa/plugins/olas/contracts/staking.py +403 -0
- iwa/plugins/olas/importer.py +736 -0
- iwa/plugins/olas/mech_reference.py +135 -0
- iwa/plugins/olas/models.py +110 -0
- iwa/plugins/olas/plugin.py +243 -0
- iwa/plugins/olas/scripts/test_full_mech_flow.py +259 -0
- iwa/plugins/olas/scripts/test_simple_lifecycle.py +74 -0
- iwa/plugins/olas/service_manager/__init__.py +60 -0
- iwa/plugins/olas/service_manager/base.py +113 -0
- iwa/plugins/olas/service_manager/drain.py +336 -0
- iwa/plugins/olas/service_manager/lifecycle.py +839 -0
- iwa/plugins/olas/service_manager/mech.py +322 -0
- iwa/plugins/olas/service_manager/staking.py +530 -0
- iwa/plugins/olas/tests/conftest.py +30 -0
- iwa/plugins/olas/tests/test_importer.py +128 -0
- iwa/plugins/olas/tests/test_importer_error_handling.py +349 -0
- iwa/plugins/olas/tests/test_mech_contracts.py +85 -0
- iwa/plugins/olas/tests/test_olas_contracts.py +249 -0
- iwa/plugins/olas/tests/test_olas_integration.py +561 -0
- iwa/plugins/olas/tests/test_olas_models.py +144 -0
- iwa/plugins/olas/tests/test_olas_view.py +258 -0
- iwa/plugins/olas/tests/test_olas_view_actions.py +137 -0
- iwa/plugins/olas/tests/test_olas_view_modals.py +120 -0
- iwa/plugins/olas/tests/test_plugin.py +70 -0
- iwa/plugins/olas/tests/test_plugin_full.py +212 -0
- iwa/plugins/olas/tests/test_service_lifecycle.py +150 -0
- iwa/plugins/olas/tests/test_service_manager.py +1065 -0
- iwa/plugins/olas/tests/test_service_manager_errors.py +208 -0
- iwa/plugins/olas/tests/test_service_manager_flows.py +497 -0
- iwa/plugins/olas/tests/test_service_manager_mech.py +135 -0
- iwa/plugins/olas/tests/test_service_manager_rewards.py +360 -0
- iwa/plugins/olas/tests/test_service_manager_validation.py +145 -0
- iwa/plugins/olas/tests/test_service_staking.py +342 -0
- iwa/plugins/olas/tests/test_staking_integration.py +269 -0
- iwa/plugins/olas/tests/test_staking_validation.py +109 -0
- iwa/plugins/olas/tui/__init__.py +1 -0
- iwa/plugins/olas/tui/olas_view.py +952 -0
- iwa/tools/check_profile.py +67 -0
- iwa/tools/release.py +111 -0
- iwa/tools/reset_env.py +111 -0
- iwa/tools/reset_tenderly.py +362 -0
- iwa/tools/restore_backup.py +82 -0
- iwa/tui/__init__.py +1 -0
- iwa/tui/app.py +174 -0
- iwa/tui/modals/__init__.py +5 -0
- iwa/tui/modals/base.py +406 -0
- iwa/tui/rpc.py +63 -0
- iwa/tui/screens/__init__.py +1 -0
- iwa/tui/screens/wallets.py +749 -0
- iwa/tui/tests/test_app.py +125 -0
- iwa/tui/tests/test_rpc.py +139 -0
- iwa/tui/tests/test_wallets_refactor.py +30 -0
- iwa/tui/tests/test_widgets.py +123 -0
- iwa/tui/widgets/__init__.py +5 -0
- iwa/tui/widgets/base.py +100 -0
- iwa/tui/workers.py +42 -0
- iwa/web/dependencies.py +76 -0
- iwa/web/models.py +76 -0
- iwa/web/routers/accounts.py +115 -0
- iwa/web/routers/olas/__init__.py +24 -0
- iwa/web/routers/olas/admin.py +169 -0
- iwa/web/routers/olas/funding.py +135 -0
- iwa/web/routers/olas/general.py +29 -0
- iwa/web/routers/olas/services.py +378 -0
- iwa/web/routers/olas/staking.py +341 -0
- iwa/web/routers/state.py +65 -0
- iwa/web/routers/swap.py +617 -0
- iwa/web/routers/transactions.py +153 -0
- iwa/web/server.py +155 -0
- iwa/web/tests/test_web_endpoints.py +713 -0
- iwa/web/tests/test_web_olas.py +430 -0
- iwa/web/tests/test_web_swap.py +103 -0
- iwa-0.0.1a2.dist-info/METADATA +234 -0
- iwa-0.0.1a2.dist-info/RECORD +186 -0
- iwa-0.0.1a2.dist-info/entry_points.txt +2 -0
- iwa-0.0.1a2.dist-info/licenses/LICENSE +21 -0
- iwa-0.0.1a2.dist-info/top_level.txt +4 -0
- tests/legacy_cow.py +248 -0
- tests/legacy_safe.py +93 -0
- tests/legacy_transaction_retry_logic.py +51 -0
- tests/legacy_tui.py +440 -0
- tests/legacy_wallets_screen.py +554 -0
- tests/legacy_web.py +243 -0
- tests/test_account_service.py +120 -0
- tests/test_balance_service.py +186 -0
- tests/test_chain.py +490 -0
- tests/test_chain_interface.py +210 -0
- tests/test_cli.py +139 -0
- tests/test_contract.py +195 -0
- tests/test_db.py +180 -0
- tests/test_drain_coverage.py +174 -0
- tests/test_erc20.py +95 -0
- tests/test_gnosis_plugin.py +111 -0
- tests/test_keys.py +449 -0
- tests/test_legacy_wallet.py +1285 -0
- tests/test_main.py +13 -0
- tests/test_mnemonic.py +217 -0
- tests/test_modals.py +109 -0
- tests/test_models.py +213 -0
- tests/test_monitor.py +202 -0
- tests/test_multisend.py +84 -0
- tests/test_plugin_service.py +119 -0
- tests/test_pricing.py +143 -0
- tests/test_rate_limiter.py +199 -0
- tests/test_reset_tenderly.py +202 -0
- tests/test_rpc_view.py +73 -0
- tests/test_safe_coverage.py +139 -0
- tests/test_safe_service.py +168 -0
- tests/test_service_manager_integration.py +61 -0
- tests/test_service_manager_structure.py +31 -0
- tests/test_service_transaction.py +176 -0
- tests/test_staking_router.py +71 -0
- tests/test_staking_simple.py +31 -0
- tests/test_tables.py +76 -0
- tests/test_transaction_service.py +161 -0
- tests/test_transfer_multisend.py +179 -0
- tests/test_transfer_native.py +220 -0
- tests/test_transfer_security.py +93 -0
- tests/test_transfer_structure.py +37 -0
- tests/test_transfer_swap_unit.py +155 -0
- tests/test_ui_coverage.py +66 -0
- tests/test_utils.py +53 -0
- tests/test_workers.py +91 -0
- tools/verify_drain.py +183 -0
- __init__.py +0 -2
- hello.py +0 -6
- iwa-0.0.0.dist-info/METADATA +0 -10
- iwa-0.0.0.dist-info/RECORD +0 -6
- iwa-0.0.0.dist-info/top_level.txt +0 -2
- {iwa-0.0.0.dist-info → iwa-0.0.1a2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Tests for CowSwap module."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from iwa.core.chain import SupportedChain
|
|
8
|
+
from iwa.plugins.gnosis.cow import CowSwap, OrderType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def mock_chain():
|
|
13
|
+
"""Mock supported chain."""
|
|
14
|
+
mock = MagicMock(spec=SupportedChain)
|
|
15
|
+
mock.chain_id = 100
|
|
16
|
+
mock.name = "Gnosis"
|
|
17
|
+
mock.get_token_address.return_value = "0xToken"
|
|
18
|
+
return mock
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def mock_cowpy_modules():
|
|
23
|
+
"""Mock cowpy modules."""
|
|
24
|
+
with (
|
|
25
|
+
patch("iwa.plugins.gnosis.cow.swap.get_cowpy_module") as mock_get_swap,
|
|
26
|
+
patch("iwa.plugins.gnosis.cow.quotes.get_cowpy_module") as mock_get_quotes,
|
|
27
|
+
):
|
|
28
|
+
# Create mocks for all various modules
|
|
29
|
+
mocks = {
|
|
30
|
+
"SupportedChainId": MagicMock(),
|
|
31
|
+
"Chain": MagicMock(),
|
|
32
|
+
"OrderBookApi": MagicMock(),
|
|
33
|
+
"OrderBookAPIConfigFactory": MagicMock(),
|
|
34
|
+
"get_order_quote": AsyncMock(),
|
|
35
|
+
"OrderQuoteRequest": MagicMock(),
|
|
36
|
+
"OrderQuoteSide3": MagicMock(),
|
|
37
|
+
"OrderQuoteSideKindBuy": MagicMock(),
|
|
38
|
+
"TokenAmount": MagicMock(),
|
|
39
|
+
"OrderQuoteSide1": MagicMock(),
|
|
40
|
+
"OrderQuoteSideKindSell": MagicMock(),
|
|
41
|
+
"Order": MagicMock(),
|
|
42
|
+
"PreSignSignature": MagicMock(),
|
|
43
|
+
"SigningScheme": MagicMock(),
|
|
44
|
+
"sign_order": MagicMock(),
|
|
45
|
+
"post_order": AsyncMock(),
|
|
46
|
+
"CompletedOrder": MagicMock(),
|
|
47
|
+
"swap_tokens": AsyncMock(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Setup specific returns
|
|
51
|
+
mocks["get_order_quote"].return_value.quote.sellAmount.root = "100"
|
|
52
|
+
mocks["get_order_quote"].return_value.quote.buyAmount.root = "90"
|
|
53
|
+
mocks["get_order_quote"].return_value.quote.validTo = 1234567890
|
|
54
|
+
|
|
55
|
+
mocks["post_order"].return_value = "0xOrderUID"
|
|
56
|
+
|
|
57
|
+
# Correctly mock Chain iteration for get_chain logic
|
|
58
|
+
# chain.value[0] == supported_chain_id (which is mocked as MagicMock by default,
|
|
59
|
+
# but in init it calls SupportedChainId(chain.chain_id))
|
|
60
|
+
|
|
61
|
+
# Let's make supported_chain_id return a specific value and chain matching it
|
|
62
|
+
mock_supported_id = MagicMock()
|
|
63
|
+
mocks["SupportedChainId"].return_value = mock_supported_id
|
|
64
|
+
|
|
65
|
+
mock_chain_enum_item = MagicMock()
|
|
66
|
+
mock_chain_enum_item.value = [mock_supported_id]
|
|
67
|
+
|
|
68
|
+
# Make Chain iterable
|
|
69
|
+
mocks["Chain"].__iter__.return_value = [mock_chain_enum_item]
|
|
70
|
+
|
|
71
|
+
mock_get_swap.side_effect = lambda name: mocks.get(name, MagicMock())
|
|
72
|
+
mock_get_quotes.side_effect = mock_get_swap.side_effect
|
|
73
|
+
yield mocks
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.fixture
|
|
77
|
+
def cowswap(mock_chain, mock_cowpy_modules):
|
|
78
|
+
"""CowSwap instance fixture."""
|
|
79
|
+
return CowSwap("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", mock_chain)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_init(cowswap, mock_chain):
|
|
83
|
+
"""Test initialization."""
|
|
84
|
+
assert cowswap.chain == mock_chain
|
|
85
|
+
assert cowswap.cow_chain is not None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_get_max_sell_amount_wei(cowswap, mock_cowpy_modules):
|
|
90
|
+
"""Test get_max_sell_amount_wei."""
|
|
91
|
+
amount = await cowswap.get_max_sell_amount_wei(100, "0xSell", "0xBuy")
|
|
92
|
+
# mocked sellAmount root is "100", slippage is 0.005 default -> 100 * 1.005 = 100
|
|
93
|
+
assert amount == 100
|
|
94
|
+
mock_cowpy_modules["get_order_quote"].assert_called_once()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.mark.asyncio
|
|
98
|
+
async def test_get_max_buy_amount_wei(cowswap, mock_cowpy_modules):
|
|
99
|
+
"""Test get_max_buy_amount_wei."""
|
|
100
|
+
amount = await cowswap.get_max_buy_amount_wei(100, "0xSell", "0xBuy")
|
|
101
|
+
# mocked buyAmount root is "90", slippage 0.005 -> 90 * 0.995 = 89.55 -> int 89
|
|
102
|
+
assert amount == 89
|
|
103
|
+
mock_cowpy_modules["get_order_quote"].assert_called_once()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.asyncio
|
|
107
|
+
async def test_swap_defaults(cowswap, mock_cowpy_modules):
|
|
108
|
+
"""Test swap with default settings."""
|
|
109
|
+
# Test SWAP with default logic (using swap_tokens from module)
|
|
110
|
+
# We need to make sure global swap_tokens is None or handled.
|
|
111
|
+
# In test context, we rely on _get_cowpy_module returning the mock.
|
|
112
|
+
|
|
113
|
+
mock_cowpy_modules["swap_tokens"].return_value = MagicMock(uid=MagicMock(root="0x123"))
|
|
114
|
+
|
|
115
|
+
# Mock verify order to return True immediately to avoid sleep
|
|
116
|
+
with patch.object(CowSwap, "check_cowswap_order", return_value={"status": "fulfilled"}):
|
|
117
|
+
result = await cowswap.swap(100, "OLAS", "WXDAI", order_type=OrderType.SELL)
|
|
118
|
+
assert result is not None
|
|
119
|
+
mock_cowpy_modules["swap_tokens"].assert_called()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@pytest.mark.asyncio
|
|
123
|
+
async def test_swap_buy_order_type(cowswap, mock_cowpy_modules):
|
|
124
|
+
"""Test swap with BUY order type."""
|
|
125
|
+
# For BUY order type, it uses self.swap_tokens_to_exact_tokens
|
|
126
|
+
# checking patching of global swap_tokens
|
|
127
|
+
|
|
128
|
+
with patch("iwa.plugins.gnosis.cow.swap.swap_tokens", new=None):
|
|
129
|
+
with patch.object(
|
|
130
|
+
CowSwap, "swap_tokens_to_exact_tokens", new_callable=AsyncMock
|
|
131
|
+
) as mock_custom_swap:
|
|
132
|
+
mock_custom_swap.return_value = MagicMock(uid=MagicMock(root="0x123"))
|
|
133
|
+
with patch.object(CowSwap, "check_cowswap_order", return_value={"status": "fulfilled"}):
|
|
134
|
+
result = await cowswap.swap(100, "OLAS", "WXDAI", order_type=OrderType.BUY)
|
|
135
|
+
assert result is not None
|
|
136
|
+
mock_custom_swap.assert_called()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.asyncio
|
|
140
|
+
async def test_swap_tokens_to_exact_tokens(cowswap, mock_cowpy_modules):
|
|
141
|
+
"""Test swap_tokens_to_exact_tokens custom logic."""
|
|
142
|
+
# Test the custom implementation
|
|
143
|
+
# It calls get_order_quote, post_order
|
|
144
|
+
|
|
145
|
+
mock_cowpy_modules["post_order"].return_value = "0xOrderUID"
|
|
146
|
+
mock_cowpy_modules["OrderBookApi"].return_value.get_order_link.return_value = "http://link"
|
|
147
|
+
|
|
148
|
+
# Mock CompletedOrder to return an object with attributes set from constructor
|
|
149
|
+
def side_effect(uid, url):
|
|
150
|
+
m = MagicMock()
|
|
151
|
+
m.uid = uid
|
|
152
|
+
m.url = url
|
|
153
|
+
return m
|
|
154
|
+
|
|
155
|
+
mock_cowpy_modules["CompletedOrder"].side_effect = side_effect
|
|
156
|
+
|
|
157
|
+
result = await CowSwap.swap_tokens_to_exact_tokens(
|
|
158
|
+
amount=100,
|
|
159
|
+
account=MagicMock(address="0xUser"),
|
|
160
|
+
chain=MagicMock(value=[100]),
|
|
161
|
+
sell_token="0xSell",
|
|
162
|
+
buy_token="0xBuy",
|
|
163
|
+
env="prod",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
assert result.uid == "0xOrderUID"
|
|
167
|
+
assert result.url == "http://link"
|
|
168
|
+
mock_cowpy_modules["get_order_quote"].assert_called() # Quote needed for sell amount calc
|
|
169
|
+
mock_cowpy_modules["post_order"].assert_called()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.asyncio
|
|
173
|
+
async def test_check_cowswap_order_success(cowswap):
|
|
174
|
+
"""Test check_cowswap_order success path."""
|
|
175
|
+
mock_order = MagicMock()
|
|
176
|
+
mock_order.url = "http://api/order"
|
|
177
|
+
|
|
178
|
+
with patch("requests.get") as mock_get:
|
|
179
|
+
mock_get.return_value.status_code = 200
|
|
180
|
+
mock_get.return_value.json.return_value = {
|
|
181
|
+
"status": "fulfilled",
|
|
182
|
+
"executedSellAmount": "100",
|
|
183
|
+
"executedBuyAmount": "90",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Need to mock loop.run_in_executor since check_cowswap_order uses it
|
|
187
|
+
# Or just let it run if requests.get is mocked?
|
|
188
|
+
# check_cowswap_order calls loop.run_in_executor(None, lambda: requests.get(...))
|
|
189
|
+
# This will run the lambda in a thread. The mock should work.
|
|
190
|
+
|
|
191
|
+
result = await cowswap.check_cowswap_order(mock_order)
|
|
192
|
+
|
|
193
|
+
assert result == {
|
|
194
|
+
"status": "fulfilled",
|
|
195
|
+
"executedSellAmount": "100",
|
|
196
|
+
"executedBuyAmount": "90",
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@pytest.mark.asyncio
|
|
201
|
+
async def test_check_cowswap_order_expired(cowswap):
|
|
202
|
+
"""Test check_cowswap_order expiration."""
|
|
203
|
+
mock_order = MagicMock()
|
|
204
|
+
mock_order.url = "http://api/order"
|
|
205
|
+
|
|
206
|
+
with patch("requests.get") as mock_get:
|
|
207
|
+
mock_get.return_value.status_code = 200
|
|
208
|
+
mock_get.return_value.json.return_value = {"status": "expired"}
|
|
209
|
+
|
|
210
|
+
result = await cowswap.check_cowswap_order(mock_order)
|
|
211
|
+
assert result is None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@pytest.mark.asyncio
|
|
215
|
+
async def test_check_cowswap_order_timeout(cowswap):
|
|
216
|
+
"""Test check_cowswap_order timeout."""
|
|
217
|
+
mock_order = MagicMock()
|
|
218
|
+
mock_order.url = "http://api/order"
|
|
219
|
+
|
|
220
|
+
with patch("requests.get") as mock_get:
|
|
221
|
+
mock_get.return_value.status_code = 200
|
|
222
|
+
mock_get.return_value.json.return_value = {"status": "open", "executedSellAmount": "0"}
|
|
223
|
+
|
|
224
|
+
# Speed up retry sleep (asyncio.sleep)
|
|
225
|
+
with patch("asyncio.sleep", new_callable=AsyncMock):
|
|
226
|
+
result = await cowswap.check_cowswap_order(mock_order)
|
|
227
|
+
assert result is None
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Tests for Safe module."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from iwa.core.models import StoredSafeAccount
|
|
8
|
+
from iwa.plugins.gnosis.safe import SafeMultisig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def mock_settings():
|
|
13
|
+
"""Mock settings."""
|
|
14
|
+
with patch("iwa.plugins.gnosis.safe.settings") as mock:
|
|
15
|
+
mock.gnosis_rpc.get_secret_value.return_value = "http://rpc"
|
|
16
|
+
yield mock
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def mock_safe_eth():
|
|
21
|
+
"""Mock safe_eth module."""
|
|
22
|
+
with (
|
|
23
|
+
patch("iwa.plugins.gnosis.safe.EthereumClient") as mock_client,
|
|
24
|
+
patch("iwa.plugins.gnosis.safe.Safe") as mock_safe,
|
|
25
|
+
):
|
|
26
|
+
yield mock_client, mock_safe
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def safe_account():
|
|
31
|
+
"""Mock safe account."""
|
|
32
|
+
return StoredSafeAccount(
|
|
33
|
+
address="0x1234567890123456789012345678901234567890",
|
|
34
|
+
owners=["0x1234567890123456789012345678901234567890"],
|
|
35
|
+
threshold=1,
|
|
36
|
+
chains=["gnosis"],
|
|
37
|
+
tag="mysafe",
|
|
38
|
+
signers=[],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_init(safe_account, mock_settings, mock_safe_eth):
|
|
43
|
+
"""Test initialization."""
|
|
44
|
+
ms = SafeMultisig(safe_account, "gnosis")
|
|
45
|
+
assert ms.multisig is not None
|
|
46
|
+
mock_safe_eth[0].assert_called_with("http://rpc") # EthereumClient init
|
|
47
|
+
mock_safe_eth[1].assert_called() # Safe init
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_init_invalid_chain(safe_account, mock_settings, mock_safe_eth):
|
|
51
|
+
"""Test initialization with invalid chain."""
|
|
52
|
+
with pytest.raises(ValueError, match="not deployed on chain"):
|
|
53
|
+
SafeMultisig(safe_account, "ethereum")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_getters(safe_account, mock_settings, mock_safe_eth):
|
|
57
|
+
"""Test safe property getters."""
|
|
58
|
+
ms = SafeMultisig(safe_account, "gnosis")
|
|
59
|
+
mock_safe_instance = mock_safe_eth[1].return_value
|
|
60
|
+
|
|
61
|
+
mock_safe_instance.retrieve_owners.return_value = ["0x1"]
|
|
62
|
+
assert ms.get_owners() == ["0x1"]
|
|
63
|
+
|
|
64
|
+
mock_safe_instance.retrieve_threshold.return_value = 2
|
|
65
|
+
assert ms.get_threshold() == 2
|
|
66
|
+
|
|
67
|
+
mock_safe_instance.retrieve_nonce.return_value = 5
|
|
68
|
+
assert ms.get_nonce() == 5
|
|
69
|
+
|
|
70
|
+
mock_safe_instance.retrieve_all_info.return_value = {"info": "test"}
|
|
71
|
+
assert ms.retrieve_all_info() == {"info": "test"}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_build_tx(safe_account, mock_settings, mock_safe_eth):
|
|
75
|
+
"""Test build_multisig_tx."""
|
|
76
|
+
ms = SafeMultisig(safe_account, "gnosis")
|
|
77
|
+
mock_safe_instance = mock_safe_eth[1].return_value
|
|
78
|
+
mock_safe_instance.build_multisig_tx.return_value = "0xTx"
|
|
79
|
+
|
|
80
|
+
tx = ms.build_tx("0xTo", 100)
|
|
81
|
+
assert tx == "0xTx"
|
|
82
|
+
|
|
83
|
+
mock_safe_instance.build_multisig_tx.assert_called()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_send_tx(safe_account, mock_settings, mock_safe_eth):
|
|
87
|
+
"""Test send_multisig_tx."""
|
|
88
|
+
ms = SafeMultisig(safe_account, "gnosis")
|
|
89
|
+
|
|
90
|
+
# Mock build_tx just in case (though it delegates)
|
|
91
|
+
# Actually we can let it delegate to mock_safe_instance which returns "0xSafeTx"
|
|
92
|
+
mock_safe_instance = mock_safe_eth[1].return_value
|
|
93
|
+
mock_safe_instance.build_multisig_tx.return_value = "0xSafeTx"
|
|
94
|
+
|
|
95
|
+
callback = MagicMock(return_value="0xHash")
|
|
96
|
+
|
|
97
|
+
tx_hash = ms.send_tx("0xTo", 100, callback)
|
|
98
|
+
assert tx_hash == "0xHash"
|
|
99
|
+
|
|
100
|
+
callback.assert_called_with("0xSafeTx")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""OLAS protocol constants."""
|
|
2
|
+
|
|
3
|
+
from enum import IntEnum
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from iwa.core.models import EthereumAddress
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentType(IntEnum):
|
|
10
|
+
"""Supported OLAS agent types."""
|
|
11
|
+
|
|
12
|
+
TRADER = 25
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Mech Marketplace Payment Types (bytes32 hex strings, without 0x prefix)
|
|
16
|
+
# From mech-client/marketplace_interact.py
|
|
17
|
+
PAYMENT_TYPE_NATIVE = "ba699a34be8fe0e7725e93dcbce1701b0211a8ca61330aaeb8a05bf2ec7abed1"
|
|
18
|
+
PAYMENT_TYPE_TOKEN = "3679d66ef546e66ce9057c4a052f317b135bc8e8c509638f7966edfd4fcf45e9"
|
|
19
|
+
PAYMENT_TYPE_NATIVE_NVM = "803dd08fe79d91027fc9024e254a0942372b92f3ccabc1bd19f4a5c2b251c316"
|
|
20
|
+
PAYMENT_TYPE_TOKEN_NVM_USDC = "0d6fd99afa9c4c580fab5e341922c2a5c4b61d880da60506193d7bf88944dd14"
|
|
21
|
+
|
|
22
|
+
# Mech Factory to Mech Type mappings by chain
|
|
23
|
+
# From mech-client/mech_marketplace_subgraph.py
|
|
24
|
+
MECH_FACTORY_TO_TYPE: Dict[str, Dict[str, str]] = {
|
|
25
|
+
"gnosis": {
|
|
26
|
+
"0x8b299c20F87e3fcBfF0e1B86dC0acC06AB6993EF": "Fixed Price Native",
|
|
27
|
+
"0x31ffDC795FDF36696B8eDF7583A3D115995a45FA": "Fixed Price Token",
|
|
28
|
+
"0x65fd74C29463afe08c879a3020323DD7DF02DA57": "NvmSubscription Native",
|
|
29
|
+
},
|
|
30
|
+
"base": {
|
|
31
|
+
"0x2E008211f34b25A7d7c102403c6C2C3B665a1abe": "Fixed Price Native",
|
|
32
|
+
"0x97371B1C0cDA1D04dFc43DFb50a04645b7Bc9BEe": "Fixed Price Token",
|
|
33
|
+
"0x847bBE8b474e0820215f818858e23F5f5591855A": "NvmSubscription Native",
|
|
34
|
+
"0x7beD01f8482fF686F025628e7780ca6C1f0559fc": "NvmSubscription Token USDC",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
TRADER_CONFIG_HASH = "108e90795119d6015274ef03af1a669c6d13ab6acc9e2b2978be01ee9ea2ec93"
|
|
39
|
+
DEFAULT_DEPLOY_PAYLOAD = "0x0000000000000000000000000000000000000000{fallback_handler}000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
40
|
+
|
|
41
|
+
# OLAS Token address on Gnosis chain
|
|
42
|
+
OLAS_TOKEN_ADDRESS_GNOSIS = EthereumAddress("0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# OLAS Protocol Contracts categorized by chain
|
|
46
|
+
# See mech_reference.py for comprehensive documentation of the mech ecosystem
|
|
47
|
+
OLAS_CONTRACTS: Dict[str, Dict[str, EthereumAddress]] = {
|
|
48
|
+
"gnosis": {
|
|
49
|
+
"OLAS_SERVICE_REGISTRY": EthereumAddress("0x9338b5153AE39BB89f50468E608eD9d764B755fD"),
|
|
50
|
+
"OLAS_SERVICE_REGISTRY_TOKEN_UTILITY": EthereumAddress(
|
|
51
|
+
"0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8"
|
|
52
|
+
),
|
|
53
|
+
"OLAS_SERVICE_MANAGER": EthereumAddress("0x068a4f0946cF8c7f9C1B58a3b5243Ac8843bf473"),
|
|
54
|
+
# Legacy mech - used by TRADER staking contracts for liveness
|
|
55
|
+
"OLAS_MECH": EthereumAddress("0x77af31De935740567Cf4fF1986D04B2c964A786a"),
|
|
56
|
+
# NEW Marketplace (v2) - no staking support yet
|
|
57
|
+
"OLAS_MECH_MARKETPLACE": EthereumAddress("0x735FAAb1c4Ec41128c367AFb5c3baC73509f70bB"),
|
|
58
|
+
# OLD Marketplace (v1) - used by Pearl Beta Mech Marketplace staking
|
|
59
|
+
"OLAS_MECH_MARKETPLACE_OLD": EthereumAddress("0x4554fE75c1f5576c1d7F765B2A036c199Adae329"),
|
|
60
|
+
},
|
|
61
|
+
"ethereum": {
|
|
62
|
+
"OLAS_SERVICE_REGISTRY": EthereumAddress("0x48b6F34dDAf31f94086BFB45e69e0618DDe3677b"),
|
|
63
|
+
"OLAS_SERVICE_MANAGER": EthereumAddress("0x9C14948a39a9c1A58e3f94639908F0076FA715C6"),
|
|
64
|
+
},
|
|
65
|
+
"base": {
|
|
66
|
+
"OLAS_SERVICE_REGISTRY": EthereumAddress("0x3841C312061daB948332A78F042Ec61Ad09fc3D8"),
|
|
67
|
+
"OLAS_SERVICE_MANAGER": EthereumAddress("0xF36183B106692DeD8b6e3B2B7347C9665f8a09B1"),
|
|
68
|
+
"OLAS_MECH_MARKETPLACE": EthereumAddress("0x4554fE75c1f5576c1d7F765B2A036c199Adae329"),
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# TRADER-compatible staking contracts categorized by chain
|
|
73
|
+
# See https://govern.olas.network/contracts
|
|
74
|
+
# NOTE: All TRADER staking contracts use the LEGACY MECH for activity tracking.
|
|
75
|
+
# The activity checker calls agentMech.getRequestsCount(multisig), where agentMech
|
|
76
|
+
# is hardcoded to the legacy mech (0x77af31De935740567Cf4fF1986D04B2c964A786a).
|
|
77
|
+
#
|
|
78
|
+
# This means:
|
|
79
|
+
# - Legacy mech requests (use_marketplace=False) -> COUNT for liveness rewards
|
|
80
|
+
# - Marketplace mech requests (use_marketplace=True) -> DO NOT COUNT
|
|
81
|
+
#
|
|
82
|
+
# For staking rewards, services MUST use legacy mech requests.
|
|
83
|
+
OLAS_TRADER_STAKING_CONTRACTS: Dict[str, Dict[str, EthereumAddress]] = {
|
|
84
|
+
"gnosis": {
|
|
85
|
+
"Hobbyist 1 (100 OLAS)": EthereumAddress("0x389B46C259631Acd6a69Bde8B6cEe218230bAE8C"),
|
|
86
|
+
"Hobbyist 2 (500 OLAS)": EthereumAddress("0x238EB6993b90A978ec6AAD7530D6429c949C08DA"),
|
|
87
|
+
"Expert (1k OLAS)": EthereumAddress("0x5344B7DD311e5d3DdDd46A4f71481Bd7b05AAA3e"),
|
|
88
|
+
"Expert 2 (1k OLAS)": EthereumAddress("0xb964e44c126410df341ae04B13aB10A985fE3513"),
|
|
89
|
+
"Expert 3 (2k OLAS)": EthereumAddress("0x80faD33Cadb5F53f9D29F02Db97D682E8B101618"),
|
|
90
|
+
"Expert 4 (10k OLAS)": EthereumAddress("0xaD9d891134443B443D7F30013c7e14Fe27F2E029"),
|
|
91
|
+
"Expert 5 (10k OLAS)": EthereumAddress("0xE56dF1E563De1B10715cB313D514af350D207212"),
|
|
92
|
+
"Expert 6 (1k OLAS)": EthereumAddress("0x2546214aEE7eEa4bEE7689C81231017CA231Dc93"),
|
|
93
|
+
"Expert 7 (10k OLAS)": EthereumAddress("0xD7A3C8b975f71030135f1a66E9e23164d54fF455"),
|
|
94
|
+
"Expert 8 (2k OLAS)": EthereumAddress("0x356C108D49C5eebd21c84c04E9162de41933030c"),
|
|
95
|
+
"Expert 9 (10k OLAS)": EthereumAddress("0x17dBAe44BC5618Cc254055B386A29576b4F87015"),
|
|
96
|
+
"Expert 10 (10k OLAS)": EthereumAddress("0xB0ef657b8302bd2c74B6E6D9B2b4b39145b19c6f"),
|
|
97
|
+
"Expert 11 (10k OLAS)": EthereumAddress("0x3112c1613eAC3dBAE3D4E38CeF023eb9E2C91CF7"),
|
|
98
|
+
"Expert 12 (10k OLAS)": EthereumAddress("0xF4a75F476801B3fBB2e7093aCDcc3576593Cc1fc"),
|
|
99
|
+
"Expert 15 (10k OLAS)": EthereumAddress("0x88eB38FF79fBa8C19943C0e5Acfa67D5876AdCC1"),
|
|
100
|
+
"Expert 16 (10k OLAS)": EthereumAddress("0x6c65430515c70a3f5E62107CC301685B7D46f991"),
|
|
101
|
+
"Expert 17 (10k OLAS)": EthereumAddress("0x1430107A785C3A36a0C1FC0ee09B9631e2E72aFf"),
|
|
102
|
+
"Expert 18 (10k OLAS)": EthereumAddress("0x041e679d04Fc0D4f75Eb937Dea729Df09a58e454"),
|
|
103
|
+
},
|
|
104
|
+
"ethereum": {},
|
|
105
|
+
"base": {},
|
|
106
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Activity checker contract interaction.
|
|
2
|
+
|
|
3
|
+
The MechActivityChecker contract tracks liveness for staked services by monitoring:
|
|
4
|
+
- Safe multisig transaction nonces
|
|
5
|
+
- Mech request counts
|
|
6
|
+
|
|
7
|
+
The liveness check (isRatioPass) verifies that the service is making enough mech
|
|
8
|
+
requests relative to the time elapsed since the last checkpoint.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Tuple
|
|
12
|
+
|
|
13
|
+
from iwa.core.constants import DEFAULT_MECH_CONTRACT_ADDRESS
|
|
14
|
+
from iwa.core.types import EthereumAddress
|
|
15
|
+
from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH, ContractInstance
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ActivityCheckerContract(ContractInstance):
|
|
19
|
+
"""Class to interact with the MechActivityChecker contract.
|
|
20
|
+
|
|
21
|
+
This contract tracks mech request activity for staked services and determines
|
|
22
|
+
if they meet the liveness requirements for staking rewards.
|
|
23
|
+
|
|
24
|
+
The getMultisigNonces() function returns an array with two values:
|
|
25
|
+
- nonces[0]: Safe multisig nonce (total transaction count)
|
|
26
|
+
- nonces[1]: Mech requests count (from AgentMech.getRequestsCount)
|
|
27
|
+
|
|
28
|
+
The isRatioPass() function checks if:
|
|
29
|
+
1. diffRequestsCounts <= diffNonces (requests can't exceed txs)
|
|
30
|
+
2. ratio = (diffRequestsCounts * 1e18) / time >= livenessRatio
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
name = "activity_checker"
|
|
34
|
+
abi_path = OLAS_ABI_PATH / "activity_checker.json"
|
|
35
|
+
|
|
36
|
+
def __init__(self, address: EthereumAddress, chain_name: str = "gnosis"):
|
|
37
|
+
"""Initialize ActivityCheckerContract.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
address: The activity checker contract address.
|
|
41
|
+
chain_name: The chain name (default: gnosis).
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(address, chain_name=chain_name)
|
|
45
|
+
|
|
46
|
+
# Get the mech address this checker tracks
|
|
47
|
+
agent_mech_function = getattr(self.contract.functions, "agentMech", None)
|
|
48
|
+
self.agent_mech = (
|
|
49
|
+
agent_mech_function().call() if agent_mech_function else DEFAULT_MECH_CONTRACT_ADDRESS
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Get liveness ratio (requests per second * 1e18)
|
|
53
|
+
self.liveness_ratio = self.contract.functions.livenessRatio().call()
|
|
54
|
+
|
|
55
|
+
def get_multisig_nonces(self, multisig: EthereumAddress) -> Tuple[int, int]:
|
|
56
|
+
"""Get the nonces for a multisig address.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
multisig: The multisig address to check.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Tuple of (safe_nonce, mech_requests_count):
|
|
63
|
+
- safe_nonce: Total Safe transaction count
|
|
64
|
+
- mech_requests_count: Total mech requests made
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
nonces = self.contract.functions.getMultisigNonces(multisig).call()
|
|
68
|
+
return (nonces[0], nonces[1])
|
|
69
|
+
|
|
70
|
+
def is_ratio_pass(
|
|
71
|
+
self,
|
|
72
|
+
current_nonces: Tuple[int, int],
|
|
73
|
+
last_nonces: Tuple[int, int],
|
|
74
|
+
ts_diff: int,
|
|
75
|
+
) -> bool:
|
|
76
|
+
"""Check if the liveness ratio requirement is passed.
|
|
77
|
+
|
|
78
|
+
The formula checks:
|
|
79
|
+
1. diffRequestsCounts <= diffNonces (mech requests can't exceed total txs)
|
|
80
|
+
2. ratio = (diffRequestsCounts * 1e18) / ts_diff >= livenessRatio
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
current_nonces: Current (safe_nonce, mech_requests_count).
|
|
84
|
+
last_nonces: Nonces at last checkpoint (safe_nonce, mech_requests_count).
|
|
85
|
+
ts_diff: Time difference in seconds since last checkpoint.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
True if liveness requirements are met.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
return self.contract.functions.isRatioPass(
|
|
92
|
+
list(current_nonces), list(last_nonces), ts_diff
|
|
93
|
+
).call()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Mech contract interaction."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
from iwa.core.contracts.contract import ContractInstance
|
|
6
|
+
from iwa.core.types import EthereumAddress
|
|
7
|
+
from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MechContract(ContractInstance):
|
|
11
|
+
"""Class to interact with the Mech contract."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
address: EthereumAddress,
|
|
16
|
+
chain_name: str,
|
|
17
|
+
use_new_abi: bool = False,
|
|
18
|
+
):
|
|
19
|
+
"""Initialize the contract."""
|
|
20
|
+
self.use_new_abi = use_new_abi
|
|
21
|
+
if use_new_abi:
|
|
22
|
+
self.abi_path = OLAS_ABI_PATH / "mech_new.json"
|
|
23
|
+
else:
|
|
24
|
+
self.abi_path = OLAS_ABI_PATH / "mech.json"
|
|
25
|
+
super().__init__(address, chain_name)
|
|
26
|
+
|
|
27
|
+
def get_price(self) -> int:
|
|
28
|
+
"""Get the current price for a request."""
|
|
29
|
+
try:
|
|
30
|
+
return self.call("price")
|
|
31
|
+
except Exception:
|
|
32
|
+
# Fallback for new ABIs if price() is not there
|
|
33
|
+
return 10**16 # 0.01 xDAI
|
|
34
|
+
|
|
35
|
+
def prepare_request_tx(
|
|
36
|
+
self,
|
|
37
|
+
from_address: EthereumAddress,
|
|
38
|
+
data: bytes,
|
|
39
|
+
value: Optional[int] = None,
|
|
40
|
+
) -> Optional[Dict]:
|
|
41
|
+
"""Prepare a request transaction."""
|
|
42
|
+
if value is None:
|
|
43
|
+
value = self.get_price()
|
|
44
|
+
|
|
45
|
+
return self.prepare_transaction(
|
|
46
|
+
method_name="request",
|
|
47
|
+
method_kwargs={"data": data},
|
|
48
|
+
tx_params={"from": from_address, "value": value},
|
|
49
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Mech Marketplace contract interaction."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
from iwa.core.contracts.contract import ContractInstance
|
|
6
|
+
from iwa.core.types import EthereumAddress
|
|
7
|
+
from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MechMarketplaceContract(ContractInstance):
|
|
11
|
+
"""Class to interact with the Mech Marketplace contract."""
|
|
12
|
+
|
|
13
|
+
name = "mech_marketplace"
|
|
14
|
+
abi_path = OLAS_ABI_PATH / "mech_marketplace.json"
|
|
15
|
+
|
|
16
|
+
def prepare_request_tx(
|
|
17
|
+
self,
|
|
18
|
+
from_address: EthereumAddress,
|
|
19
|
+
request_data: bytes,
|
|
20
|
+
priority_mech: EthereumAddress,
|
|
21
|
+
response_timeout: int = 300,
|
|
22
|
+
max_delivery_rate: int = 10_000,
|
|
23
|
+
payment_type: bytes = b"\x00" * 32,
|
|
24
|
+
payment_data: bytes = b"",
|
|
25
|
+
value: int = 10_000_000_000_000_000, # Default 0.01 xDAI
|
|
26
|
+
) -> Optional[Dict]:
|
|
27
|
+
"""Prepare a marketplace request transaction.
|
|
28
|
+
|
|
29
|
+
Matches ABI:
|
|
30
|
+
request(bytes requestData, uint256 maxDeliveryRate, bytes32 paymentType, address priorityMech, uint256 responseTimeout, bytes paymentData)
|
|
31
|
+
"""
|
|
32
|
+
return self.prepare_transaction(
|
|
33
|
+
method_name="request",
|
|
34
|
+
method_kwargs={
|
|
35
|
+
"requestData": request_data,
|
|
36
|
+
"maxDeliveryRate": max_delivery_rate,
|
|
37
|
+
"paymentType": payment_type,
|
|
38
|
+
"priorityMech": priority_mech,
|
|
39
|
+
"responseTimeout": response_timeout,
|
|
40
|
+
"paymentData": payment_data,
|
|
41
|
+
},
|
|
42
|
+
tx_params={"from": from_address, "value": value},
|
|
43
|
+
)
|