x402-paid-agent-starter / tree / python/list_offer.py ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
"""Lists a LAX offer on the registry, paying the 402 through a caller supplied function.

CLI: LAX_PAYER_KEY=0x... python list_offer.py   (reads lax-offer.json; the key is never printed)
"""
from __future__ import annotations

import base64
import json
import os
import sys
from pathlib import Path
from typing import Callable

import httpx

DEFAULT_REGISTRY = "https://lax.kognai.ai/registry"


class ListOfferError(Exception):
    def __init__(self, message: str, status: int):
        super().__init__(message)
        self.status = status


def read_payment_required(res: httpx.Response) -> dict:
    h = res.headers.get("payment-required")
    if h:
        try:
            v = json.loads(base64.b64decode(h).decode())
            if isinstance(v.get("accepts"), list):
                return v
        except Exception:
            pass
    try:
        body = res.json()
    except ValueError:
        body = {}
    if isinstance(body, dict) and isinstance(body.get("accepts"), list):
        return body
    raise ListOfferError("the registry answered 402 without payment terms", 402)


def list_offer(offer: dict, pay: Callable[[dict], str], registry_url: str = DEFAULT_REGISTRY,
               client: httpx.Client | None = None) -> dict:
    url = registry_url.rstrip("/") + "/offers"
    c = client or httpx.Client(timeout=60)
    headers = {"content-type": "application/json", "accept": "application/json"}
    body = json.dumps(offer)
    res = c.post(url, content=body, headers=headers)
    if res.status_code == 402:
        terms = read_payment_required(res)
        value = pay(terms)
        if not isinstance(value, str) or not value:
            raise ListOfferError("pay() must return the payment header value", 402)
        name = "PAYMENT-SIGNATURE" if int(terms.get("x402Version", 1)) >= 2 else "X-PAYMENT"
        res = c.post(url, content=body, headers={**headers, name: value})
    try:
        out = res.json()
    except ValueError:
        out = {}
    if not 200 <= res.status_code < 300:
        raise ListOfferError(out.get("error") if isinstance(out.get("error"), str) else f"the registry answered HTTP {res.status_code}", res.status_code)
    if not isinstance(out.get("id"), str) or not out["id"]:
        raise ListOfferError("the registry answer has no listing id", res.status_code)
    return {"id": out["id"]}


def main() -> int:  # pragma: no cover
    from eth_account import Account
    from evm_pay import exact_payment_header
    key = os.environ.get("LAX_PAYER_KEY")
    if not key:
        sys.stderr.write("Set LAX_PAYER_KEY in the environment.\n")
        return 1
    account = Account.from_key(key)
    offer = json.loads((Path(__file__).parent / "lax-offer.json").read_text())
    cap = int(os.environ.get("MAX_FEE_ATOMIC", "10000"))
    out = list_offer(offer, lambda t: exact_payment_header(t, account, max_atomic=cap))
    print(f"listed: {out['id']}")
    return 0


if __name__ == "__main__":  # pragma: no cover
    sys.exit(main())

agentgate-source: code mirror on Cloudflare Pages. All repos