DEV Community

gasrent
gasrent

Posted on

Renting TRON bandwidth from Python before a USDT payout batch

Disclosure: I work on gasrent. The scripts follow the MIT examples at https://github.com/gasrent/gasrent-api-examples .

This is part 2 on USDT (TRC20) payouts without burning TRX for resources. Part 1 covers estimating energy with TronWeb and renting that energy. This article leaves energy alone and focuses on bandwidth.

If you already rent energy and still see TRX leave the hot wallet on every transfer, bandwidth is usually why. Energy pays for contract execution. Bandwidth pays for the bytes of the transaction. A rental of one does nothing for the other.

Why a USDT batch runs out of free bandwidth

TRON gives every account a free daily bandwidth allowance. A plain USDT transfer without a memo uses about 345 bandwidth points. One or two withdrawals a day often fit. A payroll-style batch of dozens does not. When neither the free pool nor delegated bandwidth covers the whole transaction, the network burns TRX for bandwidth.

Two rules trip people up:

  1. Free bandwidth and delegated (or staked) bandwidth are not added together for a single transfer. One pool has to cover the whole transaction by itself.
  2. Energy on the address does not help. You can hold a full day of rented energy and still burn TRX for bandwidth on a large batch.

What the bandwidth API looks like

gasrent is a TRON energy and bandwidth rental aggregator. Bandwidth uses the same API key and the same prepaid TRX balance as energy. Orders are separate: an energy order_id is not a bandwidth order_id, and the webhooks are bandwidth.filled / bandwidth.cancelled.

From the live API reference and OpenAPI document:

  • Quote: POST /api/v1/bandwidth/quote
  • Order: POST /api/v1/bandwidth
  • Read one: GET /api/v1/bandwidth/{order_id}
  • Volume: 1 to 100,000 inclusive
  • Duration: 5m, 1h, 1d, 3d, or 7d (no 15-minute bandwidth rental)
  • Auth: X-API-KEY
  • Idempotency: idempotency_key in the body, or X-Idempotency-Key (8 to 128 characters). A bandwidth key is not shared with energy keys.

A bandwidth order does not activate the receiver address. Pass an active hot wallet.

A quote response has this shape (live price_trx / price_sun strings omitted so the post stays timeless):

{
  "resource": "bandwidth",
  "volume": 1000,
  "duration": "1h",
  "price_trx": "...",
  "price_sun": "..."
}
Enter fullscreen mode Exit fullscreen mode

An order adds order_id, idempotency_key, status (Pending, Filled, or Cancelled), address, txid, and created_at. Filled means the chain shows a bandwidth delegation that covers the ordered volume (the API requires nearly the full amount).

Public GET /api/v1/prices also lists bandwidth tiers for dashboards. For a firm charge, use the authenticated quote. Docs: https://gasrent.click/api.html and https://gasrent.click/openapi.json .

Size the rental to the batch

For N plain USDT transfers without a memo, start from about N * 345 bandwidth points. Round up to a package a supplier will fill. Common public tiers include 270, 345, 1000, 5000, and 10000. With exact_volume omitted or false, a quote may return a larger volume when that fill is acceptable under the API rules. It never returns a smaller one.

Duration depends on the schedule:

  • One batch, then idle: 5m or 1h right before the run.
  • Several batches across a day: one 1d or 3d rental sized to a full day of transfers.
  • Continuous sending: keep a day-long floor and top up with a short rental on spikes.

Compare a short rental for one batch against a longer rental that covers several batches on the same day. The public prices table is enough for that comparison. Lock the charge with /bandwidth/quote only when you are about to order.

Python: quote, order, poll

Set GASRENT_KEY and TRON_ADDRESS, then:

import os, time, uuid
import requests

BASE = os.environ.get("GASRENT_BASE", "https://gasrent.click/api/v1")
KEY, ADDRESS = os.environ["GASRENT_KEY"], os.environ["TRON_ADDRESS"]

def api(path, method="GET", body=None):
    r = requests.request(method, BASE + path, json=body, headers={"X-API-KEY": KEY}, timeout=60)
    data = r.json() if r.content else {}
    if not r.ok:
        raise RuntimeError(f"{r.status_code} {data.get('error_code', 'error')}")
    return data

