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.


Order statuses

OrderStatusID is documented as a fixed set, used identically by get_status and the webhook. The SDK exposes them as tronado.OrderStatusCode:

OrderStatusID OrderStatusCode Meaning
20 WAITING_FOR_PAYMENT Order created; awaiting the user's payment
25 PHOTO_SENT_TO_ADMIN Payment proof submitted, sent to an admin
27 READY_TO_TRANSFER Approved; queued for the on-chain TRX transfer
30 PAYMENT_ACCEPTED Successful, final payment (IsPaid == true)
40 PAYMENT_REJECTED Payment rejected
200 CANCELLED Order cancelled

Only 30 (PAYMENT_ACCEPTED) means money is settled — that's what is_payment_accepted checks (alongside IsPaid). OrderStatusTitle is a Persian, display-only label, so branch on the id, never the title.

from tronado import OrderStatusCode

status = tron.order.get_status(id="TrndOrderID_55")
if status.is_payment_accepted:                 # IsPaid or OrderStatusID == 30
    fulfill()
elif status.order_status is OrderStatusCode.PAYMENT_REJECTED:
    notify_rejected()
# status.order_status is None for any id Tronado may add in future (no crash).

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.1.tar.gz (40.9 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.1-py3-none-any.whl (39.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: tronado-0.1.1.tar.gz
  • Upload date:
  • Size: 40.9 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.1.tar.gz
Algorithm Hash digest
SHA256 013265d52842f8faf291820cf16287ec013c0cb5b0f72a1ab3db92a5447b9f6e
MD5 3163eeb5122a84855ed1286d0347693f
BLAKE2b-256 451184821adb0ff323d81ddd698cdb093b353e9d47f6cfc8bdd0a03f29744669

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tronado-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 39.9 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6dea0c76d97cb598255d4d249d63b09a3ad89cb9336b3cf7996c486f5984ea1e
MD5 9b8a8135b2a80a24f9aeb82861b609ad
BLAKE2b-256 b6e9ca54674131676c18f89c442b84fc7413f882be2e373d93ff5d2e24b14be0

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