관리 메뉴

Bbaktaeho

Tron 온체인 데이터 파헤치기 본문

개발 (Develop)/블록체인 (Blockchain)

Tron 온체인 데이터 파헤치기

Bbaktaeho 2025. 3. 4. 23:53
반응형

Tron 블록체인은 저렴한 수수료와 빠른 블록 생성 시간으로 인해 많은 Web3 빌더들이 이용하고 있는 네트워크입니다.
특히 예전부터 하루에 일정량 무료로 트랜잭션을 전송할 수 있도록 하여 접근성도 좋은 편인 것 같습니다.
또한 여러 시스템 컨트랙트(Actuator)가 빌더들을 위한 기능들을 제공하고 있기도 합니다. 

최근 Sun Pump가 Tron 네트워크에 릴리즈 되면서 활성 어카운트가 증가하고 있습니다. 이에 따라 자산 이동과 관련된 트랜잭션 TPS 까지 증가되고 있죠.

저는 증가하는 관심도에 따라 온체인 데이터를 보고 이해하는 것이 더더욱 중요하다고 생각했습니다.
따라서 이 글을 통해 Tron 의 온체인 데이터를 분석하여 누구나 온체인 데이터를 이해하고 설명할 수 있도록 공유하고자 합니다.

 

분석하기 전에

이번 글에서는 자산과 관련된 온체인 데이터를 분석해 볼 것입니다.
자산은 흔히 알고있는 TRXTRC-10TRC-20, 721, 1155 등의 코인과 토큰들 입니다.

데이터를 분석하기에 앞서 선행되면 좋은 Tron 의 간단한 설명과 관련 용어를 알아보도록 하겠습니다.
좀 더 깊은 지식을 원하신다면 Tron 문서를 참고해 주세요.

 

Resource Model

Resource Model 은 크게 Bandwidth, Energy 두 가지로 나눌 수 있습니다.
이 두 모델은 네트워크의 컴퓨팅 파워를 사용하거나, 네트워크상의 전송 비용 등을 표현합니다.
블록체인 클라이언트의 wallet/getaccountresource API 를 통해 Account의 리소스 상태를 확인할 수 있습니다.

Bandwith Point (BP)

BP는 하루에 Account가 사용할 수 있는 bytes의 수와 같습니다.
예를 들어 네트워크의 트랜잭션을 전송할 때 트랜잭션의 bytes가 200이면 200 BP가 소모되게 됩니다.

직접 BP 를 얻을 수도 있습니다. 방법은 TRX를 동결해서 BP를 얻는 방식입니다.
전체 네트워크의 동결된 자산의 양과 계산식에 따라 변동될 수 있습니다.
계산식은 아래와 같습니다.

BP = amount of TRX staked / total amount of TRX staked * 43_200_000_000

 

또한 Tron은 매일 무료로 BP를 제공합니다.

Account당 하루에 600 BP가 무료로 제공되며 계정 잔액이 동결되지 않았거나 BP를 모두 소진한 경우에만 사용할 수 있습니다.

과거에는 5000 BP, 1500 BP 도 제공했으나 최근 v4.7.2 버전부터는 600 BP가 제공되고 있네요.

만약 BP가 부족한 경우, 소유하고 있는 TRX를 사용하게 됩니다.

Energy

Energy 는 스마트 컨트랙트를 실행하는 동안 소모되는 리소스입니다.
BP와 달리 무료로 제공받을 수는 없고 TRX를 스테이킹해야만 얻을 수 있습니다.
계산식은 아래와 같습니다:

amount of energy obtained = amount of TRX staked / total amount of TRX staked for obtaining energy in the whole network * 90_000_000_000

여기서 90_000_000_000 은 일일 총 Energy 공급량입니다.

Energy 역시 부족한 경우, 소유하고 있는 TRX를 사용하게 됩니다.

TRX를 사용할 땐 Energy 당 210 SUN (0.000210 TRX)를 지불해야 합니다. 역시 과거에는 더 비싼 가격이었지만 점점 발전하면서 현재 210 SUN 으로 되었습니다. 아래 데이터 분석에서 다시 다루도록 하겠습니다.

그 외, Dynamic Energy Model 이 존재하는데 이는 리소스 균형 메커니즘으로 리소스 점유에 따라 에너지 소비를 동적으로 조정할 수 있는 모델입니다.

쉽게 얘기하면 인기 있는 컨트랙트에 네트워크 자원이 과도하게 집중되어 페널티를 부여하는 것입니다.

즉, 더 비싼 Energy 값을 줘야 한 다는 것입니다. Tron 은 6시간의 Epoch 으로 블록을 제안 및 합의를 진행하는데 다음 주기가 오기 전 특정 비율 계산을 통해 Dynamic Energy Model 이 동작하게 됩니다.

증가 공식:

energy_factor = min((1 + energy_factor) * (1 + increaese_factor)-1, max_factor)

 

감소 공식:

energy_factor = max((1 + energy_factor) * (1 - decrease_factor)- 1, 0)

 

트랜잭션을 전송하기 전에 wallet/getcontractinfo API 를 통해 limit 설정을 다이나믹하게 하는 게 좋습니다.

 

TRX, TRC-10

먼저 TRX 는 Tron의 공식 코인입니다. 코어에서 Account 구조의 balance 를 담당합니다.

아래는 Tron 공식 스캔 사이트에서 확인한 TRX 입니다.

tronscan TRX

네트워크에서 블록 보상과 수수료 모델에 가장 중요한 역할을 하죠.
가장 작은 단위는 SUN 이며 1,000,000 SUN 에 1 TRX 입니다.

 

TRC-10 은 Tron 코어에서 발행되는 토큰입니다. 스캔부터 보겠습니다.

