This repository has been deprecated. All development has moved to the unified Provider SDK monorepo.
Please use t-0-network/provider-sdk instead.
This repository is no longer maintained and will be archived.
Python SDK and project initializer for building T-0 Network payment providers. The repository contains two packages:
- t0-provider-sdk -- SDK providing ConnectRPC communication, secp256k1 cryptographic signing/verification, and ASGI/WSGI middleware for signature validation.
- t0-provider-starter -- CLI tool that scaffolds a complete provider project with sensible defaults.
- Python >= 3.13
- uv -- dependency management and runner
- Docker (optional, for containerized deployment)
-
Create a new provider project:
uvx t0-provider-starter my_provider
Alternative (install first, then run):
pip install t0-provider-starter t0-provider-starter my_provider
-
Install dependencies:
cd my_provider uv sync -
Configure environment:
Edit
.env-- thePROVIDER_PRIVATE_KEYis auto-generated. SetNETWORK_PUBLIC_KEY(provided by the T-0 team) and optionally adjustTZERO_ENDPOINT. -
Start the server:
uv run python -m provider.main
The generated project uses an async ASGI server (uvicorn) by default. See WSGI Alternative below if you prefer a synchronous server.
-
Share your public key (printed during project initialization) with the T-0 team.
t0-provider-starter <project_name> [-d <directory>]
| Argument / Option | Required | Default | Description |
|---|---|---|---|
project_name |
Yes | -- | Name for pyproject.toml and the project directory |
-d, --directory |
No | ./<project_name> |
Target directory for the generated project |
The CLI generates a secp256k1 keypair, writes the private key to .env, and prints the corresponding public key.
my_provider/
├── pyproject.toml # Project metadata, depends on t0-provider-sdk
├── Dockerfile # Multi-stage build with python:3.13-slim
├── .env.example # Template environment file
├── .env # Generated with your private key (git-ignored)
└── src/provider/
├── __init__.py
├── main.py # Entry point: server, quote publishing, quote retrieval
├── config.py # Environment variable loading and validation
├── publish_quotes.py # Sample quote publishing loop
├── get_quote.py # Sample quote retrieval
└── handler/
├── __init__.py
├── payment.py # ProviderServiceImplementation (async, 5 RPC methods)
└── payment_sync.py # ProviderServiceSyncImplementation (sync/WSGI variant)
The primary file to modify is src/provider/handler/payment.py (async) or src/provider/handler/payment_sync.py (sync/WSGI), which contain stub implementations for all provider RPC methods.
| Variable | Required | Default | Description |
|---|---|---|---|
PROVIDER_PRIVATE_KEY |
Yes | -- | secp256k1 private key (hex, 0x-prefixed). Auto-generated by the CLI. |
NETWORK_PUBLIC_KEY |
Yes | Sandbox key (pre-filled) | T-0 Network public key for verifying inbound request signatures. Update for production. |
TZERO_ENDPOINT |
No | https://api-sandbox.t-0.network |
T-0 Network API base URL |
PORT |
No | 8080 |
Server listen port |
QUOTE_PUBLISHING_INTERVAL |
No | 5000 |
Interval in milliseconds between quote publications |
The generated template contains numbered TODO comments that guide you through the integration process. Follow them in order:
-
Step 1.1 -- Initialize and start the server (completed after Quick Start). See
src/provider/main.py. -
Step 1.2 -- Share the generated public key from
.envwith the T-0 team. -
Step 1.3 -- Replace the sample quote publishing logic with your own. See
src/provider/publish_quotes.py. -
Step 1.4 -- Verify that quotes for your target currency are successfully received. See
src/provider/get_quote.py.
-
Step 2.1 -- Implement
update_paymentto handle updates for payments you initiate. Seesrc/provider/handler/payment.py. -
Step 2.2 -- Deploy your integration and share the base URL with the T-0 team.
-
Step 2.3 -- Test payment submission end-to-end.
-
Step 2.4 -- Implement
pay_outto handle payouts initiated by counterparts. Seesrc/provider/handler/payment.py. -
Step 2.5 -- Ask the T-0 team to submit a test payout to verify your
pay_outendpoint.
Additional optional methods in src/provider/handler/payment.py:
update_limit-- handle notifications about limit changesappend_ledger_entries-- handle notifications about ledger transactionsapprove_payment_quotes-- approve quotes after AML check
The default generated project uses async ASGI (uvicorn). If you prefer a synchronous WSGI server (e.g. gunicorn, waitress), replace the ASGI setup in src/provider/main.py:
from t0_provider_sdk.api.tzero.v1.payment.provider_connect import ProviderServiceWSGIApplication
from t0_provider_sdk.provider import handler_sync, new_wsgi_app
from provider.handler.payment_sync import ProviderServiceSyncImplementation
def create_provider_app(config, network_client_sync):
service = ProviderServiceSyncImplementation(network_client_sync)
return new_wsgi_app(
config.network_public_key,
handler_sync(ProviderServiceWSGIApplication, service),
)Then run with a WSGI server:
gunicorn provider.main:app --bind 0.0.0.0:8080The sync variant uses payment_sync.py instead of payment.py -- implement the same RPC methods as regular def functions instead of async def.
Run these inside the generated project directory:
uv run python -m provider.main # Start the provider server
uv run pytest # Run tests
uv run ruff check . # LintThe generated project includes a multi-stage Dockerfile using python:3.13-slim:
docker build -t my-provider .
docker run --env-file .env -p 8080:8080 my-provider- Set
TZERO_ENDPOINTto the production API URL - Ensure
NETWORK_PUBLIC_KEYmatches the production network key - Keep
PROVIDER_PRIVATE_KEYin a secrets manager, not in plaintext files - Synchronize server clock via NTP (signature timestamps are validated)
- Private key protection:
PROVIDER_PRIVATE_KEYmust never be committed to version control. Add.envto your.gitignore. - Signature verification: All inbound requests from the T-0 Network are verified against
NETWORK_PUBLIC_KEYvia ASGI or WSGI middleware. Verification uses raw request body bytes, not re-serialized protobuf. - Timestamp validation: Request timestamps must be within +/- 60 seconds of server time. Keep system clocks synchronized.
- Body size limit: Inbound request bodies are limited to 4 MB by default.
PROVIDER_PRIVATE_KEY is not set in .env
Copy .env.example to .env and set PROVIDER_PRIVATE_KEY. The CLI generates this automatically; if you created the project manually, generate a secp256k1 private key.
ModuleNotFoundError: No module named 'provider'
Run uv sync in the generated project directory. The project uses a src/ layout that requires installation.
Signature verification failures
Ensure the server clock is synchronized (NTP). Timestamps outside +/- 60 seconds are rejected. Verify that NETWORK_PUBLIC_KEY matches the key provided by the T-0 team.
pip install connectrpc installs the wrong package
The correct PyPI package is connect-python (which imports as connectrpc). The connectrpc package on PyPI is a different, unmaintained package by a third party. The SDK's pyproject.toml already depends on the correct package.
Contact the T-0 team for integration support, API credentials, and production onboarding.
Everything below is for SDK and starter maintainers, not end users.
provider-python/
├── pyproject.toml # uv workspace root
├── sdk/ # t0-provider-sdk package
│ ├── pyproject.toml
│ ├── buf.yaml + buf.gen.yaml # Proto code generation config
│ └── src/t0_provider_sdk/
│ ├── api/ # Generated ConnectRPC stubs (committed)
│ ├── proto/ # Proto definitions (source of truth)
│ ├── crypto/ # hash, keys, signer, verifier
│ ├── common/ # header constants
│ ├── network/ # signing transport, client factory
│ └── provider/ # middleware, interceptor, handler
├── starter/ # t0-provider-starter CLI package
│ ├── pyproject.toml
│ └── src/t0_provider_starter/
│ ├── cli.py # Click-based CLI entry point
│ ├── keygen.py # secp256k1 keypair generation
│ └── template/ # Embedded project template files
├── tests/
│ └── cross_test/ # Go SDK interop tests
│ ├── go_helper/ # Go binary for cross-testing
│ ├── test_cross_signature.py # Crypto interop (hash, sign, verify)
│ ├── test_cross_server.py # ASGI server-to-server
│ └── test_cross_server_sync.py # WSGI server-to-server
└── docs/
└── PITFALLS.md # Known issues and workarounds
This is a uv workspace with two members: sdk and starter.
uv sync --all-packagesInstalls all workspace dependencies including dev tools (pytest, ruff, uvicorn, protoc-gen-connect-python).
uv run pytest -v # All tests (SDK + integration + cross)
uv run pytest sdk/tests -v # SDK unit tests only
uv run pytest sdk/tests/integration -v # Integration tests only
uv run pytest tests/cross_test -v # Go cross-tests (requires Go helper)These tests validate interoperability between the Python and Go SDKs: Keccak256 hash equivalence, public key derivation, bidirectional signature verification, and end-to-end server-to-server communication (both ASGI and WSGI).
cd tests/cross_test/go_helper && go build -o go_helper . && cd ../../..
uv run pytest tests/cross_test/ -vuv run ruff check .Configuration in root pyproject.toml: target Python 3.13, line length 120, rules: E, F, W, I, N, UP, B, A, SIM, TCH.
When proto definitions change, regenerate the Python + ConnectRPC stubs:
cd sdk
buf dep update
buf generate- Proto source files:
sdk/src/t0_provider_sdk/proto/ - Generated output:
sdk/src/t0_provider_sdk/api/ - Generated code is committed to the repository
- After regeneration, verify that
api/buf/__init__.pyandapi/buf/validate/__init__.pyexist
Reference for porting changes from the Go SDK:
| Go SDK | Python SDK |
|---|---|
crypto/hash.go |
sdk/src/t0_provider_sdk/crypto/hash.py |
crypto/sign.go |
sdk/src/t0_provider_sdk/crypto/signer.py |
crypto/verify_signature.go |
sdk/src/t0_provider_sdk/crypto/verifier.py |
crypto/helper.go |
sdk/src/t0_provider_sdk/crypto/keys.py |
common/header.go |
sdk/src/t0_provider_sdk/common/headers.py |
network/signing_transport.go |
sdk/src/t0_provider_sdk/network/signing.py |
network/client.go |
sdk/src/t0_provider_sdk/network/client.py |
provider/verify_signature.go |
sdk/src/t0_provider_sdk/provider/middleware.py (ASGI), middleware_wsgi.py (WSGI) |
provider/signature_error.go |
sdk/src/t0_provider_sdk/provider/interceptor.py |
provider/handler.go |
sdk/src/t0_provider_sdk/provider/handler.py |
| PyPI Package | Import | Purpose | Notes |
|---|---|---|---|
connect-python |
connectrpc |
ConnectRPC runtime | Do NOT use the connectrpc PyPI package (different, unmaintained) |
protobuf |
google.protobuf |
Message serialization | >= 5.28 required |
coincurve |
coincurve |
secp256k1 ECDSA | Signing, verification, key derivation |
pycryptodome |
Crypto.Hash.keccak |
Keccak256 hash | Do NOT use pysha3 (incompatible with Python 3.13) or hashlib.sha3_256 (different padding) |
pyqwest |
pyqwest |
HTTP client (Rust-backed) | Transitive dependency via connect-python |
See docs/PITFALLS.md for a comprehensive list of known issues and workarounds.
Publishing is fully automated via GitHub Actions:
- Go to Actions → Create Release → Run workflow (or
gh workflow run "Create Release"). - The release workflow bumps the patch version in both
sdk/pyproject.tomlandstarter/pyproject.toml, commits, tags, and creates a GitHub Release. - The tag push triggers the Publish Packages workflow, which:
- Publishes
t0-provider-sdkto PyPI (environment:pypi-sdk) - Publishes
t0-provider-starterto PyPI (environment:pypi-starter) - Uploads wheels and sdists to the GitHub Release
- Publishes
PyPI authentication uses Trusted Publishers (OIDC) — no API tokens needed.