iwa 0.0.1a6__py3-none-any.whl → 0.0.10__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.
- iwa/core/chain/interface.py +30 -23
- iwa/core/chain/models.py +21 -0
- iwa/core/contracts/contract.py +8 -2
- iwa/core/pricing.py +30 -21
- iwa/core/services/safe.py +13 -8
- iwa/core/services/transaction.py +15 -4
- iwa/core/utils.py +22 -0
- iwa/plugins/gnosis/safe.py +4 -3
- iwa/plugins/gnosis/tests/test_safe.py +9 -7
- iwa/plugins/olas/contracts/service.py +4 -4
- iwa/plugins/olas/contracts/staking.py +2 -3
- iwa/plugins/olas/plugin.py +14 -7
- iwa/plugins/olas/service_manager/lifecycle.py +109 -48
- iwa/plugins/olas/service_manager/mech.py +1 -1
- iwa/plugins/olas/service_manager/staking.py +92 -34
- iwa/plugins/olas/tests/test_plugin.py +6 -1
- iwa/plugins/olas/tests/test_plugin_full.py +12 -7
- iwa/plugins/olas/tests/test_service_manager_validation.py +16 -15
- iwa/tools/list_contracts.py +2 -2
- iwa/web/dependencies.py +1 -3
- iwa/web/routers/accounts.py +1 -2
- iwa/web/routers/olas/admin.py +1 -3
- iwa/web/routers/olas/funding.py +1 -3
- iwa/web/routers/olas/general.py +1 -3
- iwa/web/routers/olas/services.py +1 -2
- iwa/web/routers/olas/staking.py +19 -22
- iwa/web/routers/swap.py +1 -2
- iwa/web/routers/transactions.py +0 -2
- iwa/web/server.py +8 -6
- iwa/web/static/app.js +22 -0
- iwa/web/tests/test_web_endpoints.py +1 -1
- iwa/web/tests/test_web_olas.py +1 -1
- {iwa-0.0.1a6.dist-info → iwa-0.0.10.dist-info}/METADATA +1 -1
- {iwa-0.0.1a6.dist-info → iwa-0.0.10.dist-info}/RECORD +50 -49
- tests/test_chain_interface_coverage.py +3 -2
- tests/test_contract.py +165 -0
- tests/test_keys.py +2 -1
- tests/test_legacy_wallet.py +11 -0
- tests/test_pricing.py +32 -15
- tests/test_safe_coverage.py +3 -3
- tests/test_safe_service.py +3 -6
- tests/test_service_transaction.py +8 -3
- tests/test_staking_router.py +6 -3
- tests/test_transaction_service.py +4 -0
- tools/create_and_stake_service.py +103 -0
- tools/verify_drain.py +1 -4
- {iwa-0.0.1a6.dist-info → iwa-0.0.10.dist-info}/WHEEL +0 -0
- {iwa-0.0.1a6.dist-info → iwa-0.0.10.dist-info}/entry_points.txt +0 -0
- {iwa-0.0.1a6.dist-info → iwa-0.0.10.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.0.1a6.dist-info → iwa-0.0.10.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Integration tests for OlasPlugin."""
|
|
2
2
|
|
|
3
|
-
from unittest.mock import
|
|
3
|
+
from unittest.mock import patch
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
import typer
|
|
@@ -103,15 +103,18 @@ def test_import_services_cli_full(plugin, runner):
|
|
|
103
103
|
def test_get_safe_signers_edge_cases(plugin):
|
|
104
104
|
"""Test _get_safe_signers with various failure scenarios."""
|
|
105
105
|
# 1. No RPC configured
|
|
106
|
-
with patch("iwa.core.
|
|
107
|
-
|
|
106
|
+
with patch("iwa.core.chain.ChainInterfaces") as mock_ci_cls:
|
|
107
|
+
mock_ci = mock_ci_cls.return_value
|
|
108
|
+
mock_ci.get.return_value.chain.rpcs = []
|
|
108
109
|
signers, exists = plugin._get_safe_signers("0x1", "gnosis")
|
|
109
110
|
assert signers is None
|
|
110
111
|
assert exists is None
|
|
111
112
|
|
|
112
113
|
# 2. Safe doesn't exist (raises exception)
|
|
113
|
-
with patch("iwa.core.
|
|
114
|
-
|
|
114
|
+
with patch("iwa.core.chain.ChainInterfaces") as mock_ci_cls:
|
|
115
|
+
mock_ci = mock_ci_cls.return_value
|
|
116
|
+
mock_ci.get.return_value.chain.rpcs = ["http://rpc"]
|
|
117
|
+
mock_ci.get.return_value.chain.rpc = "http://rpc"
|
|
115
118
|
with patch("safe_eth.eth.EthereumClient"), patch("safe_eth.safe.Safe") as mock_safe_cls:
|
|
116
119
|
mock_safe = mock_safe_cls.return_value
|
|
117
120
|
mock_safe.retrieve_owners.side_effect = Exception("Generic error")
|
|
@@ -121,8 +124,10 @@ def test_get_safe_signers_edge_cases(plugin):
|
|
|
121
124
|
assert exists is False
|
|
122
125
|
|
|
123
126
|
# 3. Success path
|
|
124
|
-
with patch("iwa.core.
|
|
125
|
-
|
|
127
|
+
with patch("iwa.core.chain.ChainInterfaces") as mock_ci_cls:
|
|
128
|
+
mock_ci = mock_ci_cls.return_value
|
|
129
|
+
mock_ci.get.return_value.chain.rpcs = ["http://rpc"]
|
|
130
|
+
mock_ci.get.return_value.chain.rpc = "http://rpc"
|
|
126
131
|
with patch("safe_eth.eth.EthereumClient"), patch("safe_eth.safe.Safe") as mock_safe_cls:
|
|
127
132
|
mock_safe = mock_safe_cls.return_value
|
|
128
133
|
mock_safe.retrieve_owners.return_value = ["0xAgent"]
|
|
@@ -45,21 +45,22 @@ def test_drain_service_partial_failures(sm, mock_wallet):
|
|
|
45
45
|
# 2. Safe drain failure
|
|
46
46
|
# 3. Agent drain success
|
|
47
47
|
|
|
48
|
-
with patch
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
with patch("time.sleep"): # Avoid real delays in drain operations
|
|
49
|
+
with patch.object(sm, "claim_rewards", return_value=(True, 10**18)):
|
|
50
|
+
# Wallet.drain is called for Safe and Agent
|
|
51
|
+
def mock_drain(from_address_or_tag=None, to_address_or_tag=None, chain_name=None):
|
|
52
|
+
if from_address_or_tag == VALID_ADDR_2: # Safe
|
|
53
|
+
raise Exception("Safe drain failed")
|
|
54
|
+
return {"native": 0.5}
|
|
55
|
+
|
|
56
|
+
mock_wallet.drain.side_effect = mock_drain
|
|
57
|
+
|
|
58
|
+
result = sm.drain_service()
|
|
59
|
+
|
|
60
|
+
assert "safe" not in result
|
|
61
|
+
assert "agent" in result
|
|
62
|
+
assert result["agent"]["native"] == 0.5
|
|
63
|
+
# Verify it continued after Safe failure
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def test_unstake_failed_event_extraction(sm):
|
iwa/tools/list_contracts.py
CHANGED
|
@@ -11,8 +11,8 @@ from iwa.core.utils import configure_logger
|
|
|
11
11
|
from iwa.plugins.olas.constants import OLAS_TRADER_STAKING_CONTRACTS
|
|
12
12
|
from iwa.plugins.olas.contracts.staking import StakingContract
|
|
13
13
|
|
|
14
|
-
# Configure logger
|
|
15
|
-
|
|
14
|
+
# Configure logger and silence noisy third-party loggers
|
|
15
|
+
configure_logger()
|
|
16
16
|
logging.getLogger("web3").setLevel(logging.WARNING)
|
|
17
17
|
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
18
18
|
|
iwa/web/dependencies.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
"""Shared dependencies for Web API routers."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
3
|
import secrets
|
|
5
4
|
from typing import Optional
|
|
6
5
|
|
|
7
6
|
from fastapi import Header, HTTPException, Security
|
|
8
7
|
from fastapi.security import APIKeyHeader
|
|
8
|
+
from loguru import logger
|
|
9
9
|
|
|
10
10
|
from iwa.core.wallet import Wallet
|
|
11
11
|
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
12
|
# Singleton wallet instance for the web app
|
|
15
13
|
wallet = Wallet()
|
|
16
14
|
|
iwa/web/routers/accounts.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"""Accounts Router for Web API."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
3
|
import time
|
|
5
4
|
|
|
6
5
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
6
|
+
from loguru import logger
|
|
7
7
|
from slowapi import Limiter
|
|
8
8
|
from slowapi.util import get_remote_address
|
|
9
9
|
|
|
10
10
|
from iwa.web.dependencies import verify_auth, wallet
|
|
11
11
|
from iwa.web.models import AccountCreateRequest, SafeCreateRequest
|
|
12
12
|
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
13
|
router = APIRouter(prefix="/api/accounts", tags=["accounts"])
|
|
15
14
|
|
|
16
15
|
# Rate limiter for this router
|
iwa/web/routers/olas/admin.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Olas Admin Router."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
3
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
4
|
+
from loguru import logger
|
|
6
5
|
from slowapi import Limiter
|
|
7
6
|
from slowapi.util import get_remote_address
|
|
8
7
|
|
|
@@ -10,7 +9,6 @@ from iwa.core.models import Config
|
|
|
10
9
|
from iwa.plugins.olas.models import OlasConfig
|
|
11
10
|
from iwa.web.dependencies import verify_auth, wallet
|
|
12
11
|
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
12
|
router = APIRouter(tags=["olas"])
|
|
15
13
|
limiter = Limiter(key_func=get_remote_address)
|
|
16
14
|
|
iwa/web/routers/olas/funding.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Olas Funding Router."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
3
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
4
|
+
from loguru import logger
|
|
6
5
|
from pydantic import BaseModel, Field
|
|
7
6
|
from slowapi import Limiter
|
|
8
7
|
from slowapi.util import get_remote_address
|
|
@@ -11,7 +10,6 @@ from iwa.core.models import Config
|
|
|
11
10
|
from iwa.plugins.olas.models import OlasConfig
|
|
12
11
|
from iwa.web.dependencies import verify_auth, wallet
|
|
13
12
|
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
13
|
router = APIRouter(tags=["olas"])
|
|
16
14
|
limiter = Limiter(key_func=get_remote_address)
|
|
17
15
|
|
iwa/web/routers/olas/general.py
CHANGED
iwa/web/routers/olas/services.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"""Olas Services Router."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
3
|
from typing import Optional
|
|
5
4
|
|
|
6
5
|
from fastapi import APIRouter, Depends, HTTPException
|
|
6
|
+
from loguru import logger
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
9
|
from iwa.core.models import Config
|
|
10
10
|
from iwa.plugins.olas.models import OlasConfig
|
|
11
11
|
from iwa.web.dependencies import verify_auth, wallet
|
|
12
12
|
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
13
|
router = APIRouter(tags=["olas"])
|
|
15
14
|
|
|
16
15
|
|
iwa/web/routers/olas/staking.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Olas Staking Router."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
3
|
from typing import Optional
|
|
5
4
|
|
|
6
5
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
6
|
+
from loguru import logger
|
|
7
7
|
from slowapi import Limiter
|
|
8
8
|
from slowapi.util import get_remote_address
|
|
9
9
|
|
|
@@ -11,7 +11,6 @@ from iwa.core.models import Config
|
|
|
11
11
|
from iwa.plugins.olas.models import OlasConfig
|
|
12
12
|
from iwa.web.dependencies import get_config, verify_auth, wallet
|
|
13
13
|
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
14
|
router = APIRouter(tags=["olas"])
|
|
16
15
|
limiter = Limiter(key_func=get_remote_address)
|
|
17
16
|
|
|
@@ -34,25 +33,18 @@ def get_staking_contracts(
|
|
|
34
33
|
raise HTTPException(status_code=400, detail="Invalid chain name")
|
|
35
34
|
|
|
36
35
|
try:
|
|
37
|
-
import
|
|
38
|
-
|
|
39
|
-
from iwa.core.chain import ChainInterface
|
|
36
|
+
from iwa.core.chain import ChainInterfaces
|
|
40
37
|
from iwa.plugins.olas.constants import OLAS_TRADER_STAKING_CONTRACTS
|
|
41
|
-
from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH
|
|
42
38
|
|
|
43
39
|
contracts = OLAS_TRADER_STAKING_CONTRACTS.get(chain, {})
|
|
44
40
|
|
|
45
41
|
# Get service bond and token if filtered
|
|
46
42
|
service_bond, service_token = _get_service_filter_info(service_key)
|
|
47
43
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
abi = json.load(f)
|
|
51
|
-
|
|
52
|
-
# Get correct web3 instance
|
|
53
|
-
w3 = ChainInterface(chain).web3
|
|
44
|
+
# Get correct interface from singleton manager
|
|
45
|
+
interface = ChainInterfaces().get(chain)
|
|
54
46
|
|
|
55
|
-
results = _fetch_all_contracts(contracts,
|
|
47
|
+
results = _fetch_all_contracts(contracts, interface)
|
|
56
48
|
filtered_results = _filter_contracts(results, service_bond, service_token)
|
|
57
49
|
|
|
58
50
|
# Return with filter metadata so frontend can explain filtering
|
|
@@ -109,14 +101,20 @@ def _get_service_filter_info(service_key: Optional[str]) -> tuple[Optional[int],
|
|
|
109
101
|
return service_bond, service_token
|
|
110
102
|
|
|
111
103
|
|
|
112
|
-
def _check_availability(name, address,
|
|
104
|
+
def _check_availability(name, address, interface):
|
|
113
105
|
"""Check availability of a single staking contract."""
|
|
106
|
+
# Import at module level would cause circular import, but we can cache it
|
|
107
|
+
# The import is cached by Python after first call so this is efficient
|
|
108
|
+
from iwa.plugins.olas.contracts.staking import StakingContract
|
|
109
|
+
|
|
114
110
|
try:
|
|
115
|
-
contract =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
contract = StakingContract(address, chain_name=interface.chain.name)
|
|
112
|
+
|
|
113
|
+
# StakingContract uses .call() which handles with_retry and rotation
|
|
114
|
+
service_ids = contract.call("getServiceIds")
|
|
115
|
+
max_services = contract.call("maxNumServices")
|
|
116
|
+
min_deposit = contract.call("minStakingDeposit")
|
|
117
|
+
staking_token = contract.call("stakingToken")
|
|
120
118
|
used = len(service_ids)
|
|
121
119
|
|
|
122
120
|
return {
|
|
@@ -141,15 +139,14 @@ def _check_availability(name, address, w3, abi):
|
|
|
141
139
|
}
|
|
142
140
|
|
|
143
141
|
|
|
144
|
-
def _fetch_all_contracts(contracts: dict,
|
|
142
|
+
def _fetch_all_contracts(contracts: dict, interface) -> list:
|
|
145
143
|
"""Fetch availability for all contracts using threads."""
|
|
146
144
|
from concurrent.futures import ThreadPoolExecutor
|
|
147
145
|
|
|
148
146
|
results = []
|
|
149
147
|
with ThreadPoolExecutor(max_workers=10) as executor:
|
|
150
|
-
# Pass w3 and abi to the helper
|
|
151
148
|
futures = [
|
|
152
|
-
executor.submit(_check_availability, name, addr,
|
|
149
|
+
executor.submit(_check_availability, name, addr, interface)
|
|
153
150
|
for name, addr in contracts.items()
|
|
154
151
|
]
|
|
155
152
|
for future in futures:
|
iwa/web/routers/swap.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Swap Router for Web API."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import logging
|
|
5
4
|
from concurrent.futures import ThreadPoolExecutor
|
|
6
5
|
from functools import lru_cache
|
|
7
6
|
from typing import Any, Optional
|
|
8
7
|
|
|
9
8
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
9
|
+
from loguru import logger
|
|
10
10
|
from pydantic import BaseModel, Field, field_validator
|
|
11
11
|
from slowapi import Limiter
|
|
12
12
|
from slowapi.util import get_remote_address
|
|
@@ -16,7 +16,6 @@ from iwa.core.chain import ChainInterfaces, SupportedChain
|
|
|
16
16
|
from iwa.plugins.gnosis.cow import CowSwap
|
|
17
17
|
from iwa.web.dependencies import verify_auth, wallet
|
|
18
18
|
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
19
|
router = APIRouter(prefix="/api/swap", tags=["swap"])
|
|
21
20
|
|
|
22
21
|
limiter = Limiter(key_func=get_remote_address)
|
iwa/web/routers/transactions.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
4
|
import json
|
|
5
|
-
import logging
|
|
6
5
|
|
|
7
6
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
8
7
|
from pydantic import BaseModel, Field, field_validator
|
|
@@ -13,7 +12,6 @@ from web3 import Web3
|
|
|
13
12
|
from iwa.core.db import SentTransaction
|
|
14
13
|
from iwa.web.dependencies import verify_auth, wallet
|
|
15
14
|
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
15
|
router = APIRouter(prefix="/api", tags=["transactions"])
|
|
18
16
|
|
|
19
17
|
# Rate limiter for this router
|
iwa/web/server.py
CHANGED
|
@@ -9,11 +9,13 @@ from fastapi import FastAPI, Request
|
|
|
9
9
|
from fastapi.middleware.cors import CORSMiddleware
|
|
10
10
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
11
11
|
from fastapi.staticfiles import StaticFiles
|
|
12
|
+
from loguru import logger
|
|
12
13
|
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
13
14
|
from slowapi.errors import RateLimitExceeded
|
|
14
15
|
from slowapi.util import get_remote_address
|
|
15
16
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
16
17
|
|
|
18
|
+
from iwa.core.utils import configure_logger
|
|
17
19
|
from iwa.core.wallet import init_db
|
|
18
20
|
|
|
19
21
|
# Pre-load cowdao_cowpy modules BEFORE async loop starts
|
|
@@ -21,15 +23,15 @@ from iwa.core.wallet import init_db
|
|
|
21
23
|
# which fails if called from an already running event loop
|
|
22
24
|
from iwa.plugins.gnosis.cow_utils import get_cowpy_module
|
|
23
25
|
|
|
24
|
-
get_cowpy_module("DEFAULT_APP_DATA_HASH") # Forces import now, not during async
|
|
25
|
-
|
|
26
|
-
# Import dependencies to ensure initialization
|
|
27
26
|
# Import routers
|
|
28
|
-
from iwa.web.routers import accounts, olas, state, swap, transactions
|
|
27
|
+
from iwa.web.routers import accounts, olas, state, swap, transactions
|
|
28
|
+
|
|
29
|
+
get_cowpy_module("DEFAULT_APP_DATA_HASH") # Forces import now, not during async
|
|
29
30
|
|
|
30
|
-
# Configure logging
|
|
31
|
+
# Configure logging (writes to iwa.log for frontend visibility)
|
|
32
|
+
configure_logger()
|
|
33
|
+
# Initialize standard logging for third-party libs (silenced by configure_logger but needed for basics)
|
|
31
34
|
logging.basicConfig(level=logging.INFO)
|
|
32
|
-
logger = logging.getLogger(__name__)
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
# Rate limiter (in-memory storage, resets on restart)
|
iwa/web/static/app.js
CHANGED
|
@@ -2564,6 +2564,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2564
2564
|
// Show spinner, hide select, disable button
|
|
2565
2565
|
select.style.display = "none";
|
|
2566
2566
|
spinnerDiv.style.display = "block";
|
|
2567
|
+
spinnerDiv.innerHTML =
|
|
2568
|
+
'<span class="loading-spinner"></span> Loading contracts...';
|
|
2567
2569
|
confirmBtn.disabled = true;
|
|
2568
2570
|
modal.classList.add("active");
|
|
2569
2571
|
|
|
@@ -2689,6 +2691,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2689
2691
|
// Load staking contracts
|
|
2690
2692
|
contractSelect.style.display = "none";
|
|
2691
2693
|
spinnerDiv.style.display = "block";
|
|
2694
|
+
spinnerDiv.innerHTML =
|
|
2695
|
+
'<span class="loading-spinner"></span> Loading contracts...';
|
|
2692
2696
|
submitBtn.disabled = true;
|
|
2693
2697
|
|
|
2694
2698
|
try {
|
|
@@ -2877,22 +2881,40 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
2877
2881
|
'button[type="submit"]',
|
|
2878
2882
|
);
|
|
2879
2883
|
contractSelect.style.display = "none";
|
|
2884
|
+
|
|
2885
|
+
// Remove hidden class to ensure visibility (overrides CSS !important)
|
|
2886
|
+
spinnerDiv.classList.remove("hidden");
|
|
2880
2887
|
spinnerDiv.style.display = "block";
|
|
2888
|
+
spinnerDiv.innerHTML =
|
|
2889
|
+
'<span class="loading-spinner"></span> Loading contracts...';
|
|
2890
|
+
|
|
2881
2891
|
submitBtn.disabled = true;
|
|
2892
|
+
|
|
2882
2893
|
authFetch("/api/olas/staking-contracts?chain=gnosis")
|
|
2883
2894
|
.then((resp) => resp.json())
|
|
2884
2895
|
.then((contracts) => {
|
|
2885
2896
|
state.stakingContractsCache = contracts;
|
|
2886
2897
|
contractSelect.innerHTML = renderContractOptions(contracts);
|
|
2898
|
+
|
|
2887
2899
|
contractSelect.style.display = "";
|
|
2900
|
+
contractSelect.classList.remove("hidden");
|
|
2901
|
+
|
|
2902
|
+
// Hide spinner
|
|
2888
2903
|
spinnerDiv.style.display = "none";
|
|
2904
|
+
spinnerDiv.classList.add("hidden");
|
|
2905
|
+
|
|
2889
2906
|
submitBtn.disabled = false;
|
|
2890
2907
|
})
|
|
2891
2908
|
.catch(() => {
|
|
2892
2909
|
contractSelect.innerHTML =
|
|
2893
2910
|
'<option value="">None (don\'t stake)</option>';
|
|
2894
2911
|
contractSelect.style.display = "";
|
|
2912
|
+
contractSelect.classList.remove("hidden");
|
|
2913
|
+
|
|
2914
|
+
// Hide spinner even on error
|
|
2895
2915
|
spinnerDiv.style.display = "none";
|
|
2916
|
+
spinnerDiv.classList.add("hidden");
|
|
2917
|
+
|
|
2896
2918
|
submitBtn.disabled = false;
|
|
2897
2919
|
});
|
|
2898
2920
|
}
|
|
@@ -26,7 +26,7 @@ async def override_verify_auth():
|
|
|
26
26
|
app.dependency_overrides[verify_auth] = override_verify_auth
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
@pytest.fixture
|
|
29
|
+
@pytest.fixture(scope="module")
|
|
30
30
|
def client():
|
|
31
31
|
"""TestClient for FastAPI app."""
|
|
32
32
|
return TestClient(app, raise_server_exceptions=False)
|
iwa/web/tests/test_web_olas.py
CHANGED