tronscan TRC-10

흔히 알고 있는 ERC-20 과 형태는 비슷하나 스마트 컨트랙트는 아니며 코어 내부에서 관리됩니다.

실제로 Solidity 언어에서 TRX 뿐만 아니라 TRC-10 도 다룰 수 있도록 되어 있습니다.

pragma solidity ^0.5.0;

contract transferTokenContract {
    constructor() payable public{}
    
    function() payable external {}
    
    function transferTokenTest(address payable toAddress, uint256 tokenValue, trcToken id) payable public    {
        toAddress.transferToken(tokenValue, id);
    }
    
    function msgTokenValueAndTokenIdTest() public payable returns(trcToken, uint256){
        trcToken id = msg.tokenid;
        uint256 value = msg.tokenvalue;
        return (id, value);
    }
    
    function getTokenBalanceTest(address accountAddress) payable public returns (uint256){
        trcToken id = 1000001;
        return accountAddress.tokenBalance(id);
    }
}

msg.tokenid, msg.tokenvalue, <address>.tokenBalance(id) 와 같이 스마트 컨트랙트에서 사용할 수 있습니다.

이 토큰을 발행하려면 1024 TRX 가 필요하며 TIP-10 제안을 통해 순차적인 id 값이 생성됩니다.

TRC-20(ERC-20) 처럼 스마트 컨트랙트를 배포해서 Account Address가 존재하는 건 아닙니다. id 가 유일성을 보장합니다.
제가 현재까지 분석한 바로는 한 Account 당 한 개의 TRC-10 이 배포되는 것 같습니다.

자세한 내용은 여기를 확인해 주세요.

 

TRX, TRC-10 은 코어 내부에서 설계된 자산이기 때문에 transaction, internalTransaction 에서 자산 이동 관련 정보를 분석할 수 있습니다.

 

TRC-20, 721, 1155

TRC-20, 721, 1155 는 Ethereum 에서 ERC-20, 721, 1155 토큰 표준과 동일합니다.

역시 스캔에서 확인해 볼 수 있습니다.

tronscan TRC-20
tronscan TRC-721
tronscan TRC-1155

Tron은 NFT 관련 컨트랙트가 비교적 활성화되어 있지 않은 듯합니다.

스캔에서 보여주는 개수만 봐도 거의 사용되고 있지 않는 수준이네요.

이들은 모두 ERC 와 동일한 기능을 가진 컨트랙트이며 표준입니다. Tron 에서 Transaction Info 의 Log 를 통해 표준 토큰의 이벤트를 분석할 수 있습니다.

 

Transaction

블록체인을 좋아하는 분들은 누구나 알고 있는 블록체인에 상태를 변경하는 가장 기본적인 단위라 볼 수 있습니다.
Tron 역시 트랜잭션으로 자산을 전송하거나 스마트 컨트랙트를 호출하려면 트랜잭션을 생성하고 서명해서 블록에 포함되어야 합니다.
Transaction 이 블록에 포함되면 Transaction Info 를 확인할 수 있으며 Info에는 Receipt, Log(event), Internal Transaction 정보가 포함됩니다.

tronscan Block Transaction Overview

캡처 이미지를 보면 한 블록에 79 개의 Transaction이 포함되었으며 이 트랜잭션 들 중 여러 타입의 트랜잭션들이 오버레이 되어 보여주고 있습니다. 이러한 데이터를 분석하여 자산이 이동된 Trnasfer 개수도 보여주고 있죠.

또한 Internal Transaction 의 개수도 보여주고 있습니다.

 

Tron은 core 에서 다양한 기능들을 미리 구현해 뒀습니다. 이러한 기능들은 System Contract(Actuator)로 불리며 트랜잭션을 통해 사용해 볼 수 있습니다.

enum ContractType {
      AccountCreateContract = 0;
      TransferContract = 1;
      TransferAssetContract = 2;
      VoteAssetContract = 3;
      VoteWitnessContract = 4;
      WitnessCreateContract = 5;
      AssetIssueContract = 6;
      WitnessUpdateContract = 8;
      ParticipateAssetIssueContract = 9;
      AccountUpdateContract = 10;
      FreezeBalanceContract = 11;
      UnfreezeBalanceContract = 12;
      WithdrawBalanceContract = 13;
      UnfreezeAssetContract = 14;
      UpdateAssetContract = 15;
      ProposalCreateContract = 16;
      ProposalApproveContract = 17;
      ProposalDeleteContract = 18;
      SetAccountIdContract = 19;
      CustomContract = 20;
      CreateSmartContract = 30;
      TriggerSmartContract = 31;
      GetContract = 32;
      UpdateSettingContract = 33;
      ExchangeCreateContract = 41;
      ExchangeInjectContract = 42;
      ExchangeWithdrawContract = 43;
      ExchangeTransactionContract = 44;
      UpdateEnergyLimitContract = 45;
      AccountPermissionUpdateContract = 46;
      ClearABIContract = 48;
      UpdateBrokerageContract = 49;
      ShieldedTransferContract = 51;
      MarketSellAssetContract = 52;
      MarketCancelOrderContract = 53;
      FreezeBalanceV2Contract = 54;
      UnfreezeBalanceV2Contract = 55;
      WithdrawExpireUnfreezeContract = 56;
      DelegateResourceContract = 57;
      UnDelegateResourceContract = 58;
      CancelAllUnfreezeV2Contract = 59;
    }

 

java-tron protocol 에선 위와 같이 정의되어 있지만 제가 조사해 봤을 때는 System Contract는 약 40개 정도 존재하는 것으로 파악했습니다. 또한 구현되어 있지만 네트워크 상에서 사용되지 않는 케이스도 있었습니다.

