← Home
中文 Version

TRC20-USDT On-Chain Evidence Preservation Experiment

Abstract

TRC20-USDT transaction records are publicly queryable, have relatively stable field structures, and can be cross-checked through multiple on-chain data interfaces. Therefore, they are common analytical objects in virtual-currency fund-flow tracing. However, the fact that an on-chain transaction is “queryable” does not mean that it has been properly preserved as evidence. Simply saving screenshots from a blockchain explorer is insufficient for subsequent review, structured analysis, file integrity verification, and report generation.

Focusing on the evidence preservation of a single TRC20-USDT transfer record, this study selects a publicly available USDT-TRC20 transaction-history response sample as the experimental object and constructs a minimal on-chain evidence package generation workflow. In the experiment, key fields including the transaction hash, token contract, sender address, recipient address, transfer amount, and on-chain timestamp are normalized. The workflow then generates the original JSON file, basic information JSON file, transaction event table, timeline table, relationship edge table, Graphviz relationship diagram, collection log, and SHA256 hash list.

The experimental results show that a single on-chain transaction can be transformed into a structured, reviewable, and verifiable evidence unit, providing a foundation for subsequent address expansion, fund-flow path analysis, and the formation of on-chain forensic reports.

Keywords:Forensics · OnChain · TRC20-USDT

#1 Introduction

Virtual-currency fund flows are characterized by on-chain transparency, concealed identities, cross-platform movement, and cross-border circulation. Stablecoins represented by USDT are frequently used in scenarios such as over-the-counter exchange, cross-border payment, and the transfer of proceeds from online fraud. From the perspective of forensic analysis, the transaction hash is often the earliest on-chain clue to appear. Through a transaction hash, information such as the sender address, recipient address, amount, Token contract, and timestamp can be queried. However, if such information remains only at the display level of a blockchain explorer page, it is still difficult to form stable forensic material.

The basic issue of on-chain evidence preservation is not complicated: after a transaction is discovered, what should be preserved, how it should be preserved, how later review can be ensured, and how it can be proved that the preserved files have not been modified. Although this issue may appear small, it directly affects the reliability of subsequent fund-flow path analysis. If the field source of a single transaction is unclear, the amount precision is processed incorrectly, the Token contract is not verified, or the original returned data is not preserved, subsequent address expansion and fund-flow analysis based on that transaction may be inaccurate.

The TRONGrid documentation provides an interface for querying the historical transactions of a specific TRC20 Token under a certain account, and supports specifying the TRC20 contract address through contract_address. In the official example, TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t is used as the TRC20-USDT contract address. The TRONSCAN documentation also provides interfaces for querying transaction details by transaction hash and for querying TRC20/TRC721 transfer lists. This indicates that on-chain transactions can not only be viewed through blockchain explorer pages, but can also be obtained as structured data through APIs.

Based on the above conditions, a single TRC20-USDT transaction is suitable as the minimal object for an on-chain evidence preservation experiment.


#2 Experimental Data and Field Description

The experimental object is a public USDT-TRC20 transaction-history response sample. This sample contains fields such as transaction_id, token_info, block_timestamp, from, to, type, and value. The original sample is as follows:

json
{
  "transaction_id": "d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668",
  "token_info": {
    "symbol": "USDT",
    "address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "decimals": 6,
    "name": "Tether USD"
  },
  "block_timestamp": 1651903617000,
  "from": "TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny",
  "to": "TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss",
  "type": "Transfer",
  "value": "15500000"
}

Among these fields, transaction_id is the transaction hash; token_info.address is the TRC20-USDT contract address; from is the sender address; to is the recipient address; value is the raw value before conversion according to Token precision; and decimals represents the number of decimal places.

This sample does not contain a block height field. Therefore, the experiment does not artificially create a block height. Instead, the field is uniformly recorded as null or left blank. In on-chain forensics, missing fields must be preserved truthfully and must not be fabricated merely for the sake of field completeness.

The amount conversion is as follows:

text
15500000 / 10^6 = 15.5 USDT

The timestamp conversion is as follows:

text
1651903617000 ms = 2022-05-07T06:06:57+00:00

#3 Method

#3.1 Evidence Package Structure

Around a single TRC20-USDT transaction, the following minimal evidence package is constructed:

