304 lines
7.2 KiB
Go
304 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"math/big"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
"usdtman"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var (
|
|
uman *usdtman.USDTMan
|
|
paymentEvents []usdtman.USDTPayment
|
|
paymentLock sync.RWMutex
|
|
upgrader = websocket.Upgrader{
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
}
|
|
clients = make(map[*websocket.Conn]bool)
|
|
clientsLock sync.RWMutex
|
|
)
|
|
|
|
func main() {
|
|
port := os.Getenv("PORT")
|
|
if port == "" {
|
|
port = "8084"
|
|
}
|
|
|
|
apiKey := os.Getenv("TRON_API_KEY")
|
|
if apiKey == "" {
|
|
apiKey = "da1e77dc-b35b-4458-846a-5a551b9df4b2"
|
|
}
|
|
|
|
// 初始化 USDTMan
|
|
proxyURL := os.Getenv("PROXY_URL") // 例如: http://127.0.0.1:7890
|
|
|
|
config := usdtman.Config{
|
|
Addresses: []string{}, // 初始无地址,可通过 API 添加
|
|
APIKey: apiKey,
|
|
QueryInterval: 5 * time.Second, // 每 5 秒查询一次
|
|
MinConfirmations: 6, // 6 个确认
|
|
MaxHistoryTxns: 20, // 每次查询最多 20 条历史交易
|
|
}
|
|
|
|
// 如果设置了代理
|
|
if proxyURL != "" {
|
|
config.ProxyURL = proxyURL
|
|
log.Printf("✅ 使用代理: %s", proxyURL)
|
|
}
|
|
|
|
uman = usdtman.NewUSDTMan(config)
|
|
|
|
// 设置收款回调
|
|
uman.OnPaymentComplete(func(payment *usdtman.USDTPayment) {
|
|
log.Printf("💰 收到 USDT: %s -> %s USDT (确认数: %d, TxID: %s)",
|
|
payment.From, payment.GetAmountString(), payment.Confirmations, payment.TxID)
|
|
|
|
paymentLock.Lock()
|
|
paymentEvents = append(paymentEvents, *payment)
|
|
if len(paymentEvents) > 100 {
|
|
paymentEvents = paymentEvents[len(paymentEvents)-100:]
|
|
}
|
|
paymentLock.Unlock()
|
|
|
|
broadcastPayment(payment)
|
|
})
|
|
|
|
// test code
|
|
uman.AddAddress("TWwGSYwpSzT6GTBr4AQw9QF6m4VVui3UGc") // tronlink trc20 gasfree 地址
|
|
uman.Start()
|
|
|
|
http.HandleFunc("/start", startMonitor)
|
|
http.HandleFunc("/stop", stopMonitor)
|
|
http.HandleFunc("/add-address", addAddress)
|
|
http.HandleFunc("/remove-address", removeAddress)
|
|
http.HandleFunc("/list-addresses", listAddresses)
|
|
http.HandleFunc("/payments", getPayments)
|
|
http.HandleFunc("/query", queryTransactions)
|
|
http.HandleFunc("/ws", handleWebSocket)
|
|
http.HandleFunc("/", serveIndex)
|
|
|
|
log.Printf("🚀 USDTMan Server running on :%s", port)
|
|
log.Fatal(http.ListenAndServe(":"+port, nil))
|
|
}
|
|
|
|
func jsonResponse(w http.ResponseWriter, success bool, message string, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": success,
|
|
"message": message,
|
|
"data": data,
|
|
})
|
|
}
|
|
|
|
func startMonitor(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
jsonResponse(w, false, "Method not allowed", nil)
|
|
return
|
|
}
|
|
|
|
if err := uman.Start(); err != nil {
|
|
jsonResponse(w, false, fmt.Sprintf("启动失败: %v", err), nil)
|
|
return
|
|
}
|
|
|
|
jsonResponse(w, true, "监听已启动", nil)
|
|
}
|
|
|
|
func stopMonitor(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
jsonResponse(w, false, "Method not allowed", nil)
|
|
return
|
|
}
|
|
|
|
uman.Stop()
|
|
jsonResponse(w, true, "监听已停止", nil)
|
|
}
|
|
|
|
func addAddress(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
jsonResponse(w, false, "Method not allowed", nil)
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonResponse(w, false, "invalid request", nil)
|
|
return
|
|
}
|
|
|
|
if req.Address == "" {
|
|
jsonResponse(w, false, "address is required", nil)
|
|
return
|
|
}
|
|
|
|
uman.AddAddress(req.Address)
|
|
jsonResponse(w, true, "地址已添加", map[string]interface{}{
|
|
"address": req.Address,
|
|
})
|
|
}
|
|
|
|
func removeAddress(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
jsonResponse(w, false, "Method not allowed", nil)
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonResponse(w, false, "invalid request", nil)
|
|
return
|
|
}
|
|
|
|
uman.RemoveAddress(req.Address)
|
|
jsonResponse(w, true, "地址已移除", map[string]interface{}{
|
|
"address": req.Address,
|
|
})
|
|
}
|
|
|
|
func listAddresses(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
jsonResponse(w, false, "Method not allowed", nil)
|
|
return
|
|
}
|
|
|
|
addresses := uman.GetAddresses()
|
|
jsonResponse(w, true, "success", map[string]interface{}{
|
|
"addresses": addresses,
|
|
"count": len(addresses),
|
|
})
|
|
}
|
|
|
|
func getPayments(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
jsonResponse(w, false, "Method not allowed", nil)
|
|
return
|
|
}
|
|
|
|
paymentLock.RLock()
|
|
events := make([]usdtman.USDTPayment, len(paymentEvents))
|
|
copy(events, paymentEvents)
|
|
paymentLock.RUnlock()
|
|
|
|
jsonResponse(w, true, "success", map[string]interface{}{
|
|
"payments": events,
|
|
"count": len(events),
|
|
})
|
|
}
|
|
|
|
func queryTransactions(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
jsonResponse(w, false, "Method not allowed", nil)
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Address string `json:"address"`
|
|
StartTime int64 `json:"startTime"` // 毫秒时间戳
|
|
EndTime int64 `json:"endTime"` // 毫秒时间戳
|
|
MinAmount string `json:"minAmount"` // 字符串格式的金额
|
|
MinConfirmations int64 `json:"minConfirmations"` // 最小确认数
|
|
Limit int `json:"limit"` // 查询数量
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonResponse(w, false, "invalid request", nil)
|
|
return
|
|
}
|
|
|
|
if req.Address == "" {
|
|
jsonResponse(w, false, "address is required", nil)
|
|
return
|
|
}
|
|
|
|
filter := usdtman.QueryFilter{
|
|
Address: req.Address,
|
|
StartTime: req.StartTime,
|
|
EndTime: req.EndTime,
|
|
MinConfirmations: req.MinConfirmations,
|
|
Limit: req.Limit,
|
|
}
|
|
|
|
// 解析最小金额
|
|
if req.MinAmount != "" {
|
|
minAmount := new(big.Int)
|
|
if _, ok := minAmount.SetString(req.MinAmount, 10); ok {
|
|
filter.MinAmount = minAmount
|
|
}
|
|
}
|
|
|
|
payments, err := uman.QueryTransactions(filter)
|
|
if err != nil {
|
|
jsonResponse(w, false, fmt.Sprintf("查询失败: %v", err), nil)
|
|
return
|
|
}
|
|
|
|
jsonResponse(w, true, "success", map[string]interface{}{
|
|
"payments": payments,
|
|
"count": len(payments),
|
|
})
|
|
}
|
|
|
|
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Println("WebSocket upgrade error:", err)
|
|
return
|
|
}
|
|
|
|
clientsLock.Lock()
|
|
clients[conn] = true
|
|
clientsLock.Unlock()
|
|
|
|
defer func() {
|
|
clientsLock.Lock()
|
|
delete(clients, conn)
|
|
clientsLock.Unlock()
|
|
conn.Close()
|
|
}()
|
|
|
|
for {
|
|
_, _, err := conn.ReadMessage()
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func broadcastPayment(payment *usdtman.USDTPayment) {
|
|
message := map[string]interface{}{
|
|
"type": "usdt_payment",
|
|
"address": payment.Address,
|
|
"amount": payment.GetAmountFloat(),
|
|
"amountRaw": payment.Amount.String(),
|
|
"from": payment.From,
|
|
"txId": payment.TxID,
|
|
"block": payment.BlockNumber,
|
|
"time": payment.Timestamp,
|
|
"confirmations": payment.Confirmations,
|
|
}
|
|
|
|
clientsLock.RLock()
|
|
defer clientsLock.RUnlock()
|
|
|
|
for conn := range clients {
|
|
conn.WriteJSON(message)
|
|
}
|
|
}
|
|
|
|
func serveIndex(w http.ResponseWriter, r *http.Request) {
|
|
http.ServeFile(w, r, "index.html")
|
|
}
|