Transaction은 온체인 데이터를 분석할 때 매우 중요합니다.

이번 글에서 특히 Transaction 과 Transaction Info 그리고 몇 가지 주요 System Contract 를 다뤄보도록 하겠습니다.

 

온체인 데이터 파헤치기

이제 본격적으로 앞서 언급했던 자산과 관련된 온체인 데이터를 분석해 보겠습니다.
Tron 데이터에 대해서 상세하게 파악해 보고 앞으로 Tron 데이터를 읽을 때 많은 도움이 되었으면 좋겠습니다.

 

데이터는 Tron Node 에서 HTTP API, gRPC, JSONRPC(Ethereum 호환) 를 통해 온체인 데이터를 조회할 수 있습니다.
아마 대부분 익숙한 JSONRPC 로 온체인 데이터를 조회할 텐데요.
JSONRPC 를 통해 Tron 데이터를 조회하면 TRC-10, Resource Model 등의 일부 데이터에 대해서는 조회가 불가능할 것입니다.
따라서 저는 HTTP API 를 통해서 분석해 보도록 하겠습니다.

 

Transaction 과 Transaction Info

가장 기본적으로 Transaction 과 Transaction Info 에 대해 먼저 알아야 합니다.
수수료가 어떻게 쓰였고, 어떤 System Contract를 호출했는지 등의 기본적인 정보들을 다 가지고 있습니다.
wallet/gettransactionbyid, wallet/gettransactioninfobyid API 를 통해서 조회할 수 있습니다.

Transaction Response

{
    "signature": [...],
    "txID": "...",
    "raw_data": {
        "contract": [{
            ...,
            "type": "..."
        }],
        "ref_block_bytes": "...",
        "ref_block_hash": "...",
        "expiration": ...
    },
    "raw_data_hex": "..."
}

signature 가 배열인 이유는 Multi-Signature 때문입니다. 보통은 배열에 하나의 요소만 존재할 것입니다.
데이터를 분석하기에 가장 중요한 field는 raw_data.contract 입니다.
앞서 설명드린 것처럼 Tron 은 모든 트랜잭션이 System Contract를 통해서 처리됩니다. 그 부분이 raw_data.contract 에서 System Contract 데이터가 달라지는 것이죠.
raw_data.contract.type 에서 어떤 컨트랙트 타입을 호출했는지 확인할 수 있습니다.
가장 기본적인 코인(TRX) 전송 수량이 안보이시죠? raw_data.contract 안에서 볼 수 있으며 type 에 따라 field 명이 달라집니다. 아래에서 상세하게 다뤄보도록 하겠습니다.

추가로 raw_data.contract 를 자세히 보시면 배열 형태를 나타내고 있는 점입니다.

실제 과거 데이터에서 배열로 들어오던 케이스가 있었습니다. 하지만 현재는 배열 안에 단 하나의 요소만 있으니, 단일 요소만 있다고 파악하시면 됩니다.

ref_ prefix 가 붙은 field 들은 Tron TAPOS 메커니즘을 위해 존재하는 데이터입니다. 이는 트랜잭션 재생을 방지하는 용도로 이용된다고만 이해하시고 넘어가도 괜찮습니다.

Transaction Info

{
  "id": "...",
  "fee": ...,
  "blockNumber": ...,
  "blockTimeStamp": ...,
  "contract_address": "...",
  "receipt": {
    "energy_usage": ...,
    "energy_fee": ...,
    "origin_energy_usage": ...,
    "energy_usage_total": ...,
    "net_usage": ...,
    "net_fee": ...,
    "energy_penalty_total": ...,
    "result": "SUCCESS", // FAILED
    ...
  },
  "log": [...],
  "internal_transactions": [...],
  ...
}

위의 Response 는 핵심 데이터 위주로만 작성했습니다. Transaction Info 역시 System Contract 에 따라 다양한 데이터가 포함된다는 점 참고 부탁드립니다.

id 는 transaction의 txID 와 동일합니다. field 이름의 통일성이 없는 게 조금 아쉽네요.
fee 는 이 트랜잭션 수수료로 사용한 전체 TRX 총량입니다. 여기서 조금 헷갈릴 수 있지만 수수료 설명은 별도로 진행하겠습니다.
block_number, block_timestamp 은 트랜잭션이 포함된 블록의 정보입니다.
contract_address 는 TriggerSmartContract 를 통해 호출한 contract 주소입니다. TriggerSmartContract 는 흔히 알고 있는 Smart Contract를 호출할 때 쓰이는 System Contract 입니다.
receipt 은 트랜잭션의 성공 실패와 상세한 수수료 정보를 포함하고 있습니다. fee 와 동일하게 아래에서 상세하게 설명하겠습니다.
log 는 스마트 컨트랙트의 event 입니다.
internal_transactions 는 호출했던 스마트 컨트랙트가 내부 로직에서 다른 컨트랙트를 호출할 때 TRX, TRC-10 자산 이동이 있었는지 알 수 있는 데이터입니다.
다만 아쉬운 점이 있는데 내부에서 어떤 기능을 호출했는지를 알 수는 없었습니다. 이 데이터도 아래에서 다시 다루도록 하겠습니다.

수수료

다시 fee 와 receipt 에 대해서 분석해 보겠습니다.

