axiom-checker/debank.go
2026-01-21 04:17:41 +02:00

327 lines
7.1 KiB
Go

package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
"math/big"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
type DeBankToken struct {
Symbol string `json:"symbol"`
Chain string `json:"chain"`
Balance string `json:"balance"`
Decimals int `json:"decimals"`
Price float64 `json:"price"`
Amount float64 `json:"amount"`
}
type DeBankResponse struct {
Data []DeBankToken `json:"data"`
}
var networks = map[string]string{
"eth": "Ethereum",
"bsc": "BSC",
"avax": "Avalanche",
"matic": "Polygon",
"arb": "Arbitrum",
"ftm": "Fantom",
"op": "Optimism",
"cro": "Cronos",
}
type DebankClient struct {
account *BrowserUID
nonce *DebankNonce
signer *DebankSigner
client *http.Client
}
func NewDebankClient(transport *http.Transport) *DebankClient {
if transport == nil {
transport = &http.Transport{}
}
account := makeBrowserUID()
return &DebankClient{
account: &account,
nonce: NewDebankNonce(),
signer: &DebankSigner{},
client: &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
},
}
}
func (d *DebankClient) Get(pathname string, params map[string]string) ([]byte, error) {
return d.request("GET", pathname, params)
}
func (d *DebankClient) request(method, pathname string, params map[string]string) ([]byte, error) {
values := url.Values{}
for k, v := range params {
values.Add(k, v)
}
keys := make([]string, 0, len(values))
for k := range values {
keys = append(keys, k)
}
sort.Strings(keys)
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
keys[i], keys[j] = keys[j], keys[i]
}
var queryParts []string
for _, k := range keys {
queryParts = append(queryParts, url.QueryEscape(k)+"="+url.QueryEscape(values.Get(k)))
}
queryString := strings.Join(queryParts, "&")
apiURL := "https://api.debank.com" + pathname
if queryString != "" {
apiURL += "?" + queryString
}
req, err := http.NewRequest(method, apiURL, nil)
if err != nil {
return nil, err
}
headers := d.makeHeaders(method, pathname, queryString)
for key, value := range headers {
req.Header.Set(key, value)
}
resp, err := d.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("debank API error: status %d, body: %s", resp.StatusCode, string(body))
}
var errorResp struct {
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
}
if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.ErrorCode != 0 {
return nil, fmt.Errorf("%d: %s", errorResp.ErrorCode, errorResp.ErrorMsg)
}
return body, nil
}
func (d *DebankClient) makeHeaders(method, pathname, query string) map[string]string {
if len(query) > 0 && query[0] == '?' {
query = query[1:]
}
nonceStr := d.nonce.Next()
ts := time.Now().Unix()
signature := d.signer.Sign(method, pathname, query, nonceStr, ts)
accountJSON, _ := json.Marshal(d.account)
return map[string]string{
"x-api-nonce": "n_" + nonceStr,
"x-api-sign": signature,
"x-api-ts": strconv.FormatInt(ts, 10),
"x-api-ver": "v2",
"source": "web",
"account": string(accountJSON),
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://debank.com/",
"Origin": "https://debank.com",
}
}
type BrowserUID struct {
RandomAt int64 `json:"random_at"`
RandomID string `json:"random_id"`
UserAddr *string `json:"user_addr"`
}
type DebankNonce struct {
abc string
local int64
}
func NewDebankNonce() *DebankNonce {
return &DebankNonce{
abc: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",
local: 0,
}
}
func (n *DebankNonce) pcg32() uint32 {
const multiplier = 6364136223846793005
const increment = 1
n.local = n.local*multiplier + increment
return uint32((uint64(n.local) >> 33) & 0xFFFFFFFF)
}
func (n *DebankNonce) Next() string {
result := make([]byte, 40)
for i := 0; i < 40; i++ {
index := int(float64(n.pcg32()) / 2147483647.0 * 61.0)
if index >= len(n.abc) {
index = len(n.abc) - 1
}
result[i] = n.abc[index]
}
return string(result)
}
type DebankSigner struct{}
func (s *DebankSigner) Sign(method, pathname, query string, nonce string, ts int64) string {
data1 := method + "\n" + pathname + "\n" + query
hash1 := sha256.Sum256([]byte(data1))
hash1Hex := hex.EncodeToString(hash1[:])
data2 := "debank-api\nn_" + nonce + "\n" + strconv.FormatInt(ts, 10)
hash2 := sha256.Sum256([]byte(data2))
hash2Hex := hex.EncodeToString(hash2[:])
xor1, xor2 := s.xor(hash2Hex)
h1Data := xor1 + hash1Hex
h1 := sha256.Sum256([]byte(h1Data))
h2Data := append([]byte(xor2), h1[:]...)
h2 := sha256.Sum256(h2Data)
return hex.EncodeToString(h2[:])
}
func (s *DebankSigner) xor(hash string) (string, string) {
rez1 := make([]byte, 64)
rez2 := make([]byte, 64)
for i := 0; i < 64; i++ {
rez1[i] = hash[i] ^ 54
rez2[i] = hash[i] ^ 92
}
return string(rez1), string(rez2)
}
func makeBrowserUID() BrowserUID {
return BrowserUID{
RandomAt: time.Now().Unix(),
RandomID: strings.ReplaceAll(generateUUID(), "-", ""),
UserAddr: nil,
}
}
func generateUUID() string {
b := make([]byte, 16)
rand.Read(b)
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
type DebankUser struct {
ID string `json:"id"`
Desc struct {
BornAt int64 `json:"born_at"`
USDValue float64 `json:"usd_value"`
} `json:"desc"`
Stats struct {
USDValue float64 `json:"usd_value"`
} `json:"stats"`
}
type DebankUserResponse struct {
User DebankUser `json:"user"`
}
func (d *DebankClient) GetUser(address string) (*DebankUserResponse, error) {
body, err := d.Get("/user", map[string]string{
"id": address,
})
if err != nil {
return nil, err
}
var wrapper struct {
Data DebankUserResponse `json:"data"`
}
if err := json.Unmarshal(body, &wrapper); err != nil {
return nil, err
}
return &wrapper.Data, nil
}
func getDeBankBalance(address string) (float64, string, error) {
transport, err := getNextProxy()
if err != nil {
transport = &http.Transport{}
}
client := NewDebankClient(transport)
userResp, err := client.GetUser(address)
if err != nil {
return 0, "", fmt.Errorf("debank API error: %v", err)
}
totalValue := userResp.User.Desc.USDValue
if totalValue == 0 {
totalValue = userResp.User.Stats.USDValue
}
if totalValue == 0 {
return 0, "", nil
}
return totalValue, "", nil
}
func getBalanceValue(balance string, decimals int) float64 {
if balance == "" {
return 0
}
bal := new(big.Int)
bal.SetString(balance, 10)
if bal.Sign() == 0 {
return 0
}
divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil))
balFloat := new(big.Float).SetInt(bal)
result := new(big.Float).Quo(balFloat, divisor)
resultFloat, _ := result.Float64()
return math.Round(resultFloat*100000) / 100000
}