Skip to main content

Production-grade, version-aware Python SDK for the Tronado Public API (v5).

Project description

Tronado Python SDK

A production-grade, version-aware Python SDK for the Tronado Public API. Tronado lets a business accept TRON (TRX) payments: you create an order, the customer pays through Tronado's in-app/web payment UI, and Tronado notifies your server with a signed webhook.

  • Sync & async clients on a single httpx core
  • Typed Pydantic v2 request/response models (amounts are Decimal, never float)
  • Idempotency-aware retries — order creation is never silently retried
  • Webhook verification for the documented X-Tronado-Sig HMAC-SHA512 scheme
  • Designed for API versioning — v5 today, future versions plug in cleanly

Currently implements API v5 (the recommended version).


Installation

pip install tronado

From source (this repository):

pip install -e ".[dev]"   # includes test/lint tooling

Requires Python 3.9+. Runtime dependencies: httpx and pydantic (v2).


Authentication

Every outbound endpoint requires your API key in the x-api-key header (this is the documented scheme — there is no Authorization: Bearer). Request a key from Tronado support.

from tronado import TronadoClient

client = TronadoClient(api_key="YOUR_API_KEY")

Or via environment variable (no argument needed):

export TRONADO_API_KEY="YOUR_API_KEY"
# optional: export TRONADO_BASE_URL="https://bot.tronado.cloud"

Configuration

Option Default Description
api_key TRONADO_API_KEY env API key for the x-api-key header
base_url https://bot.tronado.cloud API base URL (TRONADO_BASE_URL honoured)
timeout 30.0 Per-request timeout (seconds)
max_retries 3 Max retries for idempotent operations
backoff_factor 0.5 Exponential-backoff base multiplier (seconds)
default_version "v5" Version used by the order / price shortcuts
default_headers {} Extra headers added to every request
user_agent tronado-python/<ver> User-Agent header
http_client None Inject your own httpx.Client/AsyncClient
client = TronadoClient(
    api_key="YOUR_API_KEY",
    timeout=15.0,
    max_retries=5,
    backoff_factor=0.25,
    default_headers={"X-Trace-Id": "abc123"},
)

You can also pass a fully built TronadoConfig:

from tronado import TronadoConfig, TronadoClient

config = TronadoConfig(api_key="YOUR_API_KEY", timeout=10)
client = TronadoClient(config=config)

Quickstart (sync)

from decimal import Decimal
from tronado import TronadoClient

with TronadoClient(api_key="YOUR_API_KEY") as tron:
    # 1. Tronado's TRX price differs from exchanges — always price first.
    price = tron.price.tron.get_price_to_toman()
    print(price.tron_price_toman, "Toman per TRX")

    # 2. Create the order and get a payment link.
    order = tron.order.get_order_token(
        payment_id="inv-1001",                       # your unique id
        wallet_address="TXYZ12345abcdef...",         # destination wallet
        tron_amount=Decimal("12.123456"),            # invoice amount in TRX
        callback_url="https://your-domain.com/payment/callback",
        wage_from_business_percentage=0,             # who absorbs the fee (0–100)
    )
    print("Pay here:", order.full_payment_url)

    # 3. Later, check status (by Tronado OrderId / TrndOrderID_x / TXID).
    status = tron.order.get_status(id="TrndOrderID_55")
    print("Paid?" , status.is_payment_accepted)

Quickstart (async)

import asyncio
from tronado import AsyncTronadoClient

async def main() -> None:
    async with AsyncTronadoClient(api_key="YOUR_API_KEY") as tron:
        price = await tron.price.tron.get_price_to_toman()
        order = await tron.order.get_order_token(
            payment_id="inv-1002",
            wallet_address="TXYZ...",
            tron_amount="8.5",
            callback_url="https://your-domain.com/payment/callback",
        )
        print(order.full_payment_url)

asyncio.run(main())

Endpoint reference

The async client exposes the same methods with await.

Order — client.order

Method Endpoint Returns
get_order_token(payment_id, wallet_address, tron_amount, callback_url, wage_from_business_percentage=0) POST /api/v5/GetOrderToken OrderTokenData
get_status(id) POST /Order/GetStatus OrderStatus (raises OrderNotFoundError if absent)
get_status_by_payment_id(id) POST /Order/GetStatusByPaymentID OrderStatus

Price — client.price

Method Endpoint Returns
price.tron.get_price_to_toman() POST /Tron/GetPriceToToman TronPrice
price.tron.get_price_with_wage_to_toman(request_code, wallet_address, tron_amount) POST /Tron/GetPriceWithWageToToman PriceWithWage
price.toman.convert_to_tron_wage_subtracted(toman, wallet) POST /Toman/ConvertToTronWageSubtracted TronConversion
price.toman.get_price_to_toman() POST /Toman/GetPriceToToman DollarPrice
price.dollar.convert_to_tron_wage_subtracted(dollar, wallet) POST /Dollar/ConvertToTronWageSubtracted TronConversion
price.dollar.get_price_to_toman() POST /Dollar/GetPriceToToman DollarPrice

Amount arguments (tron_amount, dollar) accept Decimal, int, float, or str. They are coerced via str to avoid binary-float rounding, and sent on the wire as JSON numbers.


Handling webhooks (IPN)

Tronado POSTs a JSON callback to your CallbackUrl on every order status change and signs it with X-Tronado-Sig = HMAC_SHA512(raw_body, your_ipn_signing_key) (lowercase hex). Get your IpnSigningKey from support. Always verify against the raw body — do not re-serialize parsed JSON.

from tronado.webhook import construct_event
from tronado.exceptions import InvalidSignatureError