선행으로 배웠던 Resource Model 에서 Bandwidth Point(BP), Energy 가 부족할 때 TRX를 사용한다고 했습니다.
먼저 리소스 소모량은 receipt 에 _usage 가 붙은 데이터에서 확인 가능합니다. BP 같은 경우 receipt 에선 net 으로 표기됩니다. receipt.energy_usage, receipt.net_usage, receipt.origin_energy_usage 이 되겠군요.
여기서 receipt.origin_energy_usage 는 스마트 컨트랙트를 배포할 때 사용된 Energy 수량입니다.
receipt.energy_usage_total 는 전체 Energy 소모량이 됩니다. 여기서 Energy 수량이 부족해서 TRX를 사용하게 돼도 이 값은 존재하게 되는데요.
이는 1 Energy 당 210 SUN 으로 계산됩니다.

receipt.energy_usage_total = receipt.energy_usage + receipt.origin_energy_usage
          or
receipt.energy_usage_total = energy_fee / 210

210 SUN 으로 변경된 시점은 비교적 최근입니다. 다음 링크를 참고해서 Energy Price 의 변동 사항을 확인할 수 있습니다.

사용할 리소스가 없을 땐 _fee 를 통해 TRX 소모량을 확인할 수 있습니다. receipt.energy_fee, receipt.net_fee가 리소스를 대체한 TRX 소모량입니다. 이렇게 소모된 TRX 는 소각됩니다. 이 데이터들을 합산한 것이 위에 설명한 것처럼 Info 의 fee 가 됩니다.

fee = receipt.energy_fee + receipt.net_fee

마지막으로 receipt.energy_penalty_total 은 Dynamic Energy Model 과 관련 있습니다.
Tron 은 독특하게 인기 있는 스마트 컨트랙트에게 페널티를 부여합니다. 이는 Resource가 한정적이기 때문에 그렇다고 하네요.

예제

{
  "id": "9735cac6d402d17056ed4505e0071ea77512273be9422bd4937806158e546a82",
  "fee": 13499850,
  "blockNumber": 67844603,
  "blockTimeStamp": 1734184338000,
  "contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "receipt": {
    "energy_fee": 13499850,
    "energy_usage_total": 64285,
    "net_usage": 345,
    "result": "SUCCESS",
    "energy_penalty_total": 49635
  },
  "log": [...],
  ...
}

이 데이터는 USDT 토큰의 Transfer 함수를 실행한 Transaction Info 입니다.
먼저 Tron 에서 가장 활발한 스마트 컨트랙트이기 때문에 페널티가 붙은 모습을 볼 수 있네요.

energy_fee 를 보니 Energy Resource 가 부족해서 TRX 를 사용했나 봅니다. 전체 합계인 fee 와 동일하네요.
energy_usage_total 는 energy_fee / 210 으로 계산하면 되겠죠? (64285 = 13499850 / 210)
net_usage 를 사용하셨네요. BP 가 충분했나 봅니다.

이제 tronscan을 통해서 보면,

tronscan USDT Transfer Transaction

 

log

앞서 설명드린 것처럼 스마트 컨트랙트의 Event 가 담긴 데이터입니다.

TRC-20, 721, 1155 의 전송 내역을 log 에서 확인할 수 있습니다.

ERC-20, 721, 1155 와 동일하므로 분석 방법은 생략하겠습니다. 

 

Internal Transaction

위에서 설명한 것처럼 Transaction Info 에 포함되어 있습니다. 다만 이 데이터를 API 를 통해 보려면 node 를 실행할 때 아래 값들을 활성화시켜 줘야 됩니다.

vm = {
    ...
  saveInternalTx = true
  saveFeaturedInternalTx = true
    ...
}

하지만 활성화 시점부터 조회가 가능합니다.

이전 데이터는 Tron 에서 제공하는 Node Snapshot 을 이용하거나 Genesis Block (0 block) 부터 다시 받아야 됩니다.
데이터는 다음과 같습니다.

// Transaction Info 의 일부
{
    ...,
    "internal_transaction": [
        "hash": "...",
        "caller_address": "...",
        "transferTo_address": "...",
        callValueInfo: [
            {
                "callValue": ...,
                "tokenId": "",
            },
            ...
        ],
        "note": ...,
        "rejected": ...
    ]
}

hash 는 Internal Transaction의 고유한 값을 나타냅니다. Transaction의 id 값과 다릅니다.
caller_address 와 transferTo_address 는 흔히 알고 있는 from, to 주소 정보가 됩니다. Internal Transaction 이므로 caller_address 는 스마트 컨트랙트의 주소가 될 것입니다.
callValueInfo 가 자산과 관련된 정보입니다. TRC-10 도 스마트 컨트랙트에서 TRX 처럼 전송이 가능합니다. 그래서 tokenId 값이 어떤 TRC-10 인지 알 수 있습니다.
만약 tokenId 가 비어있으면 TRX 를 전송한 것입니다. 수량은 callValue 를 통해 알 수 있습니다.

개인적으로 어떤 기능을 호출했는지 input 값이 있었더라면 좋았을 것 같네요.

 

System Contract

이제 본격적으로 Transaction 에서 raw_data.contract 에 해당하는 System Contract 에 대해서 분석해 보겠습니다.

AccountCreateContract

AccountCreateContract 는 새로운 Account를 네트워크에 활성화시킵니다.
Tron 은 Account 를 활성화시켜야 블록체인 원장에 기록됩니다.

Node의 wallet/createaccount API 를 통해서 Account를 활성화시키지만, 굳이 사용하지 않고 TRX, TRC-10 전송만으로 새로운 Account가 활성화됩니다. 활성화 트랜잭션은 수수료를 보고 판단할 수도 있습니다. 다만 과거에는 지금과 달라서 판단 기준을 잡으려면 블록체인이 업데이트되는 시점을 찾아야 합니다. 지금은 참고로 이러한 데이터도 있다로 봐주시면 좋을 것 같습니다.

