Back to Blog

Deep Dives / H01

Как triggerConstantContract обеспечивает точное моделирование energy

Каждый разработчик, который когда-либо покупал TRON energy, сталкивался с одной проблемой: сколько energy на самом деле требует моя транзакция? Стандартный подход — использовать жестко закодированные оценки — 65 000 для передачи USDT, 200 000 для обмена на DEX — и надеяться, что оценка достаточно точна. Обычно это не так.

Решение существует в самом протоколе TRON: triggerConstantContract, API dry-run, который моделирует выполнение смарт-контракта без трансляции транзакции. Эта статья содержит глубокий технический анализ того, как работает этот механизм, как MERX использует его для точной оценки energy и почему результаты принципиально лучше, чем жестко закодированные значения.

Проблема жестко закодированных оценок

Рассмотрим передачу USDT. Обычно цитируемая цифра — 65 000 energy. Однако фактически потребляемый energy зависит от нескольких факторов:

Передача USDT на "65 000 energy" может фактически потребить:

Использование 65 000 в качестве фиксированной оценки означает, что вы иногда переплачиваете (тратите деньги впустую) и иногда недоплачиваете (вызывая частичное сжигание TRX).

Что делает triggerConstantContract

triggerConstantContract — это метод API полного узла TRON, который выполняет вызов смарт-контракта в среде моделирования, доступной только для чтения. Узел обрабатывает вызов точно так же, как для реальной транзакции — включая все операции чтения памяти, проверки состояния и вычислительные шаги — но не:

Ответ содержит точный energy (газ), потребленный во время моделирования, вместе с возвращаемым значением и любыми изменениями состояния, которые произошли бы.

Endpoint API

Метод доступен через полные узлы TRON и TronGrid:

POST https://api.trongrid.io/wallet/triggerconstantcontract

Структура запроса

{
  "owner_address": "TYourAddress...",
  "contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "function_selector": "transfer(address,uint256)",
  "parameter": "0000000000000000000000...",
  "visible": true
}

Ключевые поля:

Структура ответа

{
  "result": {
    "result": true,
    "code": "SUCCESS",
    "message": ""
  },
  "energy_used": 64285,
  "constant_result": ["0000000000000000000000000000000000000001"],
  "transaction": {
    "ret": [{ "contractRet": "SUCCESS" }]
  }
}

Поле energy_used содержит точное потребление energy для этого конкретного вызова с этими параметрами относительно текущего состояния контракта.

Кодирование ABI

Поле parameter требует кодирования параметров функции в формате ABI. Понимание кодирования ABI необходимо для создания правильных запросов моделирования.

Базовые типы

Кодирование ABI дополняет все значения до 32 байт (64 символа в шестнадцатеричной системе):

address: Дополнить слева до 32 байт
  TJGPeXwDpe6MBY2gwGPVbXbNJhkALrfLjX
  -> 0000000000000000000000005e09d2c48fee51bfb71e4f4a5d3e2f2c3a8b7d01

uint256: Дополнить слева до 32 байт
  1000000 (1 USDT в формате с 6 десятичными знаками)
  -> 00000000000000000000000000000000000000000000000000000000000f4240

Кодирование передачи USDT

Для transfer(address,uint256) с получателем TRecipient... и суммой 1000000:

parameter = <recipient_padded_32_bytes><amount_padded_32_bytes>

Использование TronWeb для кодирования ABI

TronWeb упрощает кодирование ABI:

import TronWeb from 'tronweb';

const tronWeb = new TronWeb({
  fullHost: 'https://api.trongrid.io',
  headers: { 'TRON-PRO-API-KEY': process.env.TRONGRID_KEY }
});

// Способ 1: Использование triggerConstantContract напрямую
const result = await tronWeb.transactionBuilder.triggerConstantContract(
  'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // Контракт USDT
  'transfer(address,uint256)',
  {},
  [
    { type: 'address', value: recipientAddress },
    { type: 'uint256', value: 1000000 }
  ],
  senderAddress
);

console.log(`Energy used: ${result.energy_used}`);

Сложные сигнатуры функций

Для более сложных вызовов (обмены на DEX, создание NFT) кодирование ABI включает несколько параметров и потенциально динамические типы:

// Моделирование обмена SunSwap
const swapResult = await tronWeb.transactionBuilder.triggerConstantContract(
  SUNSWAP_ROUTER_ADDRESS,
  'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)',
  {},
  [
    { type: 'uint256', value: amountIn },
    { type: 'uint256', value: amountOutMin },
    { type: 'address[]', value: [tokenA, WTRX, tokenB] },
    { type: 'address', value: recipientAddress },
    { type: 'uint256', value: deadline }
  ],
  senderAddress
);

console.log(`Swap energy: ${swapResult.energy_used}`);
// Может вернуть 187 432 вместо предполагаемых 200 000

