APIを使ったTRONエネルギーレンタルの自動化方法

2026-05-13

そもそもなぜエネルギーレンタルを自動化するのか

ホットウォレット、取引所の出金エンジン、ユーザーの代わりにTRC-20送金を発火するコントラクトなどを運用しているなら、痛みはすでにご存知でしょう。トランザクションが発火する瞬間にTRONエネルギーが用意できている必要があります。UIから手動でエネルギーをチャージする方法では、1日数件の送金を超えると破綻します。タイミングを逃せば、トランザクションは送信元アドレスのTRXを焼却することになり、事前にレンタルしたエネルギーよりはるかに高コストになることがほとんどです。

解決策は、バックエンドがトランザクションをブロードキャストする直前にレンタルAPIを呼び出し、人手を介さずプログラム的にエネルギーをプロビジョニングすることです。本稿では、tronenergyrent.comに対してそれをどう実現するかを具体的に解説します。実際のリクエスト形式、非同期な注文ライフサイクル、合理的な期間の選び方、そしてサービスにそのまま組み込めるPythonの動作例を取り上げます。

APIがオンチェーンで実際に行うこと

コードを書く前に、オンチェーンの仕組みを理解しておくと、手探りでデバッグせずに済みます。

TRONのリソースモデルは、計算(エネルギー)とトランザクションサイズ(bandwidth)を分離しています。標準的なUSDT TRC-20送金は、受取側がすでにUSDTを保有している場合は約65,000エネルギーを消費し、受取側のUSDT残高がゼロの場合は約130,000を消費します。最初の受信送金は残高エントリを作成するストレージコストを負担するため、コストは送信側ではなく完全に受取側に依存します。送金1件あたりのbandwidth使用量は約345バイトで、多くのアカウントは日々の無料枠でカバーできる範囲です。

レンタルAPIを呼び出すと、サービスは自身のTRXをステークし、Stake 2.0のDelegateResourceContractを使って、指定されたアドレスに生成されたエネルギーを委譲します。委譲はアドレス固有かつ期間限定です。レンタル期間が満了すると、エネルギーは自動的にプロバイダーに回収されます。

重要な点が一つあります。レンタルは非同期です。APIコールはorderIdPAID_BY_USERのstateを即座に返しますが、オンチェーンの委譲トランザクションはバックグラウンドでブロードキャストされ、通常は数秒以内に確定します。統合側では、初回レスポンスは注文が受理され支払われた確認とみなし、その後、stateがENERGY_DELEGATEDに遷移するまで注文詳細エンドポイントをポーリングする必要があります。

適切なレンタル期間の選び方

APIはperiodパラメータを受け取り、許容される値は1h1d3d30dの4つです。価格はオンチェーンのエネルギー市場に連動して変動し、1日の中でも動くため、最新の数値は常に料金ページに掲載されています。相対的な順序は安定していて、1hは1回あたりが最安、30dは同じアドレスからの多数の送金にわたって償却した場合に最安です。

送信1件ごとにレンタルを1回トリガーするイベント駆動型システムでは、ほぼ常に1hティアが正解です。絶対コストが最も低く、エネルギーは委譲後数秒以内に消費されます。1d以上の長期ティアは、たとえば夜間の支払いジョブのように、予測可能なバッチワークロードを実行しており、APIを何十回も呼ぶ代わりに大きなエネルギーブロックを一度でレンタルしたい場合に意味を持ちます。

同一アドレスから1時間あたり20件から30件を超える送金が継続的に発火するシステムでは、想定ボリュームをカバーするサイズの1dブロックをレンタルする方が、送信ごとに呼び出すよりクリーンです。初期コストは上がりますが、APIのオーバーヘッドとオンチェーン確定のレイテンシがホットパスから消えます。

認証とリクエストの構造

レンタルエンドポイントは以下にあります。

GET https://api.tronenergyrent.com/place-energy-order

クエリパラメータを伴う素朴なGETリクエストです。認証はapiKeyクエリパラメータで行い、これは登録と入金後にダッシュボードから生成します。ヘッダー認証もJSONリクエストボディもこのエンドポイントには存在しません。

パラメータは以下のとおりです。

  • apiKey(必須):ダッシュボードから取得したAPIキー。
  • period(必須):レンタル期間で、1h1d3d30dのいずれか。
  • energyAmount(必須):委譲するエネルギー量。最小値は15000。すでにUSDTを保有している受取側への標準的なUSDT送金1件には65000が安全な値で、USDT新規保有者への初回送金には130000を使用します。
  • destinationAddress(必須):委譲されたエネルギーを受け取るTRONアドレス(base58check形式、Tで始まる)。
  • preActivateDestinationAddress(任意、デフォルト0):宛先アドレスがTRXを一度も受信しておらずオンチェーンでアクティベートされていない場合に1に設定します。サービスはエネルギーを委譲する前に、プリペイド残高から1.5 TRXを送ってアドレスをアクティベートします。アドレスがすでにアクティブな場合は、追加コストを避けるため0のままにしてください。

レスポンスは注文が成功したか失敗したかにかかわらず、常にHTTPステータス200で返されます。HTTPステータスではなく、JSONボディのstatusフィールドで分岐してください。成功時のボディは次のような形になります。

{
  "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"
  }
}

エラー時のボディにはstatus: "ERROR"、機械可読なerrorCode、人間可読なerrorDescriptionが含まれます。

{
  "status": "ERROR",
  "errorCode": "INVALID_ENERGY_AMOUNT",
  "errorDescription": "energyAmount is less than 15000",
  "requestId": "71431087-4",
  "payload": null
}

どちらの分岐でも必ずrequestIdをログに記録してください。特定のレンタルをサポートに追跡してもらう必要が生じた場合、彼らが検索に使うのがそのIDです。