{
  "signature": [...],
  "txID": "0409c784cd51543340ad13c2ecf300201a1a9678934fc6392d45ce8bbbad1d9f",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "owner_address": "TW5nGmU4KuZZoactLZ7y7FZnhx6H1jTxLj",
            "account_address": "TMmXir2ohc7eTZFZxn1P6AeTbJnwLrT7b5"
          },
          "type_url": "type.googleapis.com/protocol.AccountCreateContract"
        },
        "type": "AccountCreateContract"
      }
    ],
    "ref_block_bytes": "8ad8",
    "ref_block_hash": "c4c1dc14e7f872d2",
    "expiration": 1530784992000
  },
  "raw_data_hex": "..."
}

raw_data.contract 만 보면 됩니다. 

type 이 AccountCreateContract 라고 친절히 설명해주고 있네요.
데이터를 분석하면 owner_address 가 신규 account_address Account 를 활성화시켰다로 볼 수 있습니다.

TransferContract

TransferContract 는 가장 기본적인 TRX 전송 컨트랙트입니다.

{
  "signature": [...],
  "txID": "0e40b1141a41ca6f4acb52341f17b2e5cbcdb161150362ca40e1eff3ae16eceb",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "amount": 100000,
            "owner_address": "TGPnfvkVkUdyCWmrVGnQ9jFW5Ca1S7XH6h",
            "to_address": "T9yxiz8M6EJ3RcDPMJo22HrGAwUZ2753u7"
          },
          "type_url": "type.googleapis.com/protocol.TransferContract"
        },
        "type": "TransferContract"
      }
    ],
    "ref_block_bytes": "07da",
    "ref_block_hash": "7ad993fc087fce41",
    "expiration": 1529897868000,
    "timestamp": 1529897570464000000
  },
  "raw_data_hex": "..."
}

owner_address  to_address 에게 amount 만큼 전송했네요.
amount 는 SUN 단위이므로 TRX 로 환산하면 0.1 TRX 를 전송했습니다.

TransferAssetContract

TransferAssetContract 는 TRC-10 을 전송하는 컨트랙트입니다.
여기선 asset 으로 TRC-10 을 표현했는데 Internal Transaction 에서는 보셨던 것처럼 token 으로 표현하기도 합니다. 개인적으로 field 이름을 통일성 있게 설계했으면 가독성이 좋았을 것 같습니다.

{
  ...
  "txID": "3426db0be3d16fd1601b674e84c5ff31947aee0e2fbd99a830ff84298c3de564",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "amount": 1,
            "asset_name": "Bitcoin",
            "owner_address": "TAEvaU3YDxjcQBHNv4PLAMJqvcJdSWdhiM",
            "to_address": "TCHLAjmhPBEpR1Qi9g9KAmBPiuMQkjNrRp"
          },
          "type_url": "type.googleapis.com/protocol.TransferAssetContract"
        },
        "type": "TransferAssetContract"
      }
    ],
    "ref_block_bytes": "8b52",
    "ref_block_hash": "2dbca869a78e9721",
    "expiration": 1535515809000,
    "timestamp": 1535515750703
  },
  ...
}

TRX 전송 컨트랙트와 크게 다르지 않습니다.
owner_address  to_address 에게 amount 만큼 asset_name 을 전송했네요.
그런데 왜 TRC-10 의 asset_name 일까요?
의 트랜잭션은 2018 년 9월의 트랜잭션입니다. 최근 트랜잭션에서는 id 값으로 조회됩니다.

{
  ...,
  "txID": "2860325a5c7a0a4402451cc026f9c6a87e0410febe860f1501efdbac06b2c4a1",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "amount": 11654260000,
            "asset_name": "1002000",
            "owner_address": "TXdaiUHATBx1y2z9PMihv8ssqNJarWxyrU",
            "to_address": "TYYWL9khqAfNC7goamRMF4guVahupoHSAy"
          },
          "type_url": "type.googleapis.com/protocol.TransferAssetContract"
        },
        "type": "TransferAssetContract"
      }
    ],
   ...
}

이 트랜잭션은 asset_name 이 id 값입니다.
즉, 과거에는 field 이름의 의미대로 name 이 나왔지만 현재는 id 로 결과가 나온다는 것입니다.
정확하게는 TIP-10 전과 후로 나뉩니다.

 

TIP-10 에 대해서 간단히 설명하면 name 중복이 불가능했다가 가능하도록 변경하자는 제안입니다. 또한 이때부터 id 가 추가되었고 ERC-20 의 decimal 처럼 precision 도 추가되어 소수점 표기를 가능하게 했습니다.
TransactionAssetContact 도 이 제안에 영향을 받습니다. asset_name 이름은 과거부터 있었기 때문에 호환성을 위해 field 이름과 값 타입을 변경하지 않은 것입니다.

 

그렇다면 첫 번째 예시("asset_name": "Bitcoin")는 어떤 TRC-10 인지 분석해 보겠습니다.

tronscan Transaction

스캔은 TRC-10 을 잘 알고 있습니다. Amount 의 BTC 를 클릭하면 해당 TRC-10 토큰 정보 페이지로 넘어갑니다.

스캔도 알고 있으니 우리도 직접 분석해 낼 수 있습니다.

찾는 방법은 매우 간단합니다. wallet/getassetissuelistbyname 에서 가장 낮은 id 찾는 것입니다.

이젠 asset_name 은 중복이 가능해서 여러 개의 토큰이 조회될 수 있는데 이때 가장 낮은 id 를 찾게 되면 바로 찾아낼 수 있습니다.
즉, asset_name 이 id 가 아닌 트랜잭션은 asset_name 그 자체 토큰이 TIP-10 이전에는 단 하나였다는 겁니다. 중복이 있다면 그 TRC-10 토큰은 TIP-10 이후로 생겼을 겁니다.

 

