Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions packages/valory/connections/polymarket_client/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"international",
]
MARKETS_LIMIT = 300
EVENTS_LIMIT = 200
EVENTS_LIMIT = 200`nMAX_PAGES = 100 # Safety cap on the keyset cursor loop (issue #936)
MARKETS_TIME_WINDOW_DAYS = 4
API_REQUEST_TIMEOUT = 10
MAX_API_RETRIES = 3
Expand Down Expand Up @@ -858,7 +858,7 @@ def _fetch_markets_by_tag_slug(
after_cursor: Optional[str] = None
all_markets: list = []

while True:
for _ in range(MAX_PAGES):
params: Dict[str, Any] = {
"tag_slug": tag_slug,
"end_date_max": end_date_max,
Expand All @@ -875,6 +875,18 @@ def _fetch_markets_by_tag_slug(
if error:
return None, error

# _request_with_retries can return a non-dict payload (JSON null, a
# stray list, or a string from a misbehaving edge proxy). Calling
# .get() on it would raise AttributeError and bubble up to
# fetch_markets' blanket except Exception, dropping the whole
# category. Degrade gracefully instead. (See issue 936.)
if not isinstance(response, dict):
self.logger.warning(
f"fetch_markets: non-dict response from keyset API "
f"(type={type(response).__name__}); breaking pagination"
)
break

events_data = response.get("events") or []

markets_this_page = 0
Expand All @@ -895,6 +907,14 @@ def _fetch_markets_by_tag_slug(
after_cursor = response.get("next_cursor")
if not after_cursor:
break
else:
# Hit MAX_PAGES without exhausting next_cursor -- upstream may be
# returning the same cursor (server bug, cache poisoning, partial
# outage). Warn so the next refresh can re-attempt. (See issue 936.)
self.logger.warning(
f"fetch_markets: hit MAX_PAGES={MAX_PAGES} cap with non-empty "
f"next_cursor; truncating market list at {len(all_markets)}"
)

return all_markets, None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ def _update_policy_for_redeemable_positions(

:param redeemable_positions: list of position dicts from the Polymarket API.
"""
# See issue #970: degrade gracefully on non-list payload.
if not isinstance(redeemable_positions, list):
self.context.logger.error(
f"Expected list of redeemable positions, got "
f"{type(redeemable_positions).__name__}: {redeemable_positions!r}"
)
return

for position in redeemable_positions:
condition_id = position.get("conditionId")
if condition_id is None:
Expand Down Expand Up @@ -262,8 +270,23 @@ def _redeem_via_builder(
current_utilized_tools: Optional[str] = None,
) -> Generator:
"""Redeem positions via builder flow (connection request)."""
# See issue #970: degrade gracefully on non-list payload.
if not isinstance(redeemable_positions, list):
self.context.logger.error(
f"Expected list of redeemable positions, got "
f"{type(redeemable_positions).__name__}: {redeemable_positions!r}"
)
return None

# Redeem each position
# See issue #970: degrade gracefully on non-list payload.
if not isinstance(redeemable_positions, list):
self.context.logger.error(
f"Expected list of redeemable positions, got "
f"{type(redeemable_positions).__name__}: {redeemable_positions!r}"
)
return None

for position in redeemable_positions:
condition_id = position.get("conditionId")
outcome_index = position.get("outcomeIndex")
Expand Down Expand Up @@ -312,6 +335,14 @@ def _prepare_redeem_tx(
return ""

# Build redemption transactions and add to multisend_batches
# See issue #970: degrade gracefully on non-list payload.
if not isinstance(redeemable_positions, list):
self.context.logger.error(
f"Expected list of redeemable positions, got "
f"{type(redeemable_positions).__name__}: {redeemable_positions!r}"
)
return ""

for position in redeemable_positions:
condition_id = position.get("conditionId")
outcome_index = position.get("outcomeIndex")
Expand Down
11 changes: 11 additions & 0 deletions packages/valory/skills/market_manager_abci/behaviours/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ def send_polymarket_connection_request(
self.context.logger.warning("No response from the Polymarket connection.")
return None

# _request_with_retries can return a non-dict payload (JSON null, a
# list, or a string from a misbehaving edge proxy). Calling
# .get() on it would raise AttributeError and bubble up to
# fetch_markets' blanket except Exception, dropping the whole
# category. Degrade gracefully instead. (See issue #970.)
if response.error:
self.context.logger.warning(
f"Polymarket connection error: {response.error}"
)
return None

response_json = json.loads(response.payload)

return response_json
Expand Down