Как MERX использует triggerConstantContract

MERX оборачивает функциональность triggerConstantContract в свой метод estimateEnergy, добавляя несколько уровней значения:

Упрощенный интерфейс

import { MerxClient } from 'merx-sdk';

const merx = new MerxClient({ apiKey: process.env.MERX_API_KEY });

const estimate = await merx.estimateEnergy({
  contract_address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
  function_selector: 'transfer(address,uint256)',
  parameter: [recipientAddress, 1000000],
  owner_address: senderAddress
});

console.log(`Exact energy: ${estimate.energy_required}`);

MERX обрабатывает кодирование ABI внутренне, поэтому вы передаете понятные человеку параметры вместо байтов в шестнадцатеричной системе.

Интеграция с ценообразованием

Оценка напрямую интегрируется с модулем ценообразования:

// Оценка energy
const estimate = await merx.estimateEnergy({
  contract_address: USDT_CONTRACT,
  function_selector: 'transfer(address,uint256)',
  parameter: [recipient, amount],
  owner_address: sender
});

// Получить цену за точное количество
const prices = await merx.getPrices({
  energy_amount: estimate.energy_required,
  duration: '5m'
});

// Купить ровно столько, сколько вам нужно, по лучшей цене
const order = await merx.createOrder({
  energy_amount: estimate.energy_required,
  duration: '5m',
  target_address: sender
});

// Общая стоимость минимизирована: точное количество по лучшей цене
const costTrx =
  (prices.best.price_sun * estimate.energy_required) / 1e6;
console.log(`Total cost: ${costTrx.toFixed(4)} TRX`);

Обнаружение ошибок

Если смоделированная транзакция будет отменена (недостаточный баланс, несанкционированный вызов, ошибка контракта), моделирование перехватит это перед тем, как вы потратите деньги на energy:

try {
  const estimate = await merx.estimateEnergy({
    contract_address: USDT_CONTRACT,
    function_selector: 'transfer(address,uint256)',
    parameter: [recipient, amount],
    owner_address: sender
  });
} catch (error) {
  if (error.code === 'SIMULATION_REVERT') {
    console.error(
      'Transaction would fail: ' + error.message
    );
    // Не покупайте energy для транзакции, которая не может быть выполнена
  }
}

Это предотвращает распространённую и дорогостоящую ошибку покупки energy для транзакции, которая не может быть выполнена.

Сравнение с жестко закодированными оценками

Точность

Data table
Тип транзакцииЖестко закодированная оценкаtriggerConstantContractРазница
Передача USDT (существующий владелец)65 00064 285-1,1%
Передача USDT (новый владелец)65 00065 527+0,8%
USDT transferFrom65 00051 481-20,8%
Простой обмен SunSwap200 000143 287-28,4%
Обмен SunSwap с несколькими переходами200 000212 456+6,2%
Создание NFT (простое)150 000112 340-25,1%
Создание NFT (сложное)150 000267 891+78,6%

Различия — это не случайный шум — это верно для заданных типов транзакций и состояний. Жестко закодированные оценки неправильны на 1-80% в зависимости от транзакции.

Влияние на стоимость

Для системы, обрабатывающей 1 000 передач USDT ежедневно, разница стоимости между жестко закодированной (65 000) и точной (среднее 63 500) оценкой при 28 SUN:

Для операций на DEX, где жестко закодированная оценка дальше от истины (200 000 против фактического ~155 000 в среднем), экономия пропорционально больше.

Граничные случаи и соображения

Результаты, зависящие от состояния

Результаты моделирования действительны для текущего состояния контракта. Если состояние контракта изменится между моделированием и выполнением (другая транзакция изменит соответствующий слот памяти), фактическое потребление energy может немного отличаться.

На практике это редко значимо для обычных операций, таких как передача токенов. Для сложных взаимодействий DeFi, которые зависят от балансов пулов или глобального состояния, добавьте небольшой буфер (2-5%) к результату моделирования:

const estimate = await merx.estimateEnergy({
  contract_address: DEX_ROUTER,
  function_selector: 'swap(...)',
  parameter: swapParams,
  owner_address: sender
});

// Добавить 5% буфер для операций, зависящих от состояния
const energyToOrder =
  Math.ceil(estimate.energy_required * 1.05);

Первый вызов и последующие вызовы

Некоторые смарт-контракты имеют логику инициализации, которая выполняется при первом взаимодействии с новым адресом. Первый вызов может стоить больше energy, чем последующие вызовы. Моделирование захватывает это правильно, потому что оно отражает текущее состояние — если адрес никогда не взаимодействовал с контрактом, моделирование включает стоимость инициализации.

Gas против Energy