"Bitcoin" 이름을 갖는 TRC-10 토큰 리스트를 확인해 보겠습니다.

{
  "assetIssue": [
    {
      "owner_address": "TV7Tkx7XiFVYUCy8q7dkn3iQsBNuuGvoCK",
      "name": "Bitcoin",
      "abbr": "BTC",
      "total_supply": 21000000,
      "trx_num": 1000000,
      "num": 1,
      "start_time": 1529991531000,
      "end_time": 1561441020000,
      "description": "Bitcoin is an innovative payment network and a new kind of money.",
      "url": "http://bitcoin",
      "id": "1000004"
    },
    {
      "owner_address": "TBNmtwdvevnd6uHUimUFLDD7odEzEFJegD",
      "name": "Bitcoin",
      "abbr": "BTC",
      "total_supply": 21000000000000,
      "frozen_supply": [
        {
          "frozen_amount": 20000000000000,
          "frozen_days": 30
        }
      ],
      "trx_num": 1000000,
      "precision": 6,
      "num": 1000000,
      "start_time": 1553184000000,
      "end_time": 1561132800000,
      "description": "True Bitcoin",
      "url": "https://bitcoin.org",
      "id": "1002211"
    },
    {
      "owner_address": "THBFadZJEk8Wg8dVuYRJUhbJky1HLtfn7y",
      "name": "Bitcoin",
      "abbr": "BTW",
      "total_supply": 2.1e18,
      "trx_num": 888000000,
      "precision": 4,
      "num": 247980000,
      "start_time": 1609023600000,
      "end_time": 1609110000000,
      "description": "Bitcoin Token ses peer-to-peer technology to operate with no central authority or banks; managing transactions and the issuing of bitcoins is carried out collectively by the network.",
      "url": "https://bitcoin.org/",
      "id": "1003591"
    },
    {
      "owner_address": "TH8kLNBptvnV4w5LbokEeApc9mhqwt2YLH",
      "name": "Bitcoin",
      "abbr": "BITCOIN",
      "total_supply": 25000000,
      "trx_num": 1000000,
      "precision": 1,
      "num": 10,
      "start_time": 1640732400000,
      "end_time": 1640818800000,
      "description": "Bitcoin uses peer-to-peer technology to operate with no central authority or banks; managing transactions and the issuing of bitcoins is carried out collectively by the network. Bitcoin is open-source",
      "url": "https://bitcoin.org/",
      "id": "1004673"
    }
  ]
}

 

id가 1000004 인 TRC-10 토큰이 첫 예제의 Bitcoin TransactionAssetContract 트랜잭션이었네요.

TIP-10 에 대해서 자세한 내용은 tip-10.md 를 참고해 주세요.

ParticipateAssetIssueContract

ParticipateAssetIssueContract 는 ICO 기간 동안 TRX 로 TRC-10 을 구매하는 컨트랙트입니다.

{
  ...,
  "txID": "bcae3954e4157cb904c67b6a3de9aea7224f6a4713fd805176cb9aef15f81de9",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "amount": 25000000,
            "asset_name": "$EED",
            "owner_address": "TXw7nYSoxrzUHtXqC61AWqtVdyr75byoLa",
            "to_address": "TVWbwZ13tPjRUgXn1rKHmR1Nf6szgEftB8"
          },
          "type_url": "type.googleapis.com/protocol.ParticipateAssetIssueContract"
        },
        "type": "ParticipateAssetIssueContract"
      }
    ],
    "ref_block_bytes": "3464",
    "ref_block_hash": "c45fbc3133f9bcf2",
    "expiration": 1533081996000,
    "timestamp": 1533081945183
  },
  ...
}

owner_address  amount 를 주고 to_address 에서 asset_name 을 구매한 것입니다.

그런데 몇 개의 TRC-10 토큰을 구매한 지는 알 수 없습니다.
먼저 알아야 할 사항은 TRC-10 의 trx_num, num 데이터들 입니다. wallet/getasset... API 들을 통해 확인할 수 있습니다.

$EED 토큰으로 분석해 보겠습니다. 순서는 다음과 같습니다.

  1. 토큰 정보 찾기
  2. trx_num, num 으로 계산하기

우리가 아는 사실은 asset_name($EED) 뿐이니 wallet/getassetissuelistbyname 으로 토큰 정보를 조회해 보도록 하겠습니다.

{
  "assetIssue": [
    {
      "owner_address": "TVWbwZ13tPjRUgXn1rKHmR1Nf6szgEftB8",
      "name": "$EED",
      "abbr": "$EED",
      "total_supply": 1000000000000,
      "trx_num": 1000000,
      "num": 2,
      "start_time": 1532814900950,
      "end_time": 1564338660950,
      "description": "SEED",
      "url": "sesameseed.org",
      "id": "1000301"
    }
  ]
}

trx_num, num field 를 찾았습니다.

다음으로 계산식을 통해 몇 개의 토큰을 구매했는지 확인해 보겠습니다.

