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 }