注文ライフサイクル

payload内のstateフィールドは、小さく固定された一連のシーケンスを進みます。

  • PAID_BY_USER:初期状態。注文は残高から支払われましたが、オンチェーンのアクションはまだ発生していません。
  • WAITING_DELEGATION:サービスが注文を取り上げ、委譲トランザクションを準備しています。
  • ENERGY_DELEGATED:委譲トランザクションがオンチェーンに確定しました。宛先アドレスはレンタルされたエネルギーを保有しており、次のアウトバウンドトランザクションでそれを消費できます。
  • ERROR_DELEGATION:何らかの理由で委譲が失敗しました。稀ですが、ネットワークが激しく混雑している間に起こり得ます。
  • CANCELLED:注文がキャンセルされ、資金は残高に返金されました。

現在の状態を確認するには、以下を呼び出します。

GET https://api.tronenergyrent.com/single-order-details?apiKey=YOUR_API_KEY&orderId=ORDER_ID

レスポンスは同じstatus / errorCode / payloadのエンベロープを使い、現在のstatepayloadの内側に入ります。注文を出した後、これを1秒か2秒ごとにポーリングし、ENERGY_DELEGATEDを確認してから初めてTRC-20送金を進めてください。実運用ではたいてい1回か2回のポーリングで済みます。

動作するPython統合

以下は最小限ながら本番想定の形に整えたパターンです。レンタルを発注し、委譲がオンチェーンに乗るまでポーリングし、何か問題が起きれば明確なエラーを表に出します。

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

強調しておくべき点がいくつかあります。HTTPステータスは常に200なので、resp.raise_for_status()だけでは実際の結果は何も分かりません。そのため、明示的なstatus == "SUCCESS"のチェックが重要です。ポーリングループにはハードタイムアウトがあり、詰まった注文が送金パイプラインを無期限にブロックすることはありません。stateでの分岐は、汎用的な「成功ではない」に依存するのではなく、3つの終端結果(ENERGY_DELEGATEDERROR_DELEGATIONCANCELLED)を明示的に扱います。

送金ワークフローでは、まずrent_energy()を呼び出し、その後TRC-20送金をブロードキャストしてください。操作の順序が重要です。先にブロードキャストすると、委譲がまだ確定していないため、トランザクションは送信側のTRXを焼却します。

プリペイド残高のサイジング

レンタルコストは、tronenergyrent.comのアカウントで維持しているプリペイド残高から差し引かれます。残高がしきい値を下回ったらアラートまたは自動チャージが走るように設定してください。妥当な下限は、ピーク時の2時間分のボリュームをカバーできる金額です。

現在の残高をプログラム的に読み取るには、以下を呼びます。

GET https://api.tronenergyrent.com/account-info?apiKey=YOUR_API_KEY

payloadには現在の残高とアカウント状態が含まれます。これを監視スタックに組み込んでおけば、午前2時に残高が枯渇していて驚くことはありません。なお、tronenergyrent.com残高への最低チャージ額は10 TRXです。

実運用におけるエラーハンドリング

本番運用では、3つの失敗モードが特に頻繁に発生します。以下のエラーコードは実APIから返るもので、安全に分岐に使えます。

  1. INSUFFICIENT_BALANCE:プリペイド残高が要求されたレンタルとプリアクティベーション分をカバーするには低すぎます。リトライしてはいけません、リトライしても解決しません。これを明示的にキャッチし、アラートまたはチャージフローをトリガーしてください。requestIdをアラートのpayloadに含めてください。
  2. INVALID_ADDRESSdestinationAddressがサーバー側のbase58checkデコードに失敗しました。たとえばユーザー入力からアドレスを動的に構築するシステムであれば、APIを呼ぶ前にローカルで検証してください。Pythonのtronpyライブラリにはis_address()があります。不正な入力をクライアント側で弾く方が、往復を待つより高速です。
  3. INACTIVE_DESTINATION_ADDRESS_ERROR:宛先アドレスがオンチェーンで一度もアクティベートされておらず、サービスにアクティベートを依頼していません。修正するには、別の場所から少額のTRXをそのアドレスにあらかじめ送金しておくか、次のコールでpreActivateDestinationAddress=1を渡し、サービスに1.5 TRXでアクティベートさせます。

もう少し微妙なケース、ORDER_IS_ALREADY_IN_PROGRESSは、同じ宛先に対して複数のワーカーが同時にレンタルを発注している場合に返ることがあります。修正はAPI側ではなくプロセス側です。宛先アドレスをキーにした分散ロック(Redisで十分です)を取り、委譲が完了するまで保持してください。

オンチェーンでの検証

ほとんどの統合では、/single-order-detailsENERGY_DELEGATEDになるまでポーリングすれば十分です。念には念をという検証が欲しい場合は、委譲が確定した後にTRONフルノードに直接問い合わせることもできます。

GET https://api.trongrid.io/v1/accounts/{destinationAddress}

レスポンスにはそのアドレスの現在のリソース状態が含まれ、外部アドレスから委譲されたエネルギーも入っています。正確なフィールド名は現行のStake 2.0ビューに依存するため、組み込み時には最新のTRON HTTP APIドキュメントを参照してください。送金をブロックせず警告ログにとどめるソフトチェックとして、レンタルAPI自体の後段で何かが間違ってしまう稀なケースに対する有用なカナリアになります。

この追加チェックは、APIではレンタルが成功しているように見えるのにオンチェーンの別の何かが間違ってしまうという、年に一度あるかどうかの場面で何時間ものデバッグを節約してくれます。

TRONの取引手数料を節約したいですか?エネルギー価格をチェック。 価格見積もり
ブログに戻る