Cách tự động hóa việc thuê TRON Energy bằng API
Vì sao cần tự động hóa việc thuê energy
Nếu bạn đang vận hành một hot wallet, một engine rút tiền cho sàn giao dịch, hoặc một hợp đồng kích hoạt các giao dịch TRC-20 thay mặt người dùng, bạn đã biết rõ nỗi đau: bạn cần TRON energy sẵn sàng ngay tại khoảnh khắc giao dịch được phát đi. Việc nạp energy thủ công qua giao diện không thể mở rộng quá vài giao dịch mỗi ngày. Bỏ lỡ thời điểm và giao dịch sẽ đốt TRX từ địa chỉ gửi thay thế, thường với chi phí cao hơn nhiều so với energy đã thuê trước.
Cách giải quyết là gọi API thuê ngay tại thời điểm backend của bạn chuẩn bị phát đi một giao dịch, để energy được cấp phát theo chương trình mà không cần con người tham gia. Bài viết này sẽ hướng dẫn chính xác cách làm điều đó với tronenergyrent.com: cấu trúc yêu cầu thực tế, vòng đời đơn hàng bất đồng bộ, các đánh đổi hợp lý về thời lượng, và một ví dụ Python hoạt động mà bạn có thể đưa thẳng vào service của mình.
API thực sự làm gì trên on-chain
Trước khi viết code, hiểu được cơ chế on-chain sẽ giúp bạn không phải debug một cách mù mờ.
Mô hình tài nguyên của TRON tách biệt tính toán (energy) khỏi kích thước giao dịch (bandwidth). Một giao dịch USDT TRC-20 tiêu chuẩn tiêu thụ khoảng 65,000 energy khi người nhận đã có sẵn USDT, và khoảng 130,000 khi số dư USDT của người nhận bằng không. Giao dịch chuyển vào đầu tiên phải trả chi phí lưu trữ cho việc tạo entry số dư, đó là lý do chi phí phụ thuộc hoàn toàn vào người nhận, không phải người gửi. Mức tiêu thụ bandwidth cho một giao dịch khoảng 345 byte, đủ nhỏ để hầu hết tài khoản đều bù được từ hạn mức miễn phí hàng ngày.
Khi bạn gọi API thuê, dịch vụ sẽ stake TRX của chính mình và ủy quyền (delegate) năng lượng kết quả cho địa chỉ bạn chỉ định, sử dụng DelegateResourceContract từ Stake 2.0. Việc ủy quyền gắn liền với địa chỉ cụ thể và có giới hạn thời gian. Khi hết thời hạn thuê, energy sẽ tự động được nhà cung cấp thu hồi lại.
Một chi tiết quan trọng: việc thuê là bất đồng bộ. Lệnh gọi API trả về ngay lập tức với một orderId và trạng thái PAID_BY_USER, nhưng giao dịch ủy quyền on-chain được phát đi ở chế độ nền và thường được xác nhận trong vài giây. Bản tích hợp của bạn nên coi phản hồi ban đầu như xác nhận rằng đơn hàng đã được chấp nhận và đã thanh toán, sau đó poll endpoint chi tiết đơn hàng cho đến khi trạng thái chuyển sang ENERGY_DELEGATED.
Chọn thời lượng thuê phù hợp
API chấp nhận tham số period với bốn giá trị được phép: 1h, 1d, 3d, 30d. Giá biến động theo thị trường energy on-chain và thay đổi trong ngày, vì vậy các con số trực tiếp luôn nằm trên trang giá. Thứ tự tương đối thì ổn định: 1h rẻ nhất cho mỗi lần gọi, 30d rẻ nhất khi phân bổ qua nhiều giao dịch từ cùng một địa chỉ.
Đối với hệ thống event-driven nơi bạn kích hoạt một lần thuê cho mỗi giao dịch đi ra, gói 1h hầu như luôn là lựa chọn đúng. Bạn trả chi phí tuyệt đối thấp nhất và energy được tiêu thụ chỉ trong vài giây sau khi được ủy quyền. Các gói 1d và dài hơn hợp lý khi bạn chạy các workload theo lô có thể dự đoán được, ví dụ một job chi trả vào ban đêm, và muốn thuê một khối energy lớn một lần thay vì gọi API hàng chục lần.
Nếu hệ thống của bạn thường xuyên thực hiện hơn 20-30 giao dịch mỗi giờ từ cùng một địa chỉ, việc thuê một khối 1d được tính toán đủ để bao phủ khối lượng dự kiến sẽ gọn gàng hơn so với gọi cho từng giao dịch. Chi phí trả trước tăng lên nhưng phần overhead của API và độ trễ xác nhận on-chain biến mất khỏi đường dẫn nóng.
Xác thực và cấu trúc yêu cầu
Endpoint thuê nằm tại:
GET https://api.tronenergyrent.com/place-energy-order
Đây là một yêu cầu GET đơn giản với các tham số query. Xác thực dùng tham số query apiKey, mà bạn sinh ra từ bảng điều khiển sau khi đăng ký và nạp tiền vào tài khoản. Không có xác thực dựa trên header và không có thân yêu cầu JSON cho endpoint này.
Các tham số là:
apiKey(bắt buộc): khóa API của bạn lấy từ bảng điều khiển.period(bắt buộc): thời lượng thuê, một trong các giá trị1h,1d,3d,30d.energyAmount(bắt buộc): lượng energy cần ủy quyền. Mức tối thiểu là15000. Đối với một giao dịch USDT tiêu chuẩn đến người nhận đã có sẵn USDT,65000là con số an toàn; đối với giao dịch đầu tiên đến một người mới giữ USDT, hãy dùng130000.destinationAddress(bắt buộc): địa chỉ TRON (base58check, bắt đầu bằngT) sẽ nhận energy được ủy quyền.preActivateDestinationAddress(tùy chọn, mặc định0): đặt thành1nếu địa chỉ đích chưa bao giờ nhận TRX và do đó chưa được kích hoạt on-chain. Dịch vụ sẽ gửi1.5 TRXtừ số dư trả trước của bạn để kích hoạt địa chỉ trước khi ủy quyền energy. Nếu địa chỉ đã hoạt động, hãy để giá trị này là0để tránh chi phí phụ.
Phản hồi luôn được trả về với HTTP status 200, bất kể đơn hàng thành công hay thất bại. Hãy rẽ nhánh theo trường status trong thân JSON thay vì theo HTTP status. Thân phản hồi thành công trông như sau:
{
"status": "SUCCESS",
"errorCode": null,
"errorDescription": null,
"requestId": "2651eacd-2428",
"payload": {
"orderId": "128de799-501e-44b2-8d6f-1fa825c2deed",
"totalPriceSun": 5662800,
"totalPriceTrx": 5.6628,
"state": "PAID_BY_USER"
}
}
Thân phản hồi lỗi có status: "ERROR", một errorCode dễ đọc bằng máy, và một errorDescription dễ đọc cho người:
{
"status": "ERROR",
"errorCode": "INVALID_ENERGY_AMOUNT",
"errorDescription": "energyAmount is less than 15000",
"requestId": "71431087-4",
"payload": null
}
Luôn log requestId ở cả hai nhánh. Nếu sau này bạn cần nhờ bộ phận hỗ trợ truy vết một lần thuê cụ thể, đó là ID mà họ sẽ tìm kiếm.
Vòng đời đơn hàng
Trường state trong payload đi qua một chuỗi cố định nhỏ:
PAID_BY_USER: trạng thái ban đầu, đơn hàng đã được thanh toán từ số dư của bạn nhưng chưa có hành động on-chain nào diễn ra.WAITING_DELEGATION: dịch vụ đã tiếp nhận đơn hàng và đang chuẩn bị giao dịch ủy quyền.ENERGY_DELEGATED: giao dịch ủy quyền đã được xác nhận on-chain. Địa chỉ đích bây giờ đang giữ energy đã thuê và có thể dùng nó cho giao dịch đi ra tiếp theo.ERROR_DELEGATION: việc ủy quyền thất bại vì lý do nào đó. Hiếm gặp, nhưng có thể xảy ra trong giai đoạn mạng tắc nghẽn nặng.CANCELLED: đơn hàng đã bị hủy và tiền được hoàn lại vào số dư của bạn.
Để kiểm tra trạng thái hiện tại, hãy gọi:
GET https://api.tronenergyrent.com/single-order-details?apiKey=YOUR_API_KEY&orderId=ORDER_ID
Phản hồi sử dụng cùng vỏ bọc status / errorCode / payload, với state hiện tại nằm bên trong payload. Hãy poll endpoint này mỗi một hoặc hai giây sau khi đặt đơn hàng và chỉ tiến hành giao dịch TRC-20 sau khi bạn thấy ENERGY_DELEGATED. Trên thực tế, thường chỉ cần một hoặc hai lần poll.
Bản tích hợp Python hoạt động
Dưới đây là một mẫu tối thiểu nhưng có hình dạng production. Nó đặt lệnh thuê, poll cho đến khi việc ủy quyền lên on-chain, và hiển thị lỗi rõ ràng nếu có gì sai sót.
import logging
import time
import requests
API_BASE = "https://api.tronenergyrent.com"
API_KEY = "your_api_key_here"
ENERGY_FOR_TRANSFER = 65_000 # use 130_000 if recipient has zero USDT balance
HTTP_TIMEOUT_SECS = 10
POLL_INTERVAL_SECS = 1
POLL_TIMEOUT_SECS = 30
def place_order(destination_address: str, period: str = "1h",
energy_amount: int = ENERGY_FOR_TRANSFER) -> dict:
resp = requests.get(
f"{API_BASE}/place-energy-order",
params={
"apiKey": API_KEY,
"period": period,
"energyAmount": energy_amount,
"destinationAddress": destination_address,
"preActivateDestinationAddress": 0,
},
timeout=HTTP_TIMEOUT_SECS,
)
resp.raise_for_status()
body = resp.json()
if body.get("status") != "SUCCESS":
raise RuntimeError(
f"place-energy-order failed: {body.get('errorCode')} "
f"({body.get('errorDescription')}) requestId={body.get('requestId')}"
)
return body["payload"]
def wait_for_delegation(order_id: str) -> dict:
deadline = time.monotonic() + POLL_TIMEOUT_SECS
while time.monotonic() < deadline:
resp = requests.get(
f"{API_BASE}/single-order-details",
params={"apiKey": API_KEY, "orderId": order_id},
timeout=HTTP_TIMEOUT_SECS,
)
resp.raise_for_status()
body = resp.json()
if body.get("status") != "SUCCESS":
raise RuntimeError(
f"single-order-details failed: {body.get('errorCode')} "
f"({body.get('errorDescription')})"
)
state = body["payload"]["state"]
if state == "ENERGY_DELEGATED":
return body["payload"]
if state == "ERROR_DELEGATION":
raise RuntimeError(f"Delegation failed for order {order_id}")
if state == "CANCELLED":
raise RuntimeError(f"Order {order_id} was cancelled")
time.sleep(POLL_INTERVAL_SECS)
raise TimeoutError(f"Order {order_id} did not reach ENERGY_DELEGATED in {POLL_TIMEOUT_SECS}s")
def rent_energy(destination_address: str) -> dict:
placed = place_order(destination_address)
logging.info(
"Order placed: id=%s priceSun=%s priceTrx=%s",
placed["orderId"], placed["totalPriceSun"], placed["totalPriceTrx"],
)
delegated = wait_for_delegation(placed["orderId"])
logging.info("Order delegated: id=%s", delegated["orderId"])
return delegated
Có vài điểm đáng nhấn mạnh. Việc kiểm tra rõ ràng status == "SUCCESS" rất quan trọng vì HTTP status luôn là 200, do đó riêng resp.raise_for_status() không nói cho bạn biết gì về kết quả thực tế. Vòng lặp poll có timeout cứng để một đơn hàng bị kẹt không thể chặn đường ống chuyển tiền của bạn vô thời hạn. Việc rẽ nhánh theo state xử lý rõ ràng ba kết cục cuối (ENERGY_DELEGATED, ERROR_DELEGATION, CANCELLED) thay vì dựa vào kiểu "không thành công" chung chung.
Trong workflow chuyển tiền, hãy gọi rent_energy() trước, rồi mới phát đi giao dịch TRC-20. Thứ tự thao tác rất quan trọng: phát trước thì giao dịch sẽ đốt TRX của người gửi vì việc ủy quyền chưa được xác nhận.
Định cỡ số dư trả trước
Chi phí thuê được trừ từ số dư trả trước bạn duy trì trong tài khoản tronenergyrent.com của mình. Hãy thiết lập cảnh báo hoặc nạp tự động khi số dư rơi xuống dưới một ngưỡng. Một mức sàn hợp lý là bất cứ con số nào đủ bao phủ hai giờ khối lượng cao điểm.
Để đọc số dư hiện tại bằng chương trình:
GET https://api.tronenergyrent.com/account-info?apiKey=YOUR_API_KEY
Payload bao gồm số dư hiện tại và trạng thái tài khoản của bạn. Hãy nối điều đó vào stack giám sát và bạn sẽ không bao giờ bị bất ngờ bởi số dư cạn kiệt lúc 2 giờ sáng. Lưu ý rằng mức nạp tối thiểu vào số dư tronenergyrent.com của bạn là 10 TRX.
Xử lý lỗi trong thực tế
Ba kiểu lỗi xuất hiện thường xuyên nhất trong production. Các mã lỗi dưới đây đến từ API thực và bạn có thể rẽ nhánh dựa trên chúng một cách an toàn.
INSUFFICIENT_BALANCE: số dư trả trước của bạn quá thấp để bao phủ lần thuê được yêu cầu cộng với bất kỳ chi phí kích hoạt trước nào. Đừng thử lại, thử lại sẽ không sửa được. Hãy bắt cụ thể lỗi này và kích hoạt luồng cảnh báo hoặc nạp tiền. Hãy thêmrequestIdvào payload cảnh báo.INVALID_ADDRESS:destinationAddresskhông vượt qua được bước giải mã base58check ở phía server. Nếu hệ thống của bạn dựng địa chỉ một cách động, ví dụ từ dữ liệu do người dùng nhập, hãy xác thực chúng cục bộ trước khi gọi API. Thư viện Pythontronpycóis_address()cho việc này; loại bỏ dữ liệu xấu ở phía client nhanh hơn nhiều so với việc chờ một vòng round trip.INACTIVE_DESTINATION_ADDRESS_ERROR: địa chỉ đích chưa bao giờ được kích hoạt on-chain và bạn không yêu cầu dịch vụ kích hoạt nó. Hãy khắc phục bằng cách nạp trước một lượng TRX nhỏ vào địa chỉ từ nơi khác, hoặc truyềnpreActivateDestinationAddress=1ở lần gọi tiếp theo để dịch vụ kích hoạt nó với1.5 TRX.
Một trường hợp tinh tế hơn: ORDER_IS_ALREADY_IN_PROGRESS có thể quay lại nếu bạn có nhiều worker đặt lệnh thuê cho cùng một đích cùng lúc. Cách khắc phục nằm ở phía tiến trình, không phải phía API. Hãy đặt một khóa phân tán (Redis hoạt động ổn) theo địa chỉ đích và giữ cho đến khi việc ủy quyền hoàn tất.
Xác minh trên on-chain
Đối với hầu hết các bản tích hợp, việc poll /single-order-details cho đến khi đạt ENERGY_DELEGATED là đủ. Nếu bạn muốn xác minh kép cho chắc chắn, bạn cũng có thể truy vấn trực tiếp full node TRON sau khi việc ủy quyền được xác nhận:
GET https://api.trongrid.io/v1/accounts/{destinationAddress}
Phản hồi chứa trạng thái tài nguyên hiện tại của địa chỉ, bao gồm cả energy được ủy quyền cho nó từ các địa chỉ bên ngoài. Tên trường chính xác phụ thuộc vào view Stake 2.0 hiện tại, vì vậy hãy tham khảo tài liệu TRON HTTP API trực tiếp khi nối điều này vào hệ thống. Như một bước kiểm tra mềm chỉ ghi cảnh báo thay vì chặn việc chuyển tiền, đây là một con chim hoàng yến hữu ích cho trường hợp hiếm hoi khi có điều gì đó sai sót ở phía sau API thuê.
Bước kiểm tra phụ đó sẽ tiết kiệm cho bạn hàng giờ debug vào cái lần duy nhất mà việc thuê trông có vẻ thành công trong API nhưng có điều gì đó khác lại sai sót trên on-chain.