text
usdt_trc20_evidence_experiment/
├── 01_basic_info.json
├── 02_raw_usdt_trc20_sample.json
├── 03_transfer_event.csv
├── 04_timeline.csv
├── 05_relation_edges.csv
├── 06_relation_graph.dot
├── 07_collection_log.txt
├── 08_hash_manifest.txt
├── README.md
└── build_evidence_package.py

This structure does not aim to cover complete case materials. It only serves the preservation of a single on-chain transaction. The original JSON file is used to retain the data source. The basic information JSON file is used for field normalization. The CSV files are used for subsequent analysis. The DOT file is used to generate a relationship graph. The log file records the processing procedure. The hash manifest is used for integrity verification.

#3.2 Field Normalization

Field normalization mainly deals with three types of issues.

First, Token contract verification. The contract address in the sample is:

text
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t

This address is consistent with the TRC20-USDT contract address listed on the official TRON page.

Second, amount precision conversion. The value returned on-chain is not 15.5 in the human-readable sense, but 15500000 stored in the smallest unit. Only by combining it with decimals=6 can the actual amount be obtained.

Third, timestamp conversion. The block_timestamp in the sample is a millisecond-level Unix timestamp and needs to be converted into standard UTC time, so that it can be aligned with other transaction records, log records, or report timelines.

#3.3 Integrity Verification

After the evidence package is generated, SHA256 values are calculated for the files in the directory. The hash manifest is not merely decorative; it is the basis for later review. If any file in the evidence package changes, the inconsistency can be detected by recalculating the hash and comparing it with the manifest.


#4 Experimental Results

After the experimental code was executed locally, 10 files were generated. The core output is as follows:

text
txid: d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668
block_time_utc: 2022-05-07T06:06:57+00:00
from: TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny
to: TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss
contract: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
amount: 15.5 USDT

#4.1 Basic Information File

The generated content of 01_basic_info.json is as follows:

json
{
  "experiment": "USDT-TRC20 single-transfer evidence-package fixation",
  "chain": "TRON",
  "token_standard": "TRC20",
  "token_name": "Tether USD",
  "token_symbol": "USDT",
  "contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "txid": "d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668",
  "block": null,
  "block_timestamp_ms": 1651903617000,
  "block_time_utc": "2022-05-07T06:06:57+00:00",
  "from": "TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny",
  "to": "TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss",
  "amount_raw": "15500000",
  "decimals": 6,
  "amount_decimal": "15.5",
  "event_type": "Transfer"
}

#4.2 Transfer Event Table

The content of 03_transfer_event.csv is as follows:

csv
txid,block,block_time_utc,from,to,amount_raw,decimals,amount_decimal,token_symbol,contract_address,event_type
d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668,,2022-05-07T06:06:57+00:00,TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny,TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss,15500000,6,15.5,USDT,TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t,Transfer

#4.3 Timeline

The content of 04_timeline.csv is as follows:

csv
order,type,txid,block_time_utc,from,to,amount_decimal,note
1,target_transfer,d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668,2022-05-07T06:06:57+00:00,TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny,TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss,15.5,Single USDT-TRC20 Transfer event fixed as target object

In a single-transaction experiment, the timeline contains only one record. In actual case analysis, upstream and downstream transactions of the address, exchange deposit and withdrawal records, chat record timestamps, and device log timestamps can be added to form a more complete sequence of events.

#4.4 Relationship Edges

The content of 05_relation_edges.csv is as follows:

csv
source,target,txid,block_time_utc,amount_decimal,token_symbol
TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny,TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss,d52cd9079cf82595dd507640b7b09e34d2dbb63a56b555355f5ef8984f1eb668,2022-05-07T06:06:57+00:00,15.5,USDT

The relationship edge file abstracts the transfer relationship as:

text
source → target

This step forms the basis for subsequent graph analysis. A single transaction corresponds to one edge, while multiple transactions can form an address transaction graph, which facilitates later graph-based analysis.

#4.5 Graphviz Relationship Graph

The content of 06_relation_graph.dot is as follows:

dot
digraph USDT_TRC20_Evidence {
  graph [rankdir=LR];
  node [shape=box, fontname="Arial"];
  "TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny" [label="FROM\nTYPrKF2s...cybny"];
  "TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss" [label="TO\nTTRmEA73...VefYss"];
  "TYPrKF2sevXuE86Xo3Y2mhFnjseiUcybny" -> "TTRmEA73gpoxK2KRmhL7GtcYLh88VefYss" [label="15.5 USDT\n2022-05-07T06:06:57+00:00"];
}

This graph only represents a minimal fund-flow direction:

text
TYPrKF2s...cybny  →  TTRmEA73...VefYss
             15.5 USDT

#4.6 Hash Manifest

The content of 08_hash_manifest.txt is as follows:

text
7fe8aa03d9b86e1f32f4c1cdd131e6718cc41effd43d4fa0eeed5c5badea57a7  01_basic_info.json
b4522f4058e7aebde624f8e43c3e5c20cf14cebe4b9e35b04e07e861092c3df7  02_raw_usdt_trc20_sample.json
a622c54d7794cc15bf72e4db6a7e59b9afadce64f87d8c4585d3963c12b4b303  03_transfer_event.csv
15c731d2ec8228b14aaef436087441fdf0b35ce5a0c1b20369b0cb7bb4f479ec  04_timeline.csv
abff190982b09be8b90c26b342291d392c06868f330233ed5f5aae4f9d5323ef  05_relation_edges.csv
c1aa19fc6eeef16c89257e34d7ad580cf2602f9e0369cc469514de270fc58c29  06_relation_graph.dot
9347aeafc136f2158517e33e9ed57416efee8aa37c2ee8628cda24eab2bf9dfb  07_collection_log.txt
a2648a96b883b1e8726a049decb23169cbf328a013b3b0a0aa9ad67a82f2e8a8  README.md
87a584838c8b01c8241082aeb2c6ce13bd3d37303cb42eadea1214de236901be  build_evidence_package.py

The hash manifest gives the evidence package file integrity verification capability. During later review, it is only necessary to recalculate the SHA256 values of the evidence package and compare them with the manifest to determine whether any file has changed.

This is also a necessary step in the preservation of electronic data, ensuring that the files have not been tampered with and that the process complies with relevant requirements.


#5 Code Implementation

The experimental code is shown below. The input is the original USDT-TRC20 sample JSON, and the output is the evidence package directory.

python
from pathlib import Path
from decimal import Decimal, getcontext
import json, csv, hashlib, datetime, sys, shutil

getcontext().prec = 50

