-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfull_app.py
More file actions
123 lines (87 loc) · 3.72 KB
/
Copy pathfull_app.py
File metadata and controls
123 lines (87 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""
Full BRC app example — auth + payments + an overlay topic, no fakes.
Unlike ``brc105_minimal_server.py`` (which stubs out auth and payment),
this wires the real pieces together with :func:`bsv_brc.build_brc_app`:
- BRC-103/104 mutual auth terminates at ``POST /.well-known/auth``.
- BRC-105 payments are settled through the server's real wallet via
``wallet.internalize_action`` (the default verifier) — no stub.
- A BRC-22 overlay topic (``tm_demo``) accepts transaction submissions
at ``POST /submit`` and admits every output 0.
Run:
pip install "bsv-brc[starlette]" uvicorn
python examples/full_app.py
In production, load the server identity from a persisted key rather than
generating a fresh one on boot, and pass a stable ``nonce_secret`` so
outstanding 402 challenges survive a restart.
References:
BRC-22: https://bsv.brc.dev/overlays/0022
BRC-29: https://bsv.brc.dev/payments/0029
BRC-105: https://bsv.brc.dev/payments/0105
"""
from __future__ import annotations
import os
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from bsv.keys import PrivateKey
from bsv.overlay_tools.ship_broadcaster import AdmittanceInstructions
from bsv.wallet.wallet_impl import ProtoWallet
from bsv_brc import (
PathPricing,
TopicEngine,
TopicManager,
build_brc_app,
get_identity,
get_payment,
)
from bsv_brc.brc22.adapters.asgi import make_submit_route
# --- the server's identity ----------------------------------------------
# Load a persisted key in production; this generates one per boot.
SERVER_WALLET = ProtoWallet(PrivateKey())
# --- a BRC-22 overlay topic ---------------------------------------------
class DemoTopicManager(TopicManager):
"""Admits output 0 of every submitted transaction into ``tm_demo``."""
def identify_admissible_outputs(self, beef: bytes, previous_coins):
return AdmittanceInstructions(outputs_to_admit=[0], coins_to_retain=[])
def get_documentation(self) -> str:
return "Demo topic: admits output 0 of every transaction."
topic_engine = TopicEngine({"tm_demo": DemoTopicManager()})
# --- application handlers ------------------------------------------------
async def health(_: Request) -> JSONResponse:
return JSONResponse({"status": "ok"})
async def whoami(request: Request) -> JSONResponse:
# Identity is set by AuthMiddleware after a verified BRC-104 request.
return JSONResponse({"identity_key": get_identity(request)})
async def premium(request: Request) -> JSONResponse:
payment = get_payment(request)
return JSONResponse(
{
"data": "hello, paying customer",
"satoshis_paid": payment.satoshis_paid if payment else 0,
"identity_key": get_identity(request),
}
)
inner = Starlette(
routes=[
Route("/health", health),
Route("/whoami", whoami),
Route("/premium", premium),
# The BRC-22 submission endpoint for the tm_demo topic.
make_submit_route(topic_engine, "/submit"),
]
)
# --- compose auth + payments around the app -----------------------------
app = build_brc_app(
inner,
wallet=SERVER_WALLET,
# /premium costs 100 sats; everything else is free (auth still required).
pricing=PathPricing({"/premium": 100}, default=0),
# A stable secret in production; random here so the example self-runs.
nonce_secret=os.environ.get("NONCE_SECRET", "").encode() or None,
# Health and the overlay submit endpoint should not require payment.
excluded_paths={"/health", "/submit", "/.well-known/auth"},
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)