В реализации EVM TRON газ и energy концептуально эквивалентны, но используют разные единицы. Ответ triggerConstantContract возвращает значение в единицах energy напрямую, совпадая с тем, что вам нужно купить у поставщиков.

Ограничения скорости

TronGrid применяет ограничения на количество вызовов API, включая triggerConstantContract. Для высокочастотных операций используйте платный план TronGrid или запустите свой собственный полный узел. Endpoint оценки MERX обрабатывает ограничение скорости внутри, распределяя запросы между несколькими полными узлами.

Схемы интеграции

Оценка перед транзакцией

Наиболее распространённый паттерн: оценка перед каждой транзакцией.

async function sendWithExactEnergy(
  contract: string,
  method: string,
  params: any[],
  sender: string
): Promise<string> {
  // 1. Моделирование
  const estimate = await merx.estimateEnergy({
    contract_address: contract,
    function_selector: method,
    parameter: params,
    owner_address: sender
  });

  // 2. Купить точное количество energy
  const order = await merx.createOrder({
    energy_amount: estimate.energy_required,
    duration: '5m',
    target_address: sender
  });

  await waitForFill(order.id);

  // 3. Выполнить транзакцию с нулевыми потерями
  return await broadcastTransaction(
    contract, method, params, sender
  );
}

Пакетная оценка

Для пакетных операций моделируйте все транзакции и покупайте energy в совокупности:

async function batchWithExactEnergy(
  operations: Operation[]
): Promise<void> {
  let totalEnergy = 0;

  for (const op of operations) {
    const estimate = await merx.estimateEnergy({
      contract_address: op.contract,
      function_selector: op.method,
      parameter: op.params,
      owner_address: op.sender
    });
    totalEnergy += estimate.energy_required;
  }

  // Одна покупка для всех операций
  await merx.createOrder({
    energy_amount: Math.ceil(totalEnergy * 1.02),
    duration: '30m',
    target_address: operations[0].sender
  });
}

Кэширование оценок

Для повторяющихся операций с одним и тем же контрактом и аналогичными параметрами кэшируйте оценку и периодически обновляйте:

class EstimationCache {
  private cache = new Map<string, {
    energy: number;
    timestamp: number;
  }>();
  private ttlMs = 300000; // 5 минут

  async getEstimate(
    contract: string,
    method: string,
    params: any[],
    sender: string
  ): Promise<number> {
    const key = `${contract}:${method}:${sender}`;
    const cached = this.cache.get(key);

    if (cached && Date.now() - cached.timestamp < this.ttlMs) {
      return cached.energy;
    }

    const estimate = await merx.estimateEnergy({
      contract_address: contract,
      function_selector: method,
      parameter: params,
      owner_address: sender
    });

    this.cache.set(key, {
      energy: estimate.energy_required,
      timestamp: Date.now()
    });

    return estimate.energy_required;
  }
}

Кэширование уместно для операций, где стоимость energy стабильна (передачи токенов существующим владельцам), но должно быть избегнуто для операций, где стоимость значительно варьируется (обмены на DeFi, где состояние пула часто изменяется).

Заключение

triggerConstantContract превращает покупку energy из игры в угадывание в точный расчёт. Вместо того чтобы угадывать, сколько energy требует ваша транзакция, и надеяться, что угадка достаточно точна, вы моделируете точную транзакцию относительно текущего состояния контракта и получаете точное число.

MERX интегрирует эту возможность напрямую в свой рабочий процесс покупки energy. Моделируйте, получите точное количество, купите по лучшей доступной цене у семи поставщиков и выполните транзакцию с нулевыми потерями и нулевым сжиганием TRX.

Технический механизм прямолинеен — dry-run вашего вызова смарт-контракта, который сообщает потребление energy без трансляции. Практическое воздействие значительно — исключает как отходы переплаты, так и штрафы недоплаты, одновременно перехватывая транзакции, которые будут отменены перед тем, как вы потратите деньги на energy.

Для разработчиков, работающих на TRON, точное моделирование — это не оптимизация — это необходимость для экономичных операций в любом значимом масштабе.

Изучите API оценки на https://merx.exchange/docs или попробуйте платформу на https://merx.exchange. Для интеграции AI-агентов с возможностями оценки см. MCP-сервер на https://github.com/Hovsteder/merx-mcp.

Попробуйте сейчас с AI

Добавьте MERX в Claude Desktop или любого MCP-совместимого клиента — без установки, без API-ключа для инструментов, доступных только для чтения:

{
  "mcpServers": {
    "merx": {
      "url": "https://merx.exchange/mcp/sse"
    }
  }
}

Спросите у вашего AI-агента: "Какая сейчас самая дешёвая TRON energy?" и получите живые цены от всех подключённых поставщиков.

Полная документация MCP: merx.exchange/docs/tools/mcp-server


All Articles