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
1 change: 1 addition & 0 deletions Dockerfile.agent
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*

COPY pyproject.toml README.md uv.lock* ./
COPY .git ./.git

# Sync dependencies using uv
RUN uv sync --frozen --no-install-project || uv sync --no-install-project
Expand Down
21 changes: 18 additions & 3 deletions docker-compose.agent.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
services:
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --appendfsync everysec
volumes:
- redis-data:/data
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 5s
timeout: 3s
retries: 5

omniclaw-agent:
build:
context: .
dockerfile: Dockerfile.agent
command: uv run omniclaw server --host 0.0.0.0 --port 8080
env_file: .env
environment:
- OMNICLAW_REDIS_URL=redis://host.docker.internal:6379/0
- OMNICLAW_REDIS_URL=redis://redis:6379/0
- OMNICLAW_AGENT_POLICY_PATH=/config/policy.json
- OMNICLAW_AGENT_TOKEN=payment-agent-token
- OMNICLAW_LOG_LEVEL=INFO
volumes:
- ./examples/agent/policy.json:/config/policy.json:ro
ports:
- "8080:8080"
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
redis:
condition: service_healthy

volumes:
redis-data:
10 changes: 5 additions & 5 deletions examples/agent/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"version": "1.0",
"tokens": {
"payment-agent-token": {
"wallet_alias": "payment-agent",
"wallet_alias": "omni-bot-v4",
"active": true,
"label": "Main Payment Agent"
"label": "Main Omni Bot"
},
"api-agent-token": {
"wallet_alias": "api-agent",
Expand All @@ -18,9 +18,9 @@
}
},
"wallets": {
"payment-agent": {
"name": "Main Payment Agent",
"description": "Primary agent for general payments",
"omni-bot-v4": {
"name": "Omni Bot V1",
"description": "Upgraded autonomous bot",
"limits": {
"daily_max": "100.00",
"hourly_max": "50.00",
Expand Down
11 changes: 6 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,9 @@ warn_unused_ignores = true
[tool.ruff.lint.per-file-ignores]
# nanopayments __init__ needs specific import order to avoid circular imports
"src/omniclaw/protocols/nanopayments/__init__.py" = ["I001"]
"src/omniclaw/cli.py" = ["I001", "E402"]
# warnings.filterwarnings() must run before other imports
"src/omniclaw/agent/server.py" = ["E402"]
"src/omniclaw/cli_agent.py" = ["E402", "B904"]
"src/omniclaw/onboarding.py" = ["E402"]
"src/omniclaw/cli.py" = ["I001", "F401", "E402"]
"src/omniclaw/__init__.py" = ["E402", "F401", "I001"]
"src/omniclaw/agent/server.py" = ["E402", "F401"]
"src/omniclaw/agent/routes.py" = ["B008", "UP037", "E402"]
"src/omniclaw/cli_agent.py" = ["B008", "E402", "I001"]
"src/omniclaw/onboarding.py" = ["E402", "I001", "F401"]
58 changes: 58 additions & 0 deletions scripts/x402_simple_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Simple x402 Facilitator Mock Server.
Implements the x402 protocol (402 Payment Required) for testing.
"""

import uvicorn
from fastapi import FastAPI, Header, HTTPException, Request, Response
from fastapi.responses import JSONResponse
from decimal import Decimal
import uuid
import time

app = FastAPI()

# In-memory store for paid requests (just for testing idempotency logic)
PAID_REQUESTS = {}

@app.get("/weather")
async def get_weather(request: Request, authorization: str = Header(None)):
if not authorization or not authorization.startswith("x402 "):
return Response(
status_code=402,
headers={
"WWW-Authenticate": 'x402 payment_url="http://localhost:8000/x402/facilitator", invoice_id="' + str(uuid.uuid4()) + '"',
"x402-amount": "1000",
"x402-token": "USDC",
},
content="Payment Required",
)
return {"weather": "sunny", "temperature": 25}

@app.get("/premium-content")
async def get_premium(request: Request, authorization: str = Header(None)):
if not authorization or not authorization.startswith("x402 "):
return Response(
status_code=402,
headers={
"WWW-Authenticate": 'x402 payment_url="http://localhost:8000/x402/facilitator", invoice_id="' + str(uuid.uuid4()) + '"',
"x402-amount": "10000",
"x402-token": "USDC",
},
content="Payment Required",
)
return {"content": "Ultra secret data πŸ’Ž"}

@app.post("/x402/facilitator")
async def facilitator(request: Request):
data = await request.json()
# Mock successful settlement
return {
"status": "success",
"transaction_id": f"mock_tx_{uuid.uuid4().hex[:8]}",
"settled_at": int(time.time()),
"facilitator_sig": "mock_signature_0x123",
}

if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
9 changes: 8 additions & 1 deletion src/omniclaw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
... )
"""

import warnings

# Suppress noisy deprecation warnings from downstream dependencies (e.g. web3, circle-sdk)
# We do this at the very top of the package to ensure it catches warnings during imports.
warnings.filterwarnings("ignore", message=".*pkg_resources is deprecated.*")
warnings.filterwarnings("ignore", category=DeprecationWarning, module="pkg_resources")

from omniclaw.client import OmniClaw
from omniclaw.core.config import Config
from omniclaw.core.exceptions import (
Expand Down Expand Up @@ -140,7 +147,7 @@
)
from omniclaw.trust.gate import TrustGate

__version__ = "0.0.1"
__version__ = "0.1.0"
__all__ = [
# Main Client
"OmniClaw",
Expand Down
9 changes: 3 additions & 6 deletions src/omniclaw/agent/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,12 @@ async def authenticate(
self,
credentials: HTTPAuthorizationCredentials,
) -> AuthenticatedAgent:
"""Authenticate request using token."""
"""Authenticate request using token against the policy mapping."""
token = credentials.credentials

if not self._agent_token or token != self._agent_token:
raise HTTPException(status_code=401, detail="Invalid token")

wallet_id = self._policy.get_wallet_id()
wallet_id = self._policy.get_wallet_id_for_token(token)
if not wallet_id:
raise HTTPException(status_code=400, detail="Wallet not initialized")
raise HTTPException(status_code=401, detail="Invalid or unauthorized token")

return AuthenticatedAgent(
token=token,
Expand Down
19 changes: 19 additions & 0 deletions src/omniclaw/agent/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,22 @@ class HealthResponse(BaseModel):

status: str
version: str = "1.0.0"


class X402PayRequest(BaseModel):
"""X402 Payment request."""

url: str = Field(..., description="x402 Service URL")
method: str = Field("GET", description="HTTP method")
body: str | None = Field(None, description="Request body")
headers: dict[str, str] | None = Field(None, description="Request headers")
idempotency_key: str | None = Field(None, description="Idempotency key")


class X402VerifyRequest(BaseModel):
"""X402 Verification request."""

signature: str = Field(..., description="Payment signature/proof")
amount: str = Field(..., description="Amount paid")
sender: str = Field(..., description="Sender address")
resource: str = Field(..., description="Resource URL")
Loading
Loading