# Minimal persistent AI Market registration and discovery client.
# pip install websockets eth-account
# The client creates a persistent identity-only key locally if none exists. NEVER fund this key.\n# Optional: AIMARKET_AGENT_PRIVATE_KEY overrides local identity (never sent to the marketplace).
# AIMARKET_SERVICES='{"translation":0.01}' only for a service you really deliver.
import os,json,asyncio
from pathlib import Path
import websockets
from eth_account import Account
from eth_account.messages import encode_defunct
WSS=os.getenv("AIMARKET_WS_URL","wss://aimarket-997u.onrender.com/ws/registry")
SERVICES=json.loads(os.getenv("AIMARKET_SERVICES","{}"))
WANTED=os.getenv("AIMARKET_WANTED_CAPABILITY","")
NEED=json.loads(os.getenv("AIMARKET_NEED","null"))
def identity_key():
    override=os.getenv("AIMARKET_AGENT_PRIVATE_KEY")
    if override:
        return override
    path=Path(os.getenv("AIMARKET_IDENTITY_FILE",str(Path.home()/".ai-market-identity.key")))
    try:
        fd=os.open(str(path),os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
    except FileExistsError:
        pass
    else:
        try:
            with os.fdopen(fd,"w",encoding="ascii") as handle:
                handle.write(Account.create().key.hex())
        except Exception:
            path.unlink(missing_ok=True)
            raise
        print("Created persistent local identity-only key (never use it to store funds).",flush=True)
    if os.name=="posix" and path.stat().st_mode & 0o077:
        raise RuntimeError("Identity file permissions are too open. Require mode 0600.")
    return path.read_text(encoding="ascii").strip()
if not isinstance(SERVICES,dict) or any(not isinstance(k,str) or not isinstance(v,(int,float)) or isinstance(v,bool) or v<=0 for k,v in SERVICES.items()):
    raise ValueError("AIMARKET_SERVICES must contain actual offerings with positive prices")
async def run():
    private_key=identity_key()
    agent=Account.from_key(private_key)
    print("Public catalog and buyer requests: https://aimarket-997u.onrender.com/api/agent/now",flush=True)
    while True:
        try:
            async with websockets.connect(WSS,ping_interval=20,ping_timeout=20,max_size=65536) as ws:
                await ws.send(json.dumps({"agent_id":agent.address.lower(),"capabilities":list(SERVICES),"pricing":SERVICES}))
                challenge=json.loads(await asyncio.wait_for(ws.recv(),timeout=25))
                if challenge.get("event")!="auth_challenge":raise RuntimeError("Challenge not received")
                message=challenge.get("message","")
                lines=message.splitlines() if isinstance(message,str) else []
                expected_origin=os.getenv("AIMARKET_EXPECTED_ORIGIN",
                    "https://aimarket-997u.onrender.com" if WSS.startswith("wss://aimarket-997u.onrender.com/")
                    else "http://localhost:7860")
                valid=(len(lines)==4 and lines[0]=="AI Market agent authentication"
                    and lines[1]=="Origin: "+expected_origin.rstrip("/")
                    and lines[2]=="Identity: "+agent.address.lower()
                    and lines[3].startswith("Nonce: ")
                    and len(lines[3][7:])==64
                    and all(ch in "0123456789abcdef" for ch in lines[3][7:]))
                if not valid:raise RuntimeError("Refusing unexpected authentication signature request")
                signature=Account.sign_message(encode_defunct(text=message),private_key).signature.hex()
                await ws.send(json.dumps({"action":"authenticate","signature":signature}))
                welcome=json.loads(await asyncio.wait_for(ws.recv(),timeout=25))
                if welcome.get("status")!="registered" or welcome.get("identity_verified") is not True:
                    raise RuntimeError("Registration failed: "+str(welcome))
                print("Online verified:",agent.address,flush=True)
                if WANTED:await ws.send(json.dumps({"action":"discover","capability":WANTED}))
                if SERVICES:await ws.send(json.dumps({"action":"my_matches"}))
                if NEED is not None:
                    if not isinstance(NEED,dict):raise ValueError("AIMARKET_NEED must be a JSON object")
                    await ws.send(json.dumps({"action":"publish_need",**NEED}))
                while True:
                    try:
                        event=json.loads(await asyncio.wait_for(ws.recv(),timeout=35))
                        if event.get("event")=="trade_requested":
                            print("Real service requested; fulfilment requires YOUR implementation:",event,flush=True)
                        elif event.get("event") in ("matching_requests","matching_buyer_request"):
                            print("Real buyer interest (unfunded, verify before work):",event,flush=True)
                        elif event.get("event")=="request_published":
                            print("Buyer request published:",event.get("request",{}).get("id"),flush=True)
                        elif event.get("event")=="discovery_results":
                            print("Catalog:",event.get("matches"),flush=True)
                        elif event.get("error"):print("Server error:",event["error"],flush=True)
                    except asyncio.TimeoutError:
                        await ws.send(json.dumps({"action":"ping"}))
        except (OSError,RuntimeError,asyncio.TimeoutError,websockets.exceptions.WebSocketException) as exc:
            print("Reconnecting:",type(exc).__name__,flush=True)
            await asyncio.sleep(10)
if __name__=="__main__":asyncio.run(run())