// java-tron 코드 일부
 long exchangeAmount = Math.multiplyExact(cost, assetIssueCapsule.getNum());
 exchangeAmount = Math.floorDiv(exchangeAmount, assetIssueCapsule.getTrxNum());
 ownerAccount.addAssetAmountV2(key, exchangeAmount, dynamicStore, assetIssueStore);
 ...
 AccountCapsule toAccount = accountStore.get(toAddress);
 toAccount.setBalance(Math.addExact(toAccount.getBalance(), cost));
 if (!toAccount.reduceAssetAmountV2(key, exchangeAmount, dynamicStore, assetIssueStore)) {
     throw new ContractExeException("reduceAssetAmount failed !");
 }`

코드를 통해 얻은 계산 식은 아래와 같습니다.

TRC-10 수량 = amount(cost) x num / trx_num

정리하면,

  1. owner_address  to_address 에게 amount(25000000 SUN) 를 주고,
  2. to_address  asset_name 토큰을 25000000 x 2 / 1000000 = 50 전달

tronscan Transaction

스캔과 일치하는 모습입니다.

TriggerSmartContract

TriggerSmartContract 는 스마트 컨트랙트를 호출할 때 사용됩니다.
TRC-20 을 전송하는 트랜잭션이라면 TriggerSmartContract 타입의 트랜잭션입니다.

{
  ...,
  "txID": "e81456cb9523cf6abf36347d714f2c9a04504d1a47639b4b553a2d195da9937b",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "data": "a3082be900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000001",
            "owner_address": "TB4XxoJQ2vyyYffNJgi3ge81QfbiuGzHdm",
            "contract_address": "TEEXEWrkMFKapSMJ6mErg39ELFKDqEs6w3",
            "call_value": 250000000
          },
          "type_url": "type.googleapis.com/protocol.TriggerSmartContract"
        },
        "type": "TriggerSmartContract"
      }
    ],
    "ref_block_bytes": "4ba2",
    "ref_block_hash": "be22ab2ceb21bd29",
    "expiration": 1545002346000,
    "fee_limit": 6000000,
    "timestamp": 1545002289587
  },
  ...
}

이 트랜잭션은 owner_address  contract_address 스마트 컨트랙트에게 data 를 실행하면서 call_value 만큼 TRX 를 전송하고 있네요.
TRX 뿐만 아니라 TRC-10 도 전송할 수 있습니다.

message TriggerSmartContract {
  bytes owner_address = 1;
  bytes contract_address = 2;
  int64 call_value = 3;
  bytes data = 4;
  int64 call_token_value = 5;
  int64 token_id = 6;
}

call_token_value, token_id 를 볼 수 있습니다.
이 값들이 존재한다면 TRC-10 도 컨트랙트로 전송한 것으로 분석하면 됩니다.

보통 _value 를 전송하게 된다면 Internal Transaction 도 있을 수 있습니다. 가능하면 정확한 자산 이동을 분석하기 위해 Internal Transaction 까지 분석하는 게 좋겠네요.

ExchangeCreateContract

Tron 은 TRX <-> TRC-10, TRC-10 <-> TRC-10 DEX 가 코어에 존재합니다.
Exchange 라고 불리고 API 로 쉽게 사용할 수 있습니다. 현재는 잘 사용되지는 않지만 과거에 사용되었던 자산 이동은 이미 벌어진 일이니 데이터를 읽을 수는 있어야 합니다.
또한 개선되거나 새로운 기능이 추가될 수도 있죠. 아무튼 그중에 첫 번째로 ExchangeCreateContract 를 분석해 보겠습니다.
이 컨트랙트는 이름 그대로 Exchange를 새롭게 생성합니다.

 ...,
  "txID": "11115b0e5bc868b94cc26b4d4c273f0aa641f6f202f2ac9aa2e674f8f1beeccc",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "first_token_balance": 1875000,
            "first_token_id": "LoveHearts",
            "owner_address": "TJPTRXBBYdqWHRb4gZ3uG2KARrmetoveoL",
            "second_token_id": "TRONGOLD",
            "second_token_balance": 1500000
          },
          "type_url": "type.googleapis.com/protocol.ExchangeCreateContract"
        },
        "type": "ExchangeCreateContract"
      }
    ],
    "ref_block_bytes": "45a7",
    "ref_block_hash": "f80086d084652c6d",
    "expiration": 1542991974000
  },
  ...
}

owner_address 는 새로운 Pair(Exchange)를 생성합니다.
first, second 모두 token_id 에 이름이 있는 것을 보니 TRC-10 <-> TRC-10 쌍을 생성하고 있네요.
first_token_id 의 first_token_balance 와 second_token_id 의 second_token_balance 만큼 쌍을 생성하고 있습니다.
모두 owner_address 에서 자산이 이동되었습니다.
추가로 Transaction Info를 보면,

{
  "id": "11115b0e5bc868b94cc26b4d4c273f0aa641f6f202f2ac9aa2e674f8f1beeccc",
  "fee": 1024000000,
  "blockNumber": 4343209,
  "blockTimeStamp": 1542991680000,
  "contractResult": [
    ""
  ],
  "receipt": {
    "net_usage": 269
  },
  "exchange_id": 91
}

exchange_id(91) 가 생성된 것을 볼 수 있습니다.
앞으로 wallet/getexchangebyid API 를 통해서 pair 정보를 확인할 수 있게 됩니다.

 

그리고 TRX <-> TRC-10 도 가능하다고 했는데요.
TRX 는 token_id 가 없어서 _ 으로 표시됩니다.
아래 예제를 보시면,

{
  ...,
  "txID": "b4735fdbf6ae79bc57f20f303c2fc2df7a858365bd09532262d4f410c9294fa1",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "first_token_balance": 10000,
            "first_token_id": "TRONATLAS",
            "owner_address": "TNfR9coWH76XNFwhJd4XS5ysFcYW3JfxD8",
            "second_token_id": "_",
            "second_token_balance": 1000000
          },
          "type_url": "type.googleapis.com/protocol.ExchangeCreateContract"
        },
        "type": "ExchangeCreateContract"
      }
    ],
    "ref_block_bytes": "7a92",
    "ref_block_hash": "c367c7f169708863",
    "expiration": 1544221803000
  },
  ...
}

second_token_id _ 으로 되어있으니 TRX 를 1000000 SUN 만큼 쌍으로 만들었다고 보시면 됩니다.

ExchangeInjectContract

ExchangeInjectContract 는 Exchange 에 Pair에서 한쪽 토큰을 추가로 주입하는 컨트랙트입니다.

{
  ...,
  "txID": "3d11aa9806b6eb45f4c26fbdaccfe915e83c2e027402c4bb565aace9597353fd",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "exchange_id": 134,
            "token_id": "_",
            "owner_address": "TYftfpnCitoxju9rMYx6XNegUHmFoPMary",
            "quant": 100000000000
          },
          "type_url": "type.googleapis.com/protocol.ExchangeInjectContract"
        },
        "type": "ExchangeInjectContract"
      }
    ],
    "ref_block_bytes": "e906",
    "ref_block_hash": "12026db87bb2c539",
    "expiration": 1545132804000
  },
  ...
}

owner_address  quant 만큼 token_id exchange_id 에 주입시켰네요.
token_id 가 _ 이니까 owner_address 는 100,000,000,000 SUN (100,000 TRX) 만큼 사용했습니다.

ExchangeWithdrawContract

ExchangeWithdrawContract 은 Exchange 에서 자신이 Pair 에 주입했던 토큰을 출금하는 컨트랙트입니다.

{
  ...,
  "txID": "9a2e31c618b7ff066637966b0e1f26d28e28593919842698325c1fd09ae79229",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "exchange_id": 46,
            "token_id": "_",
            "owner_address": "TJhZzCtF5macLpJuEYFRSMgcvi7g2e3tKo",
            "quant": 159059000000
          },
          "type_url": "type.googleapis.com/protocol.ExchangeWithdrawContract"
        },
        "type": "ExchangeWithdrawContract"
      }
    ],
    "ref_block_bytes": "cef0",
    "ref_block_hash": "31e87e7d8963bfe3",
    "expiration": 1542500319000
  },
  ...
}

owner_address  quant 만큼 token_id 를 exchange_id 에서 출금했습니다.

token_id 가 _ 인걸 보니 TRX 를 출금했습니다. 이 트랜잭션을 통해 159,059,000,000 SUN (159,059 TRX) 를 챙겨갔네요.

ExchangeTransactionContract

ExchangeTransactionContract 는 Pair 에서 쌍이 되는 토큰을 교환하는 컨트랙트입니다.
Swap 이라고도 부릅니다.

{
  ...,
  "txID": "f5bc35c6ab42bcc4aaa537e02910d072830a99a9c741b3e7236518a876d72db5",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "exchange_id": 69,
            "token_id": "ReynaToken",
            "expected": 1978730300,
            "owner_address": "TBAL1iNxXo63922jgnBCphktwv3CPmfcQa",
            "quant": 1000000
          },
          "type_url": "type.googleapis.com/protocol.ExchangeTransactionContract"
        },
        "type": "ExchangeTransactionContract"
      }
    ],
    "ref_block_bytes": "a8dd",
    "ref_block_hash": "54e7ff8921310e6b",
    "expiration": 1543659708000
  },
  ...
}

owner_address  quant 만큼 token_id 를 exchange_id 에서 상대 토큰과 교환합니다.
트랜잭션에서 expected 를 통해 기대 값을 확인할 수 있지만 실제로 상대 토큰의 교환 수량은 다릅니다.
실제 수량은 Transaction Info 에서 확인할 수 있습니다.

{
  "id": "f5bc35c6ab42bcc4aaa537e02910d072830a99a9c741b3e7236518a876d72db5",
  "fee": 2680,
  "blockNumber": 4565215,
  "blockTimeStamp": 1543659414000,
  "contractResult": [
    ""
  ],
  "receipt": {
    "net_fee": 2680
  },
  "exchange_received_amount": 2055561166
}

exchange_received_amount 이 실제 수량입니다.
상대 토큰이 무엇인지 확인하려면 트랜잭션에서 exchange_id 가 있으니 wallet/getexchangebyid API 를 통해 확인 가능합니다.

조회해 보면,

"first_token_id": "31303030383933"
...
"second_token_id": "5f"

이렇게 응답이 오는데 해당 값은 byte string 으로서 변환시키면 first_token_id는 1000893 이고 second_token_id는 _ 으로 TRX 입니다.

그렇다면 owner_address 는 1000000 ReynaToken 으로 2,055,561,166 SUN (2,055.561166 TRX) 를 얻으셨네요.

 

정리

여기까지 대표적인 자산 이동과 관련된 온체인 데이터를 분석해 봤습니다.

이번 글에는 포함되지 않았지만 자산을 동결 및 스테이킹하여 네트워크의 Resource 를 얻을 수 있는 System Contract 들도 존재합니다.

이는 추가로 다뤄보도록 하겠습니다.

 

현재 Tron 에서는 TRC-20 등의 스마트 컨트랙트 자산들이 매우 활성화되어 있을 겁니다. 이에 따라 Account 도 늘어나고 있습니다.

tronscan Data Charts

스캔을 보면 신규 Account 가 늘어나고 있는 것을 확인할 수 있습니다.

따라서 Tron 블록체인의 트랜잭션이 증가할 것이며 이에 따라 자산 관련 트랜잭션은 많아질 것입니다.

 

앞으로 Tron 은 TIP 를 통해 계속 발전할 것입니다. 이 또한 온체인 상에서 제안에 대한 투표를 진행하죠.

계속해서 새로운 데이터들도 생기는 것이니 온체인 데이터에 대한 안목을 넓히는 게 중요합니다.

 

Tron 온체인 데이터에 가까워졌을 거라 생각하며 이만 마치겠습니다.
긴 글 읽어주셔서 감사합니다.

 

References

Tron Docs

Tron API

java-tron

Tron Blog

반응형