FastAPI dependency
FastAPI's dependency injection is a perfect fit for an OrangeCheck gate — you write it once and inject it into any endpoint.
Install
pip install fastapi uvicorn orangecheck
Minimum working example
from fastapi import FastAPI, Depends, HTTPException, Header
from orangecheck import AsyncClient
app = FastAPI()
oc = AsyncClient()
async def require_stake(
x_oc_address: str = Header(...),
min_sats: int = 100_000,
min_days: int = 30,
):
r = await oc.check(addr=x_oc_address, min_sats=min_sats, min_days=min_days)
if not r.ok:
raise HTTPException(status_code=403, detail={"reasons": list(r.reasons)})
return r
@app.post("/post")
async def post_comment(gate = Depends(require_stake)):
return {"ok": True, "sats": gate.sats}
The x_oc_address header is captured automatically via FastAPI's Header(...). When it's missing, FastAPI returns 422 without ever hitting orangecheck.
From a session cookie
For high-stakes gates, run the signed-challenge flow first:
from fastapi import Cookie
async def require_stake(
verified_btc_address: str | None = Cookie(None),
min_sats: int = 100_000,
):
if not verified_btc_address:
raise HTTPException(status_code=401, detail="not signed in")
r = await oc.check(addr=verified_btc_address, min_sats=min_sats)
if not r.ok:
raise HTTPException(status_code=403, detail={"reasons": list(r.reasons)})
return r
Parameterise per-endpoint thresholds
from typing import Callable, Annotated
def gate(*, min_sats: int = 100_000, min_days: int = 30) -> Callable:
async def dep(x_oc_address: Annotated[str, Header()]):
r = await oc.check(addr=x_oc_address, min_sats=min_sats, min_days=min_days)
if not r.ok:
raise HTTPException(status_code=403, detail={"reasons": list(r.reasons)})
return r
return dep
# Different thresholds per endpoint
@app.post("/post", dependencies=[Depends(gate(min_sats=100_000))])
async def post(): return {"ok": True}
@app.post("/mint", dependencies=[Depends(gate(min_sats=1_000_000, min_days=180))])
async def mint(): return {"ok": True}
Signed-challenge endpoints
from orangecheck import challenge_issue, challenge_verify, VerificationError
from fastapi import Request
from fastapi.responses import JSONResponse
@app.get("/auth/challenge")
async def auth_challenge(addr: str, request: Request):
c = challenge_issue(addr=addr, audience="https://example.com", purpose="login")
request.session["oc_nonce"] = c.nonce
return {"message": c.message}
@app.post("/auth/verify")
async def auth_verify(body: dict, request: Request):
try:
r = challenge_verify(
message=body["message"],
signature=body["signature"],
expected_nonce=request.session.get("oc_nonce"),
expected_audience="https://example.com",
)
except VerificationError as e:
return JSONResponse({"error": str(e)}, status_code=401)
request.session["verified_btc_address"] = r.address
return {"ok": True, "address": r.address}
(Use starlette.middleware.sessions.SessionMiddleware or authlib for session support.)