AlgoGate is a Python SDK for paywalling FastAPI routes with Algorand x402-style micropayments.
The goal is simple:
- a FastAPI developer adds
AlgoGate - protected routes return
402 Payment Requireduntil they are paid for - the SDK scaffolds a Chrome extension beside the developer’s
main.py - the extension creates a wallet, pays, retries the route, and shows the API response
This branch turns the project into an SDK-first repository.
When a developer writes:
from fastapi import FastAPI
from algogate import AlgoGate
gate = AlgoGate(
receiver="ALGORAND_ADDRESS",
price_microalgo=500_000,
network="testnet",
api_name="My Premium API"
)
app = FastAPI()
gate.init_app(app)
@app.get("/api/premium")
@gate.protect
async def premium_route():
return {"data": "secret premium content"}AlgoGate does all of this:
- It adds middleware that understands payment errors and payment sessions.
- It adds SDK routes such as:
/algogate/dashboard/algogate/events/algogate/routes/algogate/health/algogate/verify
- It protects the decorated route.
- It scaffolds an
algogate_extension/folder next to the developer’smain.pyon first run. - That generated extension already contains:
- popup UI
- wallet onboarding
- local encrypted wallet storage
- 402 challenge handling
- Algorand payment signing
- route retry logic
.
├── algogate/
│ ├── __init__.py
│ ├── challenge.py
│ ├── config.py
│ ├── dashboard.py
│ ├── exceptions.py
│ ├── gate.py
│ ├── middleware.py
│ ├── payment.py
│ ├── scaffold.py
│ └── session.py
├── requirements.txt
└── README.md
The actual browser extension is not committed as a shipped folder.
Instead, it is generated at runtime by:
That means the SDK ships the extension as templates inside Python code and writes them out on gate.init_app(app).
For local development:
python3 -m pip install .For editable local development:
python3 -m pip install -e .The intended published package name is:
pip install algogate-sdkRequirements file:
Current dependencies:
fastapi>=0.110.0pydantic>=2.7.0httpx>=0.27.0python-dotenv>=1.0.0websockets>=12.0uvicorn>=0.29.0
No Python algosdk package is required.
The SDK verifies payments with the Algorand indexer REST API through httpx.
This branch includes release-ready packaging files:
And GitHub Actions workflows:
Recommended release flow:
- Push changes to
sdk - Run the TestPyPI workflow manually
- Verify install from TestPyPI
- Create a GitHub release
- The PyPI publish workflow runs automatically
from fastapi import FastAPI
from algogate import AlgoGate
gate = AlgoGate(
receiver="ABC123...",
price_microalgo=500_000,
network="testnet",
api_name="Weather Premium API",
api_key="my-internal-api-key"
)
app = FastAPI()
gate.init_app(app)
@app.get("/api/weather/free")
async def free_weather():
return {"temp": "25C", "note": "basic data"}
@app.get("/api/weather/premium")
@gate.protect
async def premium_weather():
return {
"temp": "25C",
"humidity": "72%",
"forecast": ["sunny", "windy", "cloudy"],
"alerts": ["none"]
}
@app.get("/api/weather/detailed")
@gate.protect_with_price(1_000_000)
async def detailed_weather():
return {
"hourly": ["..."],
"radar": "..."
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)Main class:
AlgoGate(
receiver: str,
price_microalgo: int,
network: str = "testnet",
api_name: str = "Protected API",
api_key: str = "",
session_ttl_seconds: int = 3600,
replay_cache_ttl: int = 86400,
scaffold_on_init: bool = True,
)Meaning of each field:
-
receiverAlgorand address that receives payments. -
price_microalgoDefault route price in microALGO.1 ALGO = 1_000_000 microALGO. -
networkEithertestnetormainnet. -
api_nameHuman-readable name shown in the extension and dashboard. -
api_keyOptional string embedded into the scaffolded extension.env. The extension sends it asX-API-Keywhen making API requests. -
session_ttl_secondsAfter a valid on-chain payment, the SDK issues a JWT session token so future calls do not need to hit the chain again immediately. -
replay_cache_ttlHow long used tx IDs stay in replay protection memory. -
scaffold_on_initIf true,gate.init_app(app)writes the extension folder automatically.
When gate.init_app(app) runs, the SDK:
- attaches AlgoGateMiddleware
- registers:
GET /algogate/dashboardWS /algogate/eventsGET /algogate/routesGET /algogate/healthPOST /algogate/verify
- stores the gate instance on
app.state.algogate - scaffolds the extension if enabled
- prints the startup banner
init_app() prints a startup banner showing:
- API name
- receiver
- default price
- network
- dashboard URL
- scaffold folder location
- Chrome load instructions
There are two protection APIs.
@gate.protectThis uses the default price_microalgo set on the gate.
@gate.protect_with_price(1_000_000)This overrides the default for one route.
On every protected request, the decorator checks X-Payment-Signature.
The route does not run.
Instead:
- a payment challenge is built
PaymentRequiredis raised- middleware turns that into a
402response - the
X-Payment-Requiredheader is set
The SDK treats it as a local payment session token.
It verifies:
- token signature
- token expiry
- token route match
If valid, the route runs immediately with no on-chain lookup.
The SDK verifies the Algorand payment on-chain:
- transaction exists
- confirmed round exists
- receiver matches
- amount is enough
- note starts with the expected route prefix
- tx id has not already been used
If valid:
- a JWT payment session is issued
- the route runs
- the response gets
X-Payment-Session: jwt.<token> - a dashboard event is broadcast
File:
Header used:
X-Payment-Required
The header value is:
- base64-encoded JSON
Shape:
{
"receiver": "ADDR...",
"amount": 500000,
"network": "testnet",
"note_prefix": "abcd1234efgh5678",
"api_name": "My Premium API",
"expires": 1712345678
}The note prefix ties a transaction to a specific protected route.
AlgoGate derives it from:
sha256(route_path).hexdigest()[:16]
The extension then sends a note like:
<note_prefix>:<timestamp>
The backend checks that the on-chain note starts with the correct prefix.
That prevents a payment meant for one route from being replayed against another route.
File:
AlgoGate uses the Algorand indexer REST API with httpx.
No Python algosdk is used on the backend.
- testnet:
https://testnet-idx.algonode.cloud - mainnet:
https://mainnet-idx.algonode.cloud
For a tx id, AlgoGate checks:
- the transaction exists
confirmed-roundexistspayment-transaction.receiver == expected_receiverpayment-transaction.amount >= expected_amount- decoded note starts with
expected_note_prefix
If any check fails:
InvalidSignatureis raised
If the tx id was already used:
ReplayAttackis raised
Also in:
Replay protection is an in-memory cache:
_used_tx_ids: dict[str, float]Meaning:
- key = tx id
- value = first-use timestamp
Before accepting a tx:
- the SDK prunes expired entries
- checks whether the tx id was already used
- rejects it if so
Important note:
This is process memory only.
If a developer wants multi-instance or persistent replay protection, they would later swap this for Redis or database storage. The current SDK keeps it simple and self-contained.
File:
After the first successful on-chain verification:
- a JWT is issued
- signed with HMAC-SHA256
- secret derived from the receiver address
Payload:
{
"sub": "<tx_id>",
"route": "/api/premium",
"exp": 1712349999
}Clients can send:
X-Payment-Signature: jwt.<token>This avoids going back to the Algorand indexer on every request.
File:
The middleware has three main jobs:
- place the current
Requestinto a context variable so the route decorator can read headers and route path without forcing the developer to addrequest: Requestto every endpoint - catch:
PaymentRequiredInvalidSignatureReplayAttack
- translate them into HTTP responses
Status mapping:
PaymentRequired->402InvalidSignature->403ReplayAttack->409
It also exposes:
X-Payment-RequiredX-Payment-Session
through Access-Control-Expose-Headers.
File:
Returns a self-contained HTML dashboard that shows:
- API name
- receiver
- network
- default price
- live payment event list
Broadcasts payment verification events.
Event shape:
{
"tx_id": "...",
"route": "/api/premium",
"amount": 500000,
"caller_ip": "127.0.0.1",
"timestamp": "...",
"session_issued": true
}The dashboard page subscribes to this websocket and updates live.
After init_app(app), these SDK routes exist:
Live HTML dashboard.
Live event feed.
Returns all protected routes discovered from the FastAPI app.
Each route entry includes:
- path
- methods
price_microalgoprice_algoapi_name
Returns:
{
"status": "ok",
"receiver": "...",
"network": "testnet",
"price": 500000
}Request:
{
"tx_id": "..."
}This is used by the generated extension while waiting for payment confirmation.
It verifies that:
- the transaction exists
- it is confirmed
- it was sent to the configured receiver
File:
On first init_app(app) run, AlgoGate writes:
algogate_extension/
manifest.json
.env
src/
background.js
popup/
popup.html
popup.js
popup.css
wallet/
algorand.js
crypto.js
vault.js
service.js
lib/
env.js
api.js
onboarding/
onboarding.html
onboarding.js
onboarding.css
node_modules/
algosdk/dist/browser/algosdk.min.js
It is written next to the developer’s main.py, using inspect.stack() to find the caller directory.
The scaffold writes:
ALGO_RECEIVER=<gate.receiver>
ALGO_NETWORK=<gate.network>
PRICE_MICROALGO=<gate.price_microalgo>
API_BASE_URL=http://127.0.0.1:8000
API_KEY=<gate.api_key>
API_NAME=<gate.api_name>AlgoGate skips regeneration and prints a message.
The generated extension is plain JavaScript.
It does not require npm install after scaffold.
Files:
src/popup/popup.htmlsrc/popup/popup.jssrc/popup/popup.css
It has two main states.
Shows:
- welcome heading
- API name
- setup wallet button
Shows:
- truncated wallet address
- balance
- receiver
- network badge
- route selector
- call API button
- response panel
- wallet unlock / lock controls
- settings panel
Files:
src/onboarding/onboarding.htmlsrc/onboarding/onboarding.jssrc/onboarding/onboarding.css
Flow:
- create password
- choose create or import
- create path shows 25-word mnemonic
- user confirms 3 random words
- wallet is encrypted with AES-GCM
- encrypted vault stored in
chrome.storage.local - unlocked session stored in
chrome.storage.session - redirect to popup
Files:
src/wallet/crypto.jssrc/wallet/vault.jssrc/wallet/service.js
The extension stores:
- encrypted wallet vault in
chrome.storage.local - unlocked session in
chrome.storage.session
So the password is needed once per session, not on every API call.
Client-side core:
src/lib/api.js
Flow:
- call protected route
- if
402, readX-Payment-Required - decode challenge
- ask for payment confirmation
- sign Algorand payment
- submit tx
- poll
/algogate/verify - retry route with
X-Payment-Signature: <tx_id> - store
X-Payment-Sessionif returned
Future calls can reuse the JWT session token automatically.
After the developer runs their FastAPI app once:
- open
chrome://extensions - enable
Developer mode - click
Load unpacked - select the generated
algogate_extension/folder
Then:
- open the popup
- set up wallet
- select a protected route
- click
Call API - approve payment
- view the API response in the popup
-
config.py Shared validated config object.
-
exceptions.py SDK exceptions.
-
challenge.py Challenge building, note prefixing, base64 encoding.
-
payment.py On-chain verification + replay protection.
-
session.py JWT issue/verify logic.
-
middleware.py Exception-to-response translation and request context handling.
-
dashboard.py Web dashboard + websocket broadcaster.
-
scaffold.py Extension generator.
-
gate.py Public developer API.
-
init.py Re-exports
AlgoGate.
On this branch, the SDK package files compile with:
python3 -m py_compile algogate/*.pyThat validates the Python syntax of the package files.
- The generated extension is intentionally self-contained.
- The backend verification uses Algorand indexer REST, not Python algosdk.
- Replay protection is in-memory.
- Session tokens are route-scoped.
- The scaffold copies or downloads the browser Algorand SDK bundle so the generated extension does not need npm install.
If you want, the next step can be:
- scaffold a sample developer app using this SDK
- run it locally
- load the generated
algogate_extension/ - test the end-to-end payment flow