做專案的時候經常需要透過監控鏈的區塊交易記錄,然後根據交易記錄與使用者的地址進行核對,從而得知使用者地址的充幣和提幣的情況。
var blockNumber = 0; //用來記錄當前檢查的區塊高度
while (true) {
var stopWatch = new Stopwatch();
stopWatch.Start();
try {
string responseString;
if (blockNumber == 0) {
const string url = "https://api.trongrid.io/wallet/getnowblock"; //獲取最新區塊交易明細
responseString = HttpClientHelper.Get(url);
} else {
const string url = "https://api.trongrid.io/wallet/getblockbynum"; // 指定 blockNumber 獲取區塊交易明顯
var requestBody = new { num = blockNumber + 1 };
responseString = HttpClientHelper.Post(url, JsonConvert.SerializeObject(requestBody), Encoding.UTF8);
}
var responseObject = JsonConvert.DeserializeObject<dynamic>(responseString);
if (responseObject == null) throw new ThreadSleepException();
if (responseObject.blockID == null) throw new ThreadSleepException();
if (responseObject.block_header == null) throw new ThreadSleepException();
blockNumber = (int)responseObject.block_header.raw_data.number;
var blockHash = (string)responseObject.blockID;
var millisecondTimestamp = (long)responseObject.block_header.raw_data.timestamp;
Console.WriteLine($" 區塊高度 {blockNumber}\t區塊雜湊 {blockHash}");
if (responseObject.transactions == null || responseObject.transactions.Count == 0) continue;
var addresses = new List<string>();
foreach (var transaction in responseObject.transactions) {
var ret = transaction.ret;
if (ret == null) continue;
if (ret.Count == 0) continue;
if (ret[0].contractRet == null || ret[0].contractRet != "SUCCESS") continue;
var rawData = transaction.raw_data;
if (rawData == null) continue;
var contracts = rawData.contract;
if (contracts == null) continue;
if (contracts.Count == 0) continue;
var contract = contracts[0];
if (contract == null) continue;
var parameter = contract.parameter;
if (parameter == null) continue;
var value = parameter.value;
if (value == null) continue;
var type = (string)contract.type;
switch (type) {
case "TransferContract": {
if (value.to_address != null && value.asset_name == null) {
// TRX 轉出地址
var fromAddress = Base58Encoder.EncodeFromHex((string)value.owner_address, 0x41);
// TRX 轉入地址
var toAddress = Base58Encoder.EncodeFromHex((string)value.to_address, 0x41);
// 轉賬金額,long 型別
var amount = (long)value.amount;
// 轉化成 decimal 型別方便業務邏輯處理
var transferAmount = amount / new decimal(1000000);
if (RedisProvider.Instance.KeyExists(fromAddress)) {
// TODO
}
if (RedisProvider.Instance.KeyExists(toAddress)) {
// TODO
}
}
break;
}
case "TriggerSmartContract": {
// 這裡監控的是 USDT 合約地址,如果需要監控其他 TRC20 代幣,修改合約地址即可
if (value.contract_address != null && (string)value.contract_address == "41a614f803b6fd780986a42c78ec9c7f77e6ded13c") {
var data = (string)value.data;
switch (data[..8]) {
case "a9059cbb": {
// USDT 轉出地址
var fromAddress = Base58Encoder.EncodeFromHex((string)value.owner_address, 0x41);
// USDT 轉入地址
var toAddress = Base58Encoder.EncodeFromHex(((string)value.data).Substring(8, 64), 0x41);
// 轉賬金額,long 型別
var amount = Convert.ToInt64(((string)value.data).Substring(72, 64), 16);
// 轉化成 decimal 型別方便業務邏輯處理
var transferAmount = amount / new decimal(1000000);
if (RedisProvider.Instance.KeyExists(fromAddress)) {
//TODO
}
if (RedisProvider.Instance.KeyExists(toAddress)) {
//TODO
}
break;
}
}
}
break;
}
case "DelegateResourceContract": {
var receiverAddress = Base58Encoder.EncodeFromHex((string)value.receiver_address, 0x41);
// receiverAddress 是指監控到地址接受到了代理能量
// TODO
break;
}
default: {
continue;
}
}
}
} catch (ThreadSleepException) {
if (stopWatch.ElapsedMilliseconds >= 1000) continue;
Thread.Sleep((int)(1000 - stopWatch.ElapsedMilliseconds));
} catch (Exception exception) {
Console.WriteLine($" {exception}");
LogManager.GetLogger(typeof(Program)).Error(exception);
if (stopWatch.ElapsedMilliseconds >= 1000) continue;
Thread.Sleep((int)(1000 - stopWatch.ElapsedMilliseconds));
}
if (stopWatch.ElapsedMilliseconds >= 2500) continue;
Thread.Sleep((int)(2500 - stopWatch.ElapsedMilliseconds));
}
說明
這段程式碼執行的時候,首選是獲取的最新的區塊高度(介面 getnowblock),然後逐個累加(介面 getblockbynum)區塊進行檢查,核對交易,根據自己的業務邏輯在 TODO 裡面進行處理。
我這裡使用了 Redis 獲取專案裡面使用者的地址與區塊的交易記錄進行比對。
其次程式碼裡面使用了 stopWatch 進行區塊時間的檢查,因為波場是3秒出一個區塊,但是由於伺服器可能會出現網路故障,這個時候會漏掉區塊,跟不上最新的高度,所以每次檢查是 2.5秒,這樣即使短時間斷網,監控程式很快就能追上最新的高度,保證能獲取到事實的交易資料。
其他依賴
PM> Install-Package Newtonsoft.Json
PM> Install-Package StackExchange.Redis
HttpClientHelper
請求波場主網需要用到 API-KEY 可以到 https://www.trongrid.io 進行申請
public static class HttpClientHelper {
public static string Get(string url, int timeout = 12000) {
var resp = Get((HttpWebRequest)WebRequest.Create(url), timeout);
using var s = resp.GetResponseStream();
using var sr = new StreamReader(s);
return sr.ReadToEnd();
}
private static HttpWebResponse Get(HttpWebRequest req, int timeout = 12000) {
req.Method = "GET";
req.ContentType = "application/json";
req.Timeout = timeout;
req.Accept = "application/json";
req.Headers.Set("TRON-PRO-API-KEY", "80a8b20f-a917-43a9-a2f1-809fe6eec0d6");
return (HttpWebResponse)req.GetResponse();
}
public static string Post(string url, string requestBody, Encoding encoding, int timeout = 12000) {
var resp = Post((HttpWebRequest)WebRequest.Create(url), requestBody, encoding, timeout);
using var s = resp.GetResponseStream();
using var sr = new StreamReader(s);
return sr.ReadToEnd();
}
private static HttpWebResponse Post(HttpWebRequest req, string requestBody, Encoding encoding, int timeout = 12000) {
var bs = encoding.GetBytes(requestBody);
req.Method = "POST";
req.ContentType = "application/json";
req.ContentLength = bs.Length;
req.Timeout = timeout;
req.Accept = "application/json";
req.Headers.Set("TRON-PRO-API-KEY", "80a8b20f-a917-43a9-a2f1-809fe6eec0d6");
using (var s = req.GetRequestStream()) {
s.Write(bs, 0, bs.Length);
}
return (HttpWebResponse)req.GetResponse();
}
}
RedisProvider
public class RedisProvider {
private readonly IDatabase _database = ConnectionMultiplexer.Connect("127.0.0.1:6379").GetDatabase();
private RedisProvider() { }
public static RedisProvider Instance { get; } = new RedisProvider();
public bool KeyExists(string key) {
return _database.KeyExists(key);
}
public bool StringSet(string key, string value) {
return _database.StringSet(key, value);
}
public string StringGet(string key) {
var value = _database.StringGet(key);
return value.IsNull ? string.Empty : value.ToString();
}
}
public class ThreadSleepException : Exception {
}
其他
波場(Tron)獲取錢包TRX、USDT餘額和剩餘頻寬、能量 - 筆記
波場(Tron)錢包設定多籤
波場(Tron)網頁版(本地)錢包開源
波場(Tron)專案常用工具分享
波場(Tron)離線簽名、廣播交易 - 筆記
波場(Tron)離線生成私鑰和地址 - 筆記