mexc api in Java



  • Hallo,

    ich verstehe Folgendes einfach net... https://mexcdevelop.github.io/apidocs/contract_v1_en/#authentication-method

    Ich verstehe nicht, was wie signiert und dann eingefügt werden muss, wann ich GET oder POST brauche, und insgesamt, wie die URL aufgebaut werden muss...

    Und das rechtsstehende Java-Schnipsel verstehe ich auch net.

    Könntet ihr in dem nachfolgenden Code mal gucken, was dort falsch ist?

    Die Ausgabe:

    t = 2024-02-09 13:10:41
    urlConn = sun.net.www.protocol.https.DelegateHttpsURLConnection:https://contract.mexc.com/api/v1/private/account/asset/currency=USDT
    line = {"success":false,"code":602,"message":"Signature verification failed!"}
    
    

    Code:

    import org.apache.commons.codec.binary.Hex;
    import org.apache.commons.collections4.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.*;
    import java.nio.charset.StandardCharsets;
    
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.text.SimpleDateFormat;
    import java.util.*;
    
    public class Bot {
    
        // get the request parameters are sorted in dictionary order, with & concatenated strings, POST should be a JSON string
        record SignVo(String reqTime, String accessKey, String secretKey, String requestParam) {
            public SignVo withRequestParam(String requestParam) {
                return new SignVo(reqTime(), accessKey(), secretKey(), requestParam);
            }
        }
    
        /**
         * Gets the get request parameter string
         *
         * @param param get/delete Request parameters map
         */
        public static String getRequestParamString(Map<String, String> param) {
            if (MapUtils.isEmpty(param)) {
                return "";
            }
            StringBuilder sb = new StringBuilder(1024);
            SortedMap<String, String> map = new TreeMap<>(param);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = StringUtils.isBlank(entry.getValue()) ? "" : entry.getValue();
                sb.append(key).append('=').append(urlEncode(value)).append('&');
            }
            sb.deleteCharAt(sb.length() - 1);
            return sb.toString();
        }
    
        public static String urlEncode(String s) {
            return URLEncoder.encode(s, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        }
    
        /**
         * signature
         */
        public static String sign(SignVo signVo) {
            if (signVo.requestParam() == null) {
                signVo = signVo.withRequestParam("");
            }
            String str = signVo.accessKey() + signVo.reqTime() + signVo.requestParam();
            return actualSignature(str, signVo.secretKey());
        }
    
        public static String actualSignature(String inputStr, String key) {
            Mac hmacSha256;
            try {
                hmacSha256 = Mac.getInstance("HmacSHA256");
                SecretKeySpec secKey =
                        new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
                hmacSha256.init(secKey);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("No such algorithm: " + e.getMessage());
            } catch (InvalidKeyException e) {
                throw new RuntimeException("Invalid key: " + e.getMessage());
            }
            byte[] hash = hmacSha256.doFinal(inputStr.getBytes(StandardCharsets.UTF_8));
            return Hex.encodeHexString(hash);
        }
    
        public static HttpURLConnection getUrlConn(String apiStr, Map<String, String> map) throws IOException {
            String ak = "...";
            String sk = "...";
            HashMap<String, String> map1 = new HashMap<>();
            //map1.put("access_key", ak);
            map1.putAll(map);
            String rp = getRequestParamString(map1);
            String t = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            System.out.println("t = " + t);
    
            URL url = URI.create("https://contract.mexc.com/" + apiStr + rp).toURL();
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            //urlConnection.setRequestProperty("api-key", sk);
            //urlConnection.setRequestProperty("t", t);
            urlConnection.setRequestProperty("sign", sign(new SignVo(t, ak, sk, rp)));
            urlConnection.setUseCaches(false);
            urlConnection.setDoInput(true);
            urlConnection.setDoOutput(false);
            return urlConnection;
        }
    
        public static void main(String[] args) throws IOException {
            URLConnection urlConn = getUrlConn("api/v1/private/account/asset/", Map.ofEntries(Map.entry("currency", "USDT")));
            System.out.println("urlConn = " + urlConn);
            urlConn.connect();
            try (BufferedReader br = new BufferedReader(new InputStreamReader(urlConn.getInputStream()))) {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println("line = " + line);
                }
            }
        }
    }
    
    

    Danke für jeden Hinweis.



  • Hm, weiter oben steht:

    The corresponding API accepts a request of Type GET, POST, or DELETE. The content-type of POST request is: application/JSON.

    Parameters are sent in JSON format (parameter naming rules are camel named), and get requests are sent in requestParam (parameter naming rules are '_' delimited)

    Das würde doch bedeuten:

    • POST requests brauchen einen json request body.
    • POST requests brauchen keine, außer den default ("Content-Type", "Content-Length"), request header.
    • json request body parameter names sollten im lowerCamelCase sein.

    Aber selbst, wenn ich das so umbaue, bekomme ich einen Code 602-Fehler zurück.

    Ich vermute langsam, etwas ist mit der Berechnung der Signatur falsch.

    Leider findet man dazu auch keine Beispiele im Internet... Vielleicht sollte ich bei den Entwicklern mal nachfragen via Telegramm?



  • @omggg sagte in mexc api in Java:

    urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

    für JSON ist das der falsche Enctype. Richtig wäre application/json

    Und Content-Length kannste erst berechnen wenn der JSON zusammengepappt ist.

    MFG



  • @_ro_ro sagte in mexc api in Java:

    für JSON ist das der falsche Enctype. Richtig wäre application/json
    Und Content-Length kannste erst berechnen wenn der JSON zusammengepappt ist.

    Das hatte ich schon verbessert... aber gleiches Ergebnis...

    For a POST reqeust, business parameters are to be put in request body and of JSON format(withcontent-typeset toapplication/json), they will not be used for signing of the request. To calculate signature, params api_key,req_time,recv_window(optional),sign need to be sent as path parameter

    Kurze Frage, was ist denn mit path parameter gemeint?

    Dass da kein Beispiel bei ist, macht mich wahnsinnig. 😞

    @_ro_ro Danke für das Sich-damit-Beschäftigen!



  • @ all : Zum Selber-Basteln:

    import org.apache.commons.codec.binary.Hex;
    import org.apache.commons.collections4.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.*;
    import java.net.*;
    import java.nio.charset.StandardCharsets;
    
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.text.SimpleDateFormat;
    import java.util.*;
    
    public class Bot {
    
        // get the request parameters are sorted in dictionary order, with & concatenated strings, POST should be a JSON string
        record SignVo(String reqTime, String accessKey, String secretKey, String requestParam) {
            public SignVo withRequestParam(String requestParam) {
                return new SignVo(reqTime(), accessKey(), secretKey(), requestParam);
            }
        }
    
        /**
         * Gets the get request parameter string
         *
         * @param param get/delete Request parameters map
         */
        public static String getRequestParamString(Map<String, String> param) {
            if (MapUtils.isEmpty(param)) {
                return "";
            }
            StringBuilder sb = new StringBuilder(1024);
            SortedMap<String, String> map = new TreeMap<>(param);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = StringUtils.isBlank(entry.getValue()) ? "" : entry.getValue();
                sb.append(key).append('=').append(urlEncode(value)).append('&');
            }
            sb.deleteCharAt(sb.length() - 1);
            return sb.toString();
        }
    
        public static String urlEncode(String s) {
            return URLEncoder.encode(s, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        }
    
        /**
         * signature
         */
        public static String sign(SignVo signVo) {
            if (signVo.requestParam() == null) {
                signVo = signVo.withRequestParam("");
            }
            String str = signVo.accessKey() + signVo.reqTime() + signVo.requestParam();
            return actualSignature(str, signVo.secretKey());
        }
    
        public static String actualSignature(String inputStr, String key) {
            Mac hmacSha256;
            try {
                hmacSha256 = Mac.getInstance("HmacSHA256");
                SecretKeySpec secKey =
                        new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
                hmacSha256.init(secKey);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("No such algorithm: " + e.getMessage());
            } catch (InvalidKeyException e) {
                throw new RuntimeException("Invalid key: " + e.getMessage());
            }
            byte[] hash = hmacSha256.doFinal(inputStr.getBytes(StandardCharsets.UTF_8));
            return Hex.encodeHexString(hash);
        }
    
        public static InputStream getInputStream(String apiStr, Map<String, String> map) throws IOException {
            //String t = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            //String t = System.currentTimeMillis() / 1000 + 5 + "";
            String t = System.currentTimeMillis() + "";
            System.out.println("t = " + t);
    
            String ak = "...";
            String sk = "...";
    
            SortedMap<String, String> treeMap = new TreeMap<>(map);
            treeMap.put("api_key", ak);
            treeMap.put("req_time", t);
    
            //String rp = getRequestParamString(treeMap);
    
            StringBuilder sb = new StringBuilder("{");
            treeMap.forEach((key, value) -> {
                sb.append("\"");
                sb.append(key);
                sb.append("\":\"");
                sb.append(value);
                sb.append("\",");
            });
            sb.deleteCharAt(sb.length() - 1);
            sb.append("}");
            String json = sb.toString();
    
            System.out.println("json = " + json);
            byte[] postData = json.getBytes(StandardCharsets.UTF_8);
            int postDataLength = postData.length;
    
            URL url = URI.create("https://contract.mexc.com/" + apiStr + sign(new SignVo(t, ak, sk, null))).toURL();
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Content-Type", "application/JSON");
            urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
            urlConnection.setUseCaches(false);
            urlConnection.setDoInput(true);
            urlConnection.setDoOutput(true);
            System.out.println("urlConnection = " + urlConnection);
    
            try (DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream())) {
                wr.write(postData);
            }
    
            return urlConnection.getInputStream();
        }
    
        public static void main(String[] args) throws IOException {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream("api/v1/private/account/asset/", Map.ofEntries(Map.entry("currency", "USDT")))))) {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println("line = " + line);
                }
            }
        }
    }
    


  • @omggg

    Path:
    https://datatracker.ietf.org/doc/html/rfc3986#page-22

    <scheme>://<authority><path>?<query>#<fragment>

    Ansonsten werde ich aus denen ihrer API-Beschreibung auch nicht schlau. Viel Erfolg weiterhin!

    MFG



  • @_ro_ro sagte in mexc api in Java:

    Ansonsten werde ich aus denen ihrer API-Beschreibung auch nicht schlau. Viel Erfolg weiterhin!

    Ich glaube langsam... die wolln gar nicht, dass man das verwendet.



  • @omggg

    ähm, was bringt eigentlich die Response? Den Kurs einer (Krypto)Währung? Und was kostet der Key? Broker-Software ist im Wucherpreis-Sektor angesiedelt 😉

    Ich würde die Finger davon lassen. MFG



  • Ich würde ja auch gerne deren SDK für Java nutzen, aber das funktioniert nur mit Spot.

    @_ro_ro sagte in mexc api in Java:

    Path:
    https://datatracker.ietf.org/doc/html/rfc3986#page-22
    <scheme>://<authority><path>?<query>#<fragment>

    Wenn ich nach dieser Definition gehe, dann müsst der Pfad so aussehen:

    https://contract.mexc.com/abcd.../api/v1/private/account/asset/{currency}

    das widerspricht "meiner" oder der "natürlichen" Intuition, imho.



  • @_ro_ro sagte in mexc api in Java:

    ähm, was bringt eigentlich die Response? Den Kurs einer (Krypto)Währung? Und was kostet der Key? Broker-Software ist im Wucherpreis-Sektor angesiedelt

    mein aktueller Kontostand

    der Key ist kostenlos, nach primärem KYC

    was meinst du mit Broker-Software? die schreibe ich ja gerade selber...



  • @omggg sagte in mexc api in Java:

    @_ro_ro sagte in mexc api in Java:

    ähm, was bringt eigentlich die Response? Den Kurs einer (Krypto)Währung? Und was kostet der Key? Broker-Software ist im Wucherpreis-Sektor angesiedelt

    mein aktueller Kontostand

    Oohh 😉

    der Key ist kostenlos, nach primärem KYC

    was meinst du mit Broker-Software? die schreibe ich ja gerade selber...

    Viel Erfolg;)



  • @_ro_ro sagte in mexc api in Java:

    Viel Erfolg;)

    Dein sarkastischer Kommentar ist destruktiv und hilft mir leider nicht weiter.



  • 🙈 Ich hab es hinbekommen.

    1. Es funktioniert mit einem einfachem GET-Request.
    2. POST-Requests funktionieren bei GET-Request nicht.
    3. Die API wird genauer unter Tab "SpotV2" beschrieben.
    
        public static InputStream getGETInputStream(String apiStr, Map<String, String> map) throws IOException {
            String t = java.time.Instant.now().getEpochSecond() * 1000L + "";
            System.out.println("t = " + t);
    
            String ak = "...";
            String sk = "...";
    
            URL url = URI.create("https://contract.mexc.com/" + apiStr).toURL();
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.setRequestProperty("ApiKey", ak);
            urlConnection.setRequestProperty("Request-Time", t);
            urlConnection.setRequestProperty("Signature", sign(new SignVo(t, ak, sk, null)));
            urlConnection.setUseCaches(false);
            urlConnection.setDoInput(true);
            urlConnection.setDoOutput(false);
            System.out.println("urlConnection = " + urlConnection);
    
            return urlConnection.getInputStream();
        }
    
        public static void main(String[] args) throws IOException {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(getGETInputStream("api/v1/private/account/asset/USDT", Map.ofEntries(Map.entry("currency", "USDT")))))) {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println("line = " + line);
                }
            }
        }
    
    

    ... Danke an alle, die dieses Thema (unnötigerweise) gelesen hatten.



  • Ohhhhhhhhhhhhhhhhhhhh, die negativen Bewertungen gehen wieder los... 😖



  • @omggg sagte in mexc api in Java:

    Ohhhhhhhhhhhhhhhhhhhh, die negativen Bewertungen gehen wieder los... 😖

    Entwickler von Brokersoftware sollten die Bedeutung von Bewertungen eigentlich kennen 😉

    MFG



  • Ja, hatte vergessen, dass hier keine infantile Nichtsnutze sind, sondern nur totale Experten...

    Ne, mal im Ernst, nach einer Begründung für die schlechte Bewertung will ich gar nicht erst fragen...



  • @omggg sagte in mexc api in Java:

    POST-Requests funktionieren bei GET-Request nicht.

    WTF. Mir sind jedoch schon Server untergekommen, denen konnte man mit Request-Method ZITRONE einen Message-Body unterjubeln. Und tatsächlich steht im RFC, daß hierfür nicht die Request-Method ausschlaggebend ist sondern der Header Content-Length. So ist ein POST mit Content-Length: 0 genauso blödsinnig wie die Annahme daß es einen Message-Body mit Request-Method GET nicht gibt.

    MFG



  • Ich weiß nicht genau, worauf du hinaus willst... ich bezog mich auf die API... nicht auf REST im Allgemeinen.

    Aber ist doch jetzt gut, es funktioniert doch.

    Vielleicht könnte dieses Thema gelöscht werden, wenn es stört.



  • @omggg sagte in mexc api in Java:

    Ich weiß nicht genau, worauf du hinaus willst... ich bezog mich auf die API... nicht auf REST im Allgemeinen.

    Einen Key im URL übertragen ist eine ganz schlechte Idee.

    Aber ist doch jetzt gut, es funktioniert doch.

    Verschlüsselung funktioniert nur mit dem Inhalt. Also Header, Body. Also POST und nicht GET. Es sind DEINE Daten.

    MFG



  • @_ro_ro sagte in mexc api in Java:

    Es sind DEINE Daten.

    Der Ansicht bin ich auch. 🙃 Und morgen wird schönes Wetter ... 🙃


    Glaube aber, ich komme gerade nicht weiter...

    json = {"leverage":20,"symbol":"BTC_USDT","side":1,"vol":"0.0005","price":47500,"type":1,"openType":1}
    Fri Feb 09 21:33:10 CET 2024
    {
      "code": 1002,
      "success": false,
      "message": "Contract not allow place order!"
    }
    
        public static InputStream getPOSTInputStream(String apiStr, Map<String, Object> map) throws IOException {
            String t = java.time.Instant.now().getEpochSecond() * 1000L + "";
    
            SortedMap<String, Object> treeMap = new TreeMap<>(map);
            JSONObject jo = new JSONObject();
            treeMap.forEach(jo::put);
            double vol = (double) treeMap.remove("vol");
            jo.put("vol", "0.0005");
    
            String json = jo.toString();
            System.out.println("json = " + json);
    
            byte[] postData = json.getBytes(StandardCharsets.UTF_8);
            int postDataLength = postData.length;
    
            URL url = URI.create("https://contract.mexc.com/" + apiStr).toURL();
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Content-Type", "application/json");
            urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
            urlConnection.setRequestProperty("ApiKey", ak);
            urlConnection.setRequestProperty("Request-Time", t);
            urlConnection.setRequestProperty("Signature", sign(new SignVo(t, ak, sk, json)));
            urlConnection.setUseCaches(false);
            urlConnection.setDoInput(true);
            urlConnection.setDoOutput(true);
    
            try (DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream())) {
                wr.write(postData);
            }
    
            return urlConnection.getInputStream();
        }
    

    Was ist denn nun wieder falsch? Die sign. scheint richtig zu sein (sonst gäbe es einen anderen Fehler), aber ich darf anscheinend keine order erstellen.

    Liegt das an "Order (Under maintenance)"?


Anmelden zum Antworten