IPN_SIGNING_KEY = "YOUR_IPN_SIGNING_KEY"

def handle_webhook(raw_body: bytes, signature_header: str) -> int:
    try:
        event = construct_event(raw_body, signature_header, IPN_SIGNING_KEY)
    except InvalidSignatureError:
        return 401  # reject

    # De-duplicate: the same (payment_id, status) may arrive more than once.
    if already_processed(event.dedup_key):
        return 200

    if event.is_payment_accepted:               # IsPaid == True or status 30
        # Charge the user with what they actually paid (includes wage), per the docs.
        credit_user(event.payment_id, event.user_paid_toman_amount)

    return 200  # return 2xx to acknowledge, else Tronado retries

FastAPI example (reads the raw body before parsing):

from fastapi import FastAPI, Request, Response
from tronado.webhook import construct_event
from tronado.exceptions import InvalidSignatureError, TronadoWebhookError

app = FastAPI()

@app.post("/payment/callback")
async def callback(request: Request) -> Response:
    raw = await request.body()
    sig = request.headers.get("X-Tronado-Sig", "")
    try:
        event = construct_event(raw, sig, "YOUR_IPN_SIGNING_KEY")
    except (InvalidSignatureError, TronadoWebhookError):
        return Response(status_code=401)
    # ... process event ...
    return Response(status_code=200)

If you only need to verify or parse separately:

from tronado.webhook import verify_signature, parse_callback

if verify_signature(raw, sig, signing_key):
    event = parse_callback(raw)

Error handling

All errors derive from TronadoError:

from tronado.exceptions import (
    TronadoError, TronadoAuthenticationError, TronadoRateLimitError,
    OrderNotFoundError, TronadoValidationError, TronadoServerError,
    TronadoTimeoutError, TronadoConnectionError,
)

try:
    status = client.order.get_status(id="maybe-missing")
except OrderNotFoundError:
    status = None
except TronadoAuthenticationError:
    ...   # bad/missing API key (HTTP 401 or envelope Code == -1)
except TronadoRateLimitError:
    ...   # 429 / per-user limits
except (TronadoTimeoutError, TronadoConnectionError):
    ...   # network problems
except TronadoServerError:
    ...   # 5xx (already retried for idempotent calls)
except TronadoError:
    ...   # catch-all

TronadoAPIError (and its subclasses) carry .status_code, .code, .message, and .response for diagnostics.

Retries & idempotency

  • Idempotent reads (price/status) are retried on timeouts, connection errors, 429, and 5xx, using exponential backoff with jitter (and Retry-After when present).
  • get_order_token is never auto-retried — it creates a transaction, so retrying on an ambiguous failure could create a duplicate order. Handle its failures explicitly.

Versioning

The Tronado API versions endpoints in the URL path (/api/v{version}/...). In v5 only GetOrderToken is version-pathed; the price/status endpoints live at unversioned roots. The SDK models this with per-version operation descriptors, so:

client.v5.order.get_order_token(...)     # explicit version pin
client.version("v5").price.tron.get_price_to_toman()
client.order.get_order_token(...)        # uses default_version

When Tronado ships a new version, it becomes a new tronado/versions/vN/ package and a one-line registry entry — the transport, models, and error handling are reused unchanged.

from tronado import available_versions
print(available_versions())   # ('v5',)

The fee (wage) model

The default wage is 20% (configurable per business; minimum is the greater of 9,000 Toman or $0.10). wage_from_business_percentage on get_order_token controls who absorbs it:

  • 0 (default): the whole fee is added on top of the user's payment; you receive the full invoice TRX.
  • 100: the whole fee is taken from your share; the user pays roughly the base value and your received TronAmount is the net after fee.
  • Values in between split the fee proportionally.

To charge your user correctly, use user_paid_toman_amount from the v5 webhook (what the user actually paid) rather than the TRX you receive.


Development

pip install -e ".[dev]"
pytest                 # run the test-suite (sync + async)
ruff check .           # lint
mypy src               # type-check

Run the suite across every supported interpreter with tox (included in the dev extra installed above):

tox                    # py39–py313 + a lint/type-check env

CI (GitHub Actions, .github/workflows/ci.yml) runs ruff and mypy once, then the test-suite on Python 3.9, 3.10, 3.11, 3.12, and 3.13.

See examples/ for runnable scripts and docs/DESIGN.md for the architecture and the full endpoint matrix.

License

MIT — see LICENSE.

Support: t.me/TronadoSupp

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tronado-0.1.0.tar.gz (38.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tronado-0.1.0-py3-none-any.whl (38.6 kB view details)

Uploaded Python 3

File details

Details for the file tronado-0.1.0.tar.gz.

File metadata

  • Download URL: tronado-0.1.0.tar.gz
  • Upload date:
  • Size: 38.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.4

File hashes

Hashes for tronado-0.1.0.tar.gz
Algorithm Hash digest
SHA256 691ca01a81fd07ff131d37f9041f83219f89d084f08e920fabf7529f02db4ffb
MD5 792e30018343075485938d37720bb076
BLAKE2b-256 0d1178c4200ad9fe8db17e074bd116267979e7e57b50d8e7b0634ef7fe7a613d

See more details on using hashes here.

File details

Details for the file tronado-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tronado-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 38.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.4

File hashes

Hashes for tronado-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5048d7efd9dd4a678ceb286611b91ae01116997607e0af2cfc0eb75550b2489a
MD5 83c2998a9ae178b7261d69e8b540077a
BLAKE2b-256 59f9293322b9efcbbe6b2b120a2292bb009033b230a32b7ff705bf06e43e1011

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page