def sha256_file(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()

def main(raw_json: str, out_dir: str) -> None:
    raw_path = Path(raw_json)
    out = Path(out_dir)
    out.mkdir(parents=True, exist_ok=True)

    data = json.loads(raw_path.read_text(encoding="utf-8"))
    event = data["data"][0]
    token = event["token_info"]

    shutil.copy2(raw_path, out / "02_raw_usdt_trc20_sample.json")

    amount_decimal = Decimal(event["value"]) / (
        Decimal(10) ** int(token["decimals"])
    )
    block_time_utc = datetime.datetime.fromtimestamp(
        event["block_timestamp"] / 1000,
        tz=datetime.timezone.utc
    )

    basic = {
        "experiment": "USDT-TRC20 single-transfer evidence-package fixation",
        "chain": "TRON",
        "token_standard": "TRC20",
        "token_name": token.get("name"),
        "token_symbol": token.get("symbol"),
        "contract_address": token.get("address"),
        "txid": event.get("transaction_id"),
        "block_timestamp_ms": event.get("block_timestamp"),
        "block_time_utc": block_time_utc.isoformat(),
        "from": event.get("from"),
        "to": event.get("to"),
        "amount_raw": event.get("value"),
        "decimals": token.get("decimals"),
        "amount_decimal": str(amount_decimal),
        "event_type": event.get("type"),
        "generated_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat()
    }

    (out / "01_basic_info.json").write_text(
        json.dumps(basic, ensure_ascii=False, indent=2),
        encoding="utf-8"
    )

    with (out / "03_transfer_event.csv").open(
        "w", encoding="utf-8", newline=""
    ) as f:
        writer = csv.DictWriter(f, fieldnames=[
            "txid", "block_time_utc", "from", "to", "amount_raw",
            "decimals", "amount_decimal", "token_symbol",
            "contract_address", "event_type"
        ])
        writer.writeheader()
        writer.writerow({
            "txid": event.get("transaction_id"),
            "block_time_utc": block_time_utc.isoformat(),
            "from": event.get("from"),
            "to": event.get("to"),
            "amount_raw": event.get("value"),
            "decimals": token.get("decimals"),
            "amount_decimal": str(amount_decimal),
            "token_symbol": token.get("symbol"),
            "contract_address": token.get("address"),
            "event_type": event.get("type")
        })

    with (out / "04_timeline.csv").open(
        "w", encoding="utf-8", newline=""
    ) as f:
        writer = csv.DictWriter(f, fieldnames=[
            "order", "type", "txid", "block_time_utc",
            "from", "to", "amount_decimal", "note"
        ])
        writer.writeheader()
        writer.writerow({
            "order": 1,
            "type": "target_transfer",
            "txid": event.get("transaction_id"),
            "block_time_utc": block_time_utc.isoformat(),
            "from": event.get("from"),
            "to": event.get("to"),
            "amount_decimal": str(amount_decimal),
            "note": "Single USDT-TRC20 Transfer event fixed as target object"
        })

    with (out / "05_relation_edges.csv").open(
        "w", encoding="utf-8", newline=""
    ) as f:
        writer = csv.DictWriter(f, fieldnames=[
            "source", "target", "txid",
            "block_time_utc", "amount_decimal", "token_symbol"
        ])
        writer.writeheader()
        writer.writerow({
            "source": event.get("from"),
            "target": event.get("to"),
            "txid": event.get("transaction_id"),
            "block_time_utc": block_time_utc.isoformat(),
            "amount_decimal": str(amount_decimal),
            "token_symbol": token.get("symbol")
        })

    from_addr = event.get("from")
    to_addr = event.get("to")

    dot = f"""digraph USDT_TRC20_Evidence {{
  graph [rankdir=LR];
  node [shape=box, fontname="Arial"];
  "{from_addr}" [label="FROM\\\\n{from_addr[:8]}...{from_addr[-6:]}"];
  "{to_addr}" [label="TO\\\\n{to_addr[:8]}...{to_addr[-6:]}"];
  "{from_addr}" -> "{to_addr}" [label="{amount_decimal} {token.get("symbol")}\\\\n{block_time_utc.isoformat()}"];
}}
"""
    (out / "06_relation_graph.dot").write_text(dot, encoding="utf-8")

    rows = []
    for p in sorted(out.iterdir()):
        if p.name == "08_hash_manifest.txt" or p.is_dir():
            continue
        rows.append(f"{sha256_file(p)}  {p.name}")

    (out / "08_hash_manifest.txt").write_text(
        "\n".join(rows) + "\n",
        encoding="utf-8"
    )

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(
            "Usage: python build_evidence_package.py raw_usdt_trc20_sample.json output_dir",
            file=sys.stderr
        )
        sys.exit(2)

    main(sys.argv[1], sys.argv[2])

Run the script as follows:

bash
python build_evidence_package.py 02_raw_usdt_trc20_sample.json output_dir

#6 Analysis

The experimental results demonstrate three points.

First, a single on-chain transaction can be normalized into an independent evidence unit. After the transaction hash, Token contract, addresses, time, and amount are structurally processed, they are no longer merely displayed content on a blockchain explorer page. Instead, they become data that can be read by programs, reviewed, imported into tables, or used to generate graphs.

Second, the original data and the extracted data must both be preserved. 02_raw_usdt_trc20_sample.json stores the original sample, while 01_basic_info.json stores the extracted fields. The former is used to review the source, and the latter is used for reading and reporting. If only the extracted result is preserved, it will later be impossible to determine whether the fields were extracted correctly. If only the original JSON is preserved, it will not be convenient for quick reading and analysis.

Third, the hash manifest is the basic guarantee of evidence package integrity. On-chain transactions are publicly queryable, but locally preserved forensic files may still be modified. Calculating SHA256 values for the files in the evidence package provides a baseline for later verification.


#7 Limitations

This experiment still has limitations.

It only addresses a basic issue: how to preserve a single USDT-TRC20 transfer record as a structured evidence package. Further fund-flow tracing, exchange account attribution, KYC information acquisition, device trace correlation, and proof of subjective knowledge still require integration with subsequent evidence.


#8 Conclusion

The experiment based on a public USDT-TRC20 transfer sample shows that a single on-chain transaction can be preserved as a minimal on-chain evidence package through field normalization, original data preservation, CSV structuring, relationship graph generation, and SHA256 verification. This evidence package cannot directly prove a person’s identity, nor can it replace complete case evidence. However, it can provide a reliable starting point for subsequent virtual-currency fund-flow tracing.

In virtual-currency fund-flow analysis, the transaction hash should not be treated merely as a query entry point. Instead, it should become the entry point of the evidence preservation process. The basic work of on-chain forensics is not to draw complex graphs at the beginning, but to first preserve each key transaction as an evidence unit that can be reviewed, verified, and further expanded.