Skip to content
Merged
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
21 changes: 16 additions & 5 deletions src/omniclaw/agent/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,22 @@ async def _choose_x402_route(
gateway_available_balance = balance.formatted_available
required_atomic = int(selected_gateway_kind.amount_atomic)
gateway_ready = balance.available >= required_atomic
gateway_reason = (
"Gateway balance is sufficient for GatewayWalletBatched"
if gateway_ready
else "Gateway balance is below the required amount"
)
if gateway_ready:
gateway_reason = "Gateway balance is sufficient for GatewayWalletBatched"
else:
# Fallback to direct on-chain balance when API-reported balance is stale/lagging.
try:
onchain_balance = await client.get_gateway_onchain_balance(wallet_id)
if onchain_balance.available >= required_atomic:
gateway_available_balance = onchain_balance.formatted_available
gateway_ready = True
gateway_reason = (
"Gateway on-chain balance is sufficient (API balance appears stale)"
)
else:
gateway_reason = "Gateway balance is below the required amount"
except Exception:
gateway_reason = "Gateway balance is below the required amount"
except Exception as exc:
gateway_ready = False
gateway_reason = f"Gateway balance check failed: {exc}"
Expand Down
58 changes: 58 additions & 0 deletions tests/test_x402_sdk_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,12 @@ async def test_choose_x402_route_prefers_exact_when_gateway_is_unfunded():
formatted_available="0.00",
)
),
get_gateway_onchain_balance=AsyncMock(
return_value=SimpleNamespace(
available=0,
formatted_available="0.00",
)
),
)
x402_adapter = SimpleNamespace(
_resolve_agent_network=lambda wallet_id, destination_chain: "eip155:84532"
Expand Down Expand Up @@ -385,6 +391,12 @@ async def test_choose_x402_route_keeps_gateway_when_it_is_the_only_option():
formatted_available="0.00",
)
),
get_gateway_onchain_balance=AsyncMock(
return_value=SimpleNamespace(
available=0,
formatted_available="0.00",
)
),
)
x402_adapter = SimpleNamespace(
_resolve_agent_network=lambda wallet_id, destination_chain: "eip155:84532"
Expand All @@ -403,6 +415,52 @@ async def test_choose_x402_route_keeps_gateway_when_it_is_the_only_option():
assert route["gateway_ready"] is False


@pytest.mark.asyncio
async def test_choose_x402_route_uses_onchain_fallback_when_api_balance_is_stale():
gateway_kind = SimpleNamespace(
amount_atomic=250000,
is_gateway_batched=True,
get_amount_usdc=lambda: Decimal("0.25"),
)
requirements = SimpleNamespace(
select_preferred_kind=lambda *, prefer_gateway, source_network: (
gateway_kind if prefer_gateway else None
)
)
client = SimpleNamespace(
_nano_adapter=object(),
get_gateway_balance=AsyncMock(
return_value=SimpleNamespace(
available=0,
formatted_available="0.00",
)
),
get_gateway_onchain_balance=AsyncMock(
return_value=SimpleNamespace(
available=300000,
formatted_available="0.30",
)
),
)
x402_adapter = SimpleNamespace(
_resolve_agent_network=lambda wallet_id, destination_chain: "eip155:84532"
)

route = await _choose_x402_route(
client=client,
wallet_id="buyer-wallet",
x402_adapter=x402_adapter,
requirements=requirements,
)

assert route["selected_route"] == "nanopayment"
assert route["payment_source"] == "gateway_balance"
assert route["selected_kind"] is gateway_kind
assert route["gateway_ready"] is True
assert route["gateway_available_balance"] == "0.30"
assert route["gateway_reason"] == "Gateway on-chain balance is sufficient (API balance appears stale)"


@pytest.mark.asyncio
async def test_pay_route_inspects_url_even_when_amount_is_supplied(
monkeypatch: pytest.MonkeyPatch,
Expand Down
Loading