volume = max(1000, 20 * 345)  # ~345 bandwidth per plain USDT transfer
req = {"volume": volume, "duration": "1h", "address": ADDRESS}
print("quote:", api("/bandwidth/quote", method="POST", body=req))

body = {**req, "idempotency_key": f"bandwidth-batch-{uuid.uuid4()}"}
order = None
for attempt in range(1, 6):
    try:
        order = api("/bandwidth", method="POST", body=body)
        break
    except RuntimeError as e:
        if "503" not in str(e) or attempt == 5:
            raise
        time.sleep(2 * attempt)

deadline = time.monotonic() + 600
while order["status"] == "Pending":
    if time.monotonic() > deadline:
        raise TimeoutError("Timed out")
    time.sleep(3)
    order = api(f"/bandwidth/{order['order_id']}")
print("final:", order["status"], order.get("txid"))
Enter fullscreen mode Exit fullscreen mode

Persist idempotency_key with the batch job before the first attempt so a retry cannot open a second charge.

Or: pip install -r requirements.txt then python bandwidth.py --order with GASRENT_KEY and TRON_ADDRESS set.

Node.js: the same steps

import { randomUUID } from 'node:crypto';

const BASE = process.env.GASRENT_BASE || 'https://gasrent.click/api/v1';
const KEY = process.env.GASRENT_KEY;
const ADDRESS = process.env.TRON_ADDRESS;

async function api(path, { method = 'GET', body } = {}) {
  const headers = { 'X-API-KEY': KEY };
  if (body !== undefined) headers['Content-Type'] = 'application/json';
  const res = await fetch(`${BASE}${path}`, {
    method, headers,
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) {
    const err = new Error(`${res.status} ${data.error_code || 'error'}`);
    err.status = res.status;
    throw err;
  }
  return data;
}

const volume = Math.max(1000, 20 * 345);
const req = { volume, duration: '1h', address: ADDRESS };
const quote = await api('/bandwidth/quote', { method: 'POST', body: req });
console.log('quote:', quote.resource, quote.volume, quote.duration);

const idempotencyKey = `bandwidth-batch-${randomUUID()}`;
let order;
for (let attempt = 1; ; attempt++) {
  try {
    order = await api('/bandwidth', {
      method: 'POST',
      body: { ...req, idempotency_key: idempotencyKey },
    });
    break;
  } catch (e) {
    if (e.status !== 503 || attempt >= 5) throw e;
    await new Promise((r) => setTimeout(r, 2000 * attempt));
  }
}
const deadline = Date.now() + 600000;
while (order.status === 'Pending') {
  if (Date.now() > deadline) throw new Error('Timed out');
  await new Promise((r) => setTimeout(r, 3000));
  order = await api(`/bandwidth/${order.order_id}`);
}
console.log('final:', order.status, order.txid);
Enter fullscreen mode Exit fullscreen mode

Or run node/bandwidth.js --order from the examples repo.

Fit this next to your energy step

  1. Simulate each USDT transfer (Part 1) and sum energy.
  2. Rent energy and wait until Filled.
  3. Size bandwidth to N * 345 (or your measured figure) and rent it for the same window.
  4. Wait until the bandwidth order is Filled.
  5. Broadcast the transfers.

If you use webhooks, handle order.filled and bandwidth.filled as different events on the same callback URL. Verify X-Signature as HMAC-SHA256 over the raw body bytes (Part 1 shows the Node receiver).

Checklist

  • The hot wallet must already be activated. Bandwidth rental will not activate it.
  • One idempotency key per batch, stored before the first POST /bandwidth.
  • Retry 503 unavailable with the same key. 409 conflict means the body changed for a reused key.
  • Keep energy and bandwidth order ids in separate columns. An energy id on a bandwidth path returns 404 not_found.
  • A read-only key can list orders; placing one needs a full key.

Once both rentals are Filled, send the USDT batch while the delegations are live. When a rental ends, the delegated share returns to the lender, so size for the sends you will make in that window.

MIT examples (Node.js, Python, PHP, including the bandwidth scripts) are in https://github.com/gasrent/gasrent-api-examples . Energy estimation and rental remain in Part 1. Site: https://gasrent.click .

Ivan Gasin

Top comments (0)