Binance API- Passing Security (Header or body)

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
usdarkejo
Posts: 6
Joined: 15 Jul 2016, 17:13

Binance API- Passing Security (Header or body)

18 May 2018, 12:12

Hi All,

I've been working on this for passed 2 days. Here is the code I have so far. From what I'm reading, this should work, but I'm sure there is something I need to modify. What I'm trying to do is pull in all my account information. The official API documentation is located here: https://github.com/binance-exchange/bin ... est-api.md

Before anyone asks if I've read the documentation or researched on my own, know that the majority of the code I have was pulled from someone elses custom AHK code that works flawlessly on another exchange. I have searched, searched, and searched some more. I have also checked all my variables and they seem to be assigned correctly.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.



oHTTP:=ComObjCreate("WinHttp.WinHttpRequest.5.1")	
apikey = "Changed For Forum Post"
secret = "Changed For Forum Post"

postdata := "TimeStamp=" a_now
sign := HMAC(secret, postdata, "sha512")

url := "https://api.binance.com/api/v3/account"



oHTTP.Open("GET", URL , False)	;Post request
oHTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
oHTTP.SetRequestHeader("Key", apikey)
oHTTP.SetRequestHeader("Sign", sign)
oHTTP.Send(PostData)	;Send POST request

response := oHTTP.ResponseText
msgbox % response
return

;#########################################
;#########################################

; HMAC ==============================================================================
HMAC(Key, Message, Algo := "MD5")
{
    static Algorithms := {MD2:    {ID: 0x8001, Size:  64}
                        , MD4:    {ID: 0x8002, Size:  64}
                        , MD5:    {ID: 0x8003, Size:  64}
                        , SHA:    {ID: 0x8004, Size:  64}
                        , SHA256: {ID: 0x800C, Size:  64}
                        , SHA384: {ID: 0x800D, Size: 128}
                        , SHA512: {ID: 0x800E, Size: 128}}
    static iconst := 0x36
    static oconst := 0x5C
    if (!(Algorithms.HasKey(Algo)))
    {
        return ""
    }
    Hash := KeyHashLen := InnerHashLen := ""
    HashLen := 0
    AlgID := Algorithms[Algo].ID
    BlockSize := Algorithms[Algo].Size
    MsgLen := StrPut(Message, "UTF-8") - 1
    KeyLen := StrPut(Key, "UTF-8") - 1
    VarSetCapacity(K, KeyLen + 1, 0)
    StrPut(Key, &K, KeyLen, "UTF-8")
    if (KeyLen > BlockSize)
    {
        CalcAddrHash(&K, KeyLen, AlgID, KeyHash, KeyHashLen)
    }

    VarSetCapacity(ipad, BlockSize + MsgLen, iconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ iconst, ipad, i, "UChar")
        i++
    }
    if (MsgLen)
    {
        StrPut(Message, &ipad + BlockSize, MsgLen, "UTF-8")
    }
    CalcAddrHash(&ipad, BlockSize + MsgLen, AlgID, InnerHash, InnerHashLen)

    VarSetCapacity(opad, BlockSize + InnerHashLen, oconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ oconst, opad, i, "UChar")
        i++
    }
    Addr := &opad + BlockSize
    i := 0
    while (i < InnerHashLen)
    {
        NumPut(NumGet(InnerHash, i, "UChar"), Addr + i, 0, "UChar")
        i++
    }
    return CalcAddrHash(&opad, BlockSize + InnerHashLen, AlgID)
}


; MD2 ===============================================================================
MD2(string, encoding = "UTF-8")
{
    return CalcStringHash(string, 0x8001, encoding)
}
; MD4 ===============================================================================
MD4(string, encoding = "UTF-8")
{
    return CalcStringHash(string, 0x8002, encoding)
}
; MD5 ===============================================================================
MD5(string, encoding = "UTF-8")
{
    return CalcStringHash(string, 0x8003, encoding)
}
; SHA ===============================================================================
SHA(string, encoding = "UTF-8")
{
    return CalcStringHash(string, 0x8004, encoding)
}
; SHA256 ============================================================================
SHA256(string, encoding = "UTF-8")
{
    return CalcStringHash(string, 0x800c, encoding)
}
; SHA384 ============================================================================
SHA384(string, encoding = "UTF-8")
{
    return CalcStringHash(string, 0x800d, encoding)
}
; SHA512 ============================================================================
SHA512(string, encoding = "UTF-8")
{
    return CalcStringHash(string, 0x800e, encoding)
}

; CalcAddrHash ======================================================================
CalcAddrHash(addr, length, algid, byref hash = 0, byref hashlength = 0)
{
    static h := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"]
    static b := h.minIndex()
    hProv := hHash := o := ""
    if (DllCall("advapi32\CryptAcquireContext", "Ptr*", hProv, "Ptr", 0, "Ptr", 0, "UInt", 24, "UInt", 0xf0000000))
    {
        if (DllCall("advapi32\CryptCreateHash", "Ptr", hProv, "UInt", algid, "UInt", 0, "UInt", 0, "Ptr*", hHash))
        {
            if (DllCall("advapi32\CryptHashData", "Ptr", hHash, "Ptr", addr, "UInt", length, "UInt", 0))
            {
                if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", 0, "UInt*", hashlength, "UInt", 0))
                {
                    VarSetCapacity(hash, hashlength, 0)
                    if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", &hash, "UInt*", hashlength, "UInt", 0))
                    {
                        loop % hashlength
                        {
                            v := NumGet(hash, A_Index - 1, "UChar")
                            o .= h[(v >> 4) + b] h[(v & 0xf) + b]
                        }
                    }
                }
            }
            DllCall("advapi32\CryptDestroyHash", "Ptr", hHash)
        }
        DllCall("advapi32\CryptReleaseContext", "Ptr", hProv, "UInt", 0)
    }
    return o
}

; CalcStringHash ====================================================================
CalcStringHash(string, algid, encoding = "UTF-8", byref hash = 0, byref hashlength = 0)
{
    chrlength := (encoding = "CP1200" || encoding = "UTF-16") ? 2 : 1
    length := (StrPut(string, encoding) - 1) * chrlength
    VarSetCapacity(data, length, 0)
    StrPut(string, &data, floor(length / chrlength), encoding)
    return CalcAddrHash(&data, length, algid, hash, hashlength)
}
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Binance API- Passing Security (Header or body)

18 May 2018, 12:48

Perhaps you need colons here:

Code: Select all

apikey := "Changed For Forum Post"
secret := "Changed For Forum Post"
Otherwise posting the error message/response text might give a clue.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
usdarkejo
Posts: 6
Joined: 15 Jul 2016, 17:13

Re: Binance API- Passing Security (Header or body)

18 May 2018, 12:57

Hi Jeeswg,

Surprisingly, that did seem to move me in the right direction. Initially, I was receiving "The request could not be satisfied". Adding the colons as you suggested did not change the outcome, but when I removed 'PostData' from line oHttp.Send(PostData)' -- as some people have suggested to identify error messages -- I received the message, "Mandatory Parameter 'timestamp' was not sent.". Now I'm wondering if maybe the format for the time sent (A_now) to the timestamp query string is not formatted correctly. I will keep playing around, but if you see I'm heading down the wrong path, please let me know.

Code: Select all

postdata := "TimeStamp=" a_now
sign := HMAC(secret, postdata, "sha512")
url := "https://api.binance.com/api/v3/account"

oHTTP.Open("GET", URL , False)	;Post request
oHTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
oHTTP.SetRequestHeader("Key", apikey)
oHTTP.SetRequestHeader("Sign", sign)
oHTTP.Send(PostData)	;Send POST request
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Binance API- Passing Security (Header or body)

18 May 2018, 13:12

I don't know Binance, I looked up Binance and timestamp, and I believe that this might work:

Code: Select all

;based on:
;convert common date formats (Excel / FileTime / Unix) - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=44727

q:: ;AHK time to Unix time (milliseconds)
;vDate := DateDiff(A_Now, 1970, "Seconds") * 1000
vDate := DateDiff(A_NowUTC, 1970, "Seconds") * 1000
MsgBox, % vDate
return

DateDiff(DateTime1, DateTime2, TimeUnits)
{
    EnvSub DateTime1, %DateTime2%, %TimeUnits%
    return DateTime1
}
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
usdarkejo
Posts: 6
Joined: 15 Jul 2016, 17:13

Re: Binance API- Passing Security (Header or body)

18 May 2018, 13:19

I thought we were on to something. I have verified the new variable is in milliseconds using your code above, modified the timestamp parameter and still receive the same error message.
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: Binance API- Passing Security (Header or body)

18 May 2018, 23:59

Fear not, young api-rentice :) ! You have searched and I have found!

While pulling some working code from somewhere else can surely help, there are almost every time some differences and you will have to read very closely (and there is often room for interpretation). With that said, the Binance API docs are not good, but I have seen much worse :roll: . In the working code (see below) I used the following code (first two you already know):

* jeeswg's Unix-Time-function (much smaller than my version, thanks for that!),
* the same HMAC function you used, I think - I haven't really compared, but just copied the version I always used successfully (I think, it was made by forum member 'just me' back in the day)
* and finally - and optionally - Coco's JSON Class (https://autohotkey.com/boards/viewtopic.php?t=627) to parse the response string (not necessary, but a huge help with JSON data - otherwise, you will have to do a lot of parsing) - this library, I included through the #include directive in the first line. Just download JSON.ahk and put it in the same folder and it should work.

Some remarks what went wrong with your code: You were creating a sha512 HMAC hash, while the binance API docs advise you to use a sha256 HMAC hash - that breaks the whole request early on beyond repair... The timestamp needs to be UNIX UTC in milliseconds (not very clear in the docs, but quite common for APIs), but that was fixed by jeeswg. Then, you have some comments which mention a POST request, although you are using a GET request - which is correct in this case:

Code: Select all

Account information (USER_DATA)
GET /api/v3/account 							(HMAC SHA256)						Get current account information.
Weight: 5

Parameters:
Name 					Type 		Mandatory 			Description
------------------------------------------------------------
recvWindow 				LONG 		NO 	
timestamp 				LONG 		YES 	
But unfortunately, you didn't follow these crucial hints from the docs:
For GET endpoints, parameters must be sent as a query string.
[...]
API-keys are passed into the Rest API via the X-MBX-APIKEY header.
You are trying to send the parameters in the request body instead. Also, the header name for the API key is wrong, and the signature needs to be part of the query string here, not a header (other APIs will handle this different). Judging by the thread title, you already had a suspicion...
That is all excusable, if you are not used to work with APIs. But these are other breaking points in your script.
I admit, without the examples from the binance docs (and a little experience), I would have needed much more time myself... :shh:

Code: Select all

#include JSON.ahk		; https://autohotkey.com/boards/viewtopic.php?t=627  optional, but highly recommended

APIKey := "API key here"
SecretKey := "API secret here"

queryString := "timestamp=" DateDiff(A_NowUTC, 1970, "Seconds") * 1000
signature :=HMAC(SecretKey , queryString, "SHA256")				; not 512 !!

oHTTP:=ComObjCreate("WinHttp.WinHttpRequest.5.1")	
url := "https://api.binance.com/api/v3/account?" queryString "&signature=" signature		; binance GET requests need all info in a query string

oHTTP.Open("GET", url , False)													;GET request
; oHTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")		; only needed for binance POST, PUT and DELETE endpoints 
oHTTP.SetRequestHeader("X-MBX-APIKEY", APIKey)							; see binance API docs: "X-MBX-APIKEY" header for API key
oHTTP.Send()															; no request body here
response := oHTTP.ResponseText											; response as one huge string

msgbox % response

/*			example JSON response from the binance API docs for this request
{
  "makerCommission": 15,
  "takerCommission": 15,
  "buyerCommission": 0,
  "sellerCommission": 0,
  "canTrade": true,
  "canWithdraw": true,
  "canDeposit": true,
  "updateTime": 123456789,
  "balances": [
    {
      "asset": "BTC",
      "free": "4723846.89208129",
      "locked": "0.00000000"
    },
    {
      "asset": "LTC",
      "free": "4763368.68006011",
      "locked": "0.00000000"
    }
  ]
}
*/

; Example how to parse the JSON response. This will only work, if you included Coco's JSON library:
obj := json.load(response)
msgbox % "makerCommission: " obj.makerCommission "`ntakerCommission: " obj.makerCommission "`ncanTrade: " obj.canTrade  "       (1 = true)`n`netc. pp."
ExitApp
;--------------------------
Esc::ExitApp
;-------------------------------------------------------------------------------------------------------------------------------------------
; provided by jeeswg ;based on:
;convert common date formats (Excel / FileTime / Unix) - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=44727
DateDiff(DateTime1, DateTime2, TimeUnits)
{
    EnvSub DateTime1, %DateTime2%, %TimeUnits%
    return DateTime1
}
;-------------------------------------------------------------------------------------------------------------------------------------------
; Hash functions by just me (?)
HMAC(Key, Message, Algo := "MD5")
{
    static Algorithms := {MD2:    {ID: 0x8001, Size:  64}
                        , MD4:    {ID: 0x8002, Size:  64}
                        , MD5:    {ID: 0x8003, Size:  64}
                        , SHA:    {ID: 0x8004, Size:  64}
                        , SHA256: {ID: 0x800C, Size:  64}
                        , SHA384: {ID: 0x800D, Size: 128}
                        , SHA512: {ID: 0x800E, Size: 128}}
    static iconst := 0x36
    static oconst := 0x5C
    if (!(Algorithms.HasKey(Algo)))
    {
        return ""
    }
    Hash := KeyHashLen := InnerHashLen := ""
    HashLen := 0
    AlgID := Algorithms[Algo].ID
    BlockSize := Algorithms[Algo].Size
    MsgLen := StrPut(Message, "UTF-8") - 1
    KeyLen := StrPut(Key, "UTF-8") - 1
    VarSetCapacity(K, KeyLen + 1, 0)
    StrPut(Key, &K, KeyLen, "UTF-8")
    if (KeyLen > BlockSize)
    {
        CalcAddrHash(&K, KeyLen, AlgID, KeyHash, KeyHashLen)
    }

    VarSetCapacity(ipad, BlockSize + MsgLen, iconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ iconst, ipad, i, "UChar")
        i++
    }
    if (MsgLen)
    {
        StrPut(Message, &ipad + BlockSize, MsgLen, "UTF-8")
    }
    CalcAddrHash(&ipad, BlockSize + MsgLen, AlgID, InnerHash, InnerHashLen)

    VarSetCapacity(opad, BlockSize + InnerHashLen, oconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ oconst, opad, i, "UChar")
        i++
    }
    Addr := &opad + BlockSize
    i := 0
    while (i < InnerHashLen)
    {
        NumPut(NumGet(InnerHash, i, "UChar"), Addr + i, 0, "UChar")
        i++
    }
    return CalcAddrHash(&opad, BlockSize + InnerHashLen, AlgID)
}

; CalcAddrHash ======================================================================
CalcAddrHash(addr, length, algid, byref hash = 0, byref hashlength = 0)
{
    static h := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"]
    static b := h.minIndex()
    hProv := hHash := o := ""
    if (DllCall("advapi32\CryptAcquireContext", "Ptr*", hProv, "Ptr", 0, "Ptr", 0, "UInt", 24, "UInt", 0xf0000000))
    {
        if (DllCall("advapi32\CryptCreateHash", "Ptr", hProv, "UInt", algid, "UInt", 0, "UInt", 0, "Ptr*", hHash))
        {
            if (DllCall("advapi32\CryptHashData", "Ptr", hHash, "Ptr", addr, "UInt", length, "UInt", 0))
            {
                if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", 0, "UInt*", hashlength, "UInt", 0))
                {
                    VarSetCapacity(hash, hashlength, 0)
                    if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", &hash, "UInt*", hashlength, "UInt", 0))
                    {
                        loop % hashlength
                        {
                            v := NumGet(hash, A_Index - 1, "UChar")
                            o .= h[(v >> 4) + b] h[(v & 0xf) + b]
                        }
                    }
                }
            }
            DllCall("advapi32\CryptDestroyHash", "Ptr", hHash)
        }
        DllCall("advapi32\CryptReleaseContext", "Ptr", hProv, "UInt", 0)
    }
    return o
}
Don't forget to include your API keys. It seems, POST requests can be done with the query string method, too, but I haven't looked closely so far.

Let us know, if this worked (it should, if I didn't make any bad copy-paste-errors) and if you need further help. Good luck!
usdarkejo
Posts: 6
Joined: 15 Jul 2016, 17:13

Re: Binance API- Passing Security (Header or body)

19 May 2018, 05:23

Thank you, Gregster! Great explanation as well - That worked like a charm. If only they would post more examples in their documentation because as you said, their documentation is not good. :-(
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Binance API- Passing Security (Header or body)

20 May 2018, 13:34

- Nicely done gregster!
- What's the significance of the timestamp, is it a problem if it's too early or too late, does it need to be precise?
- Here's a function to get the Unix time now in milliseconds:

Code: Select all

q:: ;date now (Unix time) (msec since 1970)
DllCall("kernel32\GetSystemTimeAsFileTime", Int64P,vIntervalsUTC) ;vIntervalsUTC: 100-nanosecond intervals since 1601
vDate := Floor(vIntervalsUTC/10000) ;result: milliseconds since 1601
;note: 11644473600000 is the number of milliseconds between 1601 and 1970
vDate -= 11644473600000 ;result: milliseconds since 1970
MsgBox, % vDate
return

w:: ;calculate milliseconds between 1601 and 1970
;value used in subroutine above
VarSetCapacity(SYSTEMTIME, 16, 0)
NumPut(1970, &SYSTEMTIME, 0, "UShort") ;wYear
NumPut(1, &SYSTEMTIME, 2, "UShort") ;wMonth
NumPut(1, &SYSTEMTIME, 6, "UShort") ;wDay
DllCall("kernel32\SystemTimeToFileTime", Ptr,&SYSTEMTIME, Int64P,vIntervalsUTC)
MsgBox, % Floor(vIntervalsUTC/10000) ;11644473600000
return
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: Binance API- Passing Security (Header or body)

20 May 2018, 14:56

jeeswg wrote:- What's the significance of the timestamp, is it a problem if it's too early or too late, does it need to be precise?
It depends, there are APIs which only use the timestamp as a (increasing) nonce - then you don't need a precise timestamp (or you could use your local time instead of UTC), but the binance API - according to the docs - seems to also compare the sent timestamp to its own server time:
Binance API docs (https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md) wrote:
A SIGNED endpoint also requires a parameter, timestamp, to be sent which should be the millisecond timestamp of when the request was created and sent.
An additional parameter, recvWindow, may be sent to specific the number of milliseconds after timestamp the request is valid for. If recvWindow is not sent, it defaults to 5000.
The logic is as follows:

Code: Select all

 if (timestamp < (serverTime + 1000) && (serverTime - timestamp) <= recvWindow) {
      // process request
    } else {
      // reject request
    }
Serious trading is about timing. Networks can be unstable and unreliable, which can lead to requests taking varying amounts of time to reach the servers. With recvWindow, you can specify that the request must be processed within a certain number of milliseconds or be rejected by the server.

So, it seems, they are using the timestamp to validate the API call; and the optional parameter recvWindow, which is mentioned in the docs I quoted in the last post, can be used to adjust the acceptable delay for a valid call.

I will have a look at the other function you provided, but since the default for recvWindow is 5000 ms and sending the data via the internet also takes at least some milliseconds, I don't think that millisecond precision is really needed here - the other function which just multiplies by 1000 is probably good enough for this application - with a (significantly) smaller window you might end up with too many discarded calls, I assume. It could still be useful for other things...
rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Binance API- Passing Security (Header or body)

23 Aug 2021, 07:00

gregster wrote:
18 May 2018, 23:59
Fear not, young api-rentice :) ! You have searched and I have found!

While pulling some working code from somewhere else can surely help, there are almost every time some differences and you will have to read very closely (and there is often room for interpretation). With that said, the Binance API docs are not good, but I have seen much worse :roll: . In the working code (see below) I used the following code (first two you already know):

* jeeswg's Unix-Time-function (much smaller than my version, thanks for that!),
* the same HMAC function you used, I think - I haven't really compared, but just copied the version I always used successfully (I think, it was made by forum member 'just me' back in the day)
* and finally - and optionally - Coco's JSON Class (https://autohotkey.com/boards/viewtopic.php?t=627) to parse the response string (not necessary, but a huge help with JSON data - otherwise, you will have to do a lot of parsing) - this library, I included through the #include directive in the first line. Just download JSON.ahk and put it in the same folder and it should work.

Some remarks what went wrong with your code: You were creating a sha512 HMAC hash, while the binance API docs advise you to use a sha256 HMAC hash - that breaks the whole request early on beyond repair... The timestamp needs to be UNIX UTC in milliseconds (not very clear in the docs, but quite common for APIs), but that was fixed by jeeswg. Then, you have some comments which mention a POST request, although you are using a GET request - which is correct in this case:

Code: Select all

Account information (USER_DATA)
GET /api/v3/account 							(HMAC SHA256)						Get current account information.
Weight: 5

Parameters:
Name 					Type 		Mandatory 			Description
------------------------------------------------------------
recvWindow 				LONG 		NO 	
timestamp 				LONG 		YES 	
But unfortunately, you didn't follow these crucial hints from the docs:
For GET endpoints, parameters must be sent as a query string.
[...]
API-keys are passed into the Rest API via the X-MBX-APIKEY header.
You are trying to send the parameters in the request body instead. Also, the header name for the API key is wrong, and the signature needs to be part of the query string here, not a header (other APIs will handle this different). Judging by the thread title, you already had a suspicion...
That is all excusable, if you are not used to work with APIs. But these are other breaking points in your script.
I admit, without the examples from the binance docs (and a little experience), I would have needed much more time myself... :shh:

Code: Select all

#include JSON.ahk		; https://autohotkey.com/boards/viewtopic.php?t=627  optional, but highly recommended

APIKey := "API key here"
SecretKey := "API secret here"

queryString := "timestamp=" DateDiff(A_NowUTC, 1970, "Seconds") * 1000
signature :=HMAC(SecretKey , queryString, "SHA256")				; not 512 !!

oHTTP:=ComObjCreate("WinHttp.WinHttpRequest.5.1")	
url := "https://api.binance.com/api/v3/account?" queryString "&signature=" signature		; binance GET requests need all info in a query string

oHTTP.Open("GET", url , False)													;GET request
; oHTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")		; only needed for binance POST, PUT and DELETE endpoints 
oHTTP.SetRequestHeader("X-MBX-APIKEY", APIKey)							; see binance API docs: "X-MBX-APIKEY" header for API key
oHTTP.Send()															; no request body here
response := oHTTP.ResponseText											; response as one huge string

msgbox % response

/*			example JSON response from the binance API docs for this request
{
  "makerCommission": 15,
  "takerCommission": 15,
  "buyerCommission": 0,
  "sellerCommission": 0,
  "canTrade": true,
  "canWithdraw": true,
  "canDeposit": true,
  "updateTime": 123456789,
  "balances": [
    {
      "asset": "BTC",
      "free": "4723846.89208129",
      "locked": "0.00000000"
    },
    {
      "asset": "LTC",
      "free": "4763368.68006011",
      "locked": "0.00000000"
    }
  ]
}
*/

; Example how to parse the JSON response. This will only work, if you included Coco's JSON library:
obj := json.load(response)
msgbox % "makerCommission: " obj.makerCommission "`ntakerCommission: " obj.makerCommission "`ncanTrade: " obj.canTrade  "       (1 = true)`n`netc. pp."
ExitApp
;--------------------------
Esc::ExitApp
;-------------------------------------------------------------------------------------------------------------------------------------------
; provided by jeeswg ;based on:
;convert common date formats (Excel / FileTime / Unix) - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=44727
DateDiff(DateTime1, DateTime2, TimeUnits)
{
    EnvSub DateTime1, %DateTime2%, %TimeUnits%
    return DateTime1
}
;-------------------------------------------------------------------------------------------------------------------------------------------
; Hash functions by just me (?)
HMAC(Key, Message, Algo := "MD5")
{
    static Algorithms := {MD2:    {ID: 0x8001, Size:  64}
                        , MD4:    {ID: 0x8002, Size:  64}
                        , MD5:    {ID: 0x8003, Size:  64}
                        , SHA:    {ID: 0x8004, Size:  64}
                        , SHA256: {ID: 0x800C, Size:  64}
                        , SHA384: {ID: 0x800D, Size: 128}
                        , SHA512: {ID: 0x800E, Size: 128}}
    static iconst := 0x36
    static oconst := 0x5C
    if (!(Algorithms.HasKey(Algo)))
    {
        return ""
    }
    Hash := KeyHashLen := InnerHashLen := ""
    HashLen := 0
    AlgID := Algorithms[Algo].ID
    BlockSize := Algorithms[Algo].Size
    MsgLen := StrPut(Message, "UTF-8") - 1
    KeyLen := StrPut(Key, "UTF-8") - 1
    VarSetCapacity(K, KeyLen + 1, 0)
    StrPut(Key, &K, KeyLen, "UTF-8")
    if (KeyLen > BlockSize)
    {
        CalcAddrHash(&K, KeyLen, AlgID, KeyHash, KeyHashLen)
    }

    VarSetCapacity(ipad, BlockSize + MsgLen, iconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ iconst, ipad, i, "UChar")
        i++
    }
    if (MsgLen)
    {
        StrPut(Message, &ipad + BlockSize, MsgLen, "UTF-8")
    }
    CalcAddrHash(&ipad, BlockSize + MsgLen, AlgID, InnerHash, InnerHashLen)

    VarSetCapacity(opad, BlockSize + InnerHashLen, oconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ oconst, opad, i, "UChar")
        i++
    }
    Addr := &opad + BlockSize
    i := 0
    while (i < InnerHashLen)
    {
        NumPut(NumGet(InnerHash, i, "UChar"), Addr + i, 0, "UChar")
        i++
    }
    return CalcAddrHash(&opad, BlockSize + InnerHashLen, AlgID)
}

; CalcAddrHash ======================================================================
CalcAddrHash(addr, length, algid, byref hash = 0, byref hashlength = 0)
{
    static h := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"]
    static b := h.minIndex()
    hProv := hHash := o := ""
    if (DllCall("advapi32\CryptAcquireContext", "Ptr*", hProv, "Ptr", 0, "Ptr", 0, "UInt", 24, "UInt", 0xf0000000))
    {
        if (DllCall("advapi32\CryptCreateHash", "Ptr", hProv, "UInt", algid, "UInt", 0, "UInt", 0, "Ptr*", hHash))
        {
            if (DllCall("advapi32\CryptHashData", "Ptr", hHash, "Ptr", addr, "UInt", length, "UInt", 0))
            {
                if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", 0, "UInt*", hashlength, "UInt", 0))
                {
                    VarSetCapacity(hash, hashlength, 0)
                    if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", &hash, "UInt*", hashlength, "UInt", 0))
                    {
                        loop % hashlength
                        {
                            v := NumGet(hash, A_Index - 1, "UChar")
                            o .= h[(v >> 4) + b] h[(v & 0xf) + b]
                        }
                    }
                }
            }
            DllCall("advapi32\CryptDestroyHash", "Ptr", hHash)
        }
        DllCall("advapi32\CryptReleaseContext", "Ptr", hProv, "UInt", 0)
    }
    return o
}
Don't forget to include your API keys. It seems, POST requests can be done with the query string method, too, but I haven't looked closely so far.

Let us know, if this worked (it should, if I didn't make any bad copy-paste-errors) and if you need further help. Good luck!
Dear @gregster, may I seek your help on the Binance API code if possible?

I try to query the Futures API account but it seems the above code will not work if I simply change the below:

Code: Select all

; Spot:
; url := "https://api.binance.com/api/v3/account?" queryString "&signature=" signature    ; binance GET requests need all info in a query string

; Future:
url := "https://api.binance.com/fapi/v2/account?" queryString "&signature=" signature    ; binance GET requests need all info in a query string
Any chance you may take a look for me if possible?

Thank you so much gregster!
rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Binance API- Passing Security (Header or body)

23 Aug 2021, 07:16

Apology, I figured it out that it need to be:

url := "https://fapi.binance.com/fapi/v1/account?"

Thank you gregster!
rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Binance API- Passing Security (Header or body)

23 Aug 2021, 07:20

@gregster If possible I would like to ask an addon question if possible.

I have added the following line to output the JSON to file so I can access the content.

FileAppend, % response, C:\Binance_Response.txt

However, its output into one big chunk of json. Is there anyway we can format the json output into text a bit more "prettier", or easier for human to read?
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: Binance API- Passing Security (Header or body)

23 Aug 2021, 22:41

rc76 wrote:
23 Aug 2021, 07:20
I have added the following line to output the JSON to file so I can access the content.

FileAppend, % response, C:\Binance_Response.txt

However, its output into one big chunk of json. Is there anyway we can format the json output into text a bit more "prettier", or easier for human to read?
Well, I assume you don't want to read through the saved txt-files, but extract relevant information. That shouldn't be a big problem, as the json's structure is usually documented, or you can just "pretty print" a json response once or twice to look up its structure. Then, you can load the json string into an AHK object via a json library and just access the relevant data by using its object keys.

To load the json string into an object, you could - for example - use Coco's Json class, mentioned above.
It can even "pretty print" the object derived from the json string (but you could also do this on one of the many websites that offer this, as you don't need to do that often, probably...)

Lets assume you got some json response from https://api.binance.com/api/v3/exchangeInfo?symbol=BNBBTC . Then you could use some random website like http://jsonprettyprint.net/ to look at the structure (although I assume that the binance API docs would also provide this information).

Example response:

Code: Select all

{"timezone":"UTC","serverTime":1629772943769,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},{"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":50},{"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":160000},{"rateLimitType":"RAW_REQUESTS","interval":"MINUTE","intervalNum":5,"limit":6100}],"exchangeFilters":[],"symbols":[{"symbol":"BNBBTC","status":"TRADING","baseAsset":"BNB","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"quoteOrderQtyMarketAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[{"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"},{"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5},{"filterType":"LOT_SIZE","minQty":"0.01000000","maxQty":"100000.00000000","stepSize":"0.01000000"},{"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5},{"filterType":"ICEBERG_PARTS","limit":10},{"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"8997.37099374","stepSize":"0.00000000"},{"filterType":"MAX_NUM_ORDERS","maxNumOrders":200},{"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5}],"permissions":["SPOT","MARGIN"]}]}
The same pretty printed by the website mentioned above:

Code: Select all

{
  "timezone": "UTC",
  "serverTime": 1629772943769,
  "rateLimits": [
    {
      "rateLimitType": "REQUEST_WEIGHT",
      "interval": "MINUTE",
      "intervalNum": 1,
      "limit": 1200
    },
    {
      "rateLimitType": "ORDERS",
      "interval": "SECOND",
      "intervalNum": 10,
      "limit": 50
    },
    {
      "rateLimitType": "ORDERS",
      "interval": "DAY",
      "intervalNum": 1,
      "limit": 160000
    },
    {
      "rateLimitType": "RAW_REQUESTS",
      "interval": "MINUTE",
      "intervalNum": 5,
      "limit": 6100
    }
  ],
  "exchangeFilters": [],
  "symbols": [
    {
      "symbol": "BNBBTC",
      "status": "TRADING",
      "baseAsset": "BNB",
      "baseAssetPrecision": 8,
      "quoteAsset": "BTC",
      "quotePrecision": 8,
      "quoteAssetPrecision": 8,
      "baseCommissionPrecision": 8,
      "quoteCommissionPrecision": 8,
      "orderTypes": [
        "LIMIT",
        "LIMIT_MAKER",
        "MARKET",
        "STOP_LOSS_LIMIT",
        "TAKE_PROFIT_LIMIT"
      ],
      "icebergAllowed": true,
      "ocoAllowed": true,
      "quoteOrderQtyMarketAllowed": true,
      "isSpotTradingAllowed": true,
      "isMarginTradingAllowed": true,
      "filters": [
        {
          "filterType": "PRICE_FILTER",
          "minPrice": "0.00000100",
          "maxPrice": "100000.00000000",
          "tickSize": "0.00000100"
        },
        {
          "filterType": "PERCENT_PRICE",
          "multiplierUp": "5",
          "multiplierDown": "0.2",
          "avgPriceMins": 5
        },
        {
          "filterType": "LOT_SIZE",
          "minQty": "0.01000000",
          "maxQty": "100000.00000000",
          "stepSize": "0.01000000"
        },
        {
          "filterType": "MIN_NOTIONAL",
          "minNotional": "0.00010000",
          "applyToMarket": true,
          "avgPriceMins": 5
        },
        {
          "filterType": "ICEBERG_PARTS",
          "limit": 10
        },
        {
          "filterType": "MARKET_LOT_SIZE",
          "minQty": "0.00000000",
          "maxQty": "8997.37099374",
          "stepSize": "0.00000000"
        },
        {
          "filterType": "MAX_NUM_ORDERS",
          "maxNumOrders": 200
        },
        {
          "filterType": "MAX_NUM_ALGO_ORDERS",
          "maxNumAlgoOrders": 5
        }
      ],
      "permissions": [
        "SPOT",
        "MARGIN"
      ]
    }
  ]
}
This helps to identify the different levels in the json data structure, if unknown.

Now let's try some code (you'll need to download and #include json.ahk):

Code: Select all

#include json.ahk	; https://www.autohotkey.com/boards/viewtopic.php?t=627

; let's assume we got this response string:
jstring = {"timezone":"UTC","serverTime":1629772943769,"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},{"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":50},{"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":160000},{"rateLimitType":"RAW_REQUESTS","interval":"MINUTE","intervalNum":5,"limit":6100}],"exchangeFilters":[],"symbols":[{"symbol":"BNBBTC","status":"TRADING","baseAsset":"BNB","baseAssetPrecision":8,"quoteAsset":"BTC","quotePrecision":8,"quoteAssetPrecision":8,"baseCommissionPrecision":8,"quoteCommissionPrecision":8,"orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"],"icebergAllowed":true,"ocoAllowed":true,"quoteOrderQtyMarketAllowed":true,"isSpotTradingAllowed":true,"isMarginTradingAllowed":true,"filters":[{"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"},{"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5},{"filterType":"LOT_SIZE","minQty":"0.01000000","maxQty":"100000.00000000","stepSize":"0.01000000"},{"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5},{"filterType":"ICEBERG_PARTS","limit":10},{"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"8997.37099374","stepSize":"0.00000000"},{"filterType":"MAX_NUM_ORDERS","maxNumOrders":200},{"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5}],"permissions":["SPOT","MARGIN"]}]}
msgbox % "Our original json string:`n`n`n" jstring

msgbox Now let's extract some specific data...
obj := json.load(jstring)		; load the json string into an AHK object
msgbox % "Timezone: " obj.timezone "`nServerTime: " obj.serverTime "`n`nSymbol: " obj.symbols[1].symbol

msgbox You can also loop through a (sub-)object, in this case the rateLimits 
for index, limitType in obj.ratelimits
	msgbox % "Interval: " limitType.interval "`nLimit: " limitType.limit

msgbox Let's see how json.ahk can print the object...
str := json.dump(obj, , 3)		; dump that object back into a prettified string
msgbox % str
; FileAppend, % str, C:\Binance_Response_pretty.txt
You will probably notice that the printed AHK object shows a different order of the keys, as AHK orders the object keys alphabetically. But that doesn't matter if you are accessing the keys by their names. (Linear arrays, the ones which use [ ], will of course keep their order.)

json.ahk is not very fast. There are other json libraries available, like the new cJson.ahk by GeekDude, which will be faster, especially for large json strings, because it uses machine code - but please note that it is still in development, and doesn't have a pretty print option yet.

I hope this helps...
rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Binance API- Passing Security (Header or body)

26 Aug 2021, 01:54

Dear @gregster , this works like a dream! Thank you so much for your awesome help. Really much appreciated!

I will continue dive into the Binance API and see if can do bit more applications and usage scenarios with AHK!
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: Binance API- Passing Security (Header or body)

26 Aug 2021, 02:08

Thanks you for the nice feedback, and happy trading!
This should also help others who are interested in the subject.

Feel free to post some results (could of course be in a separate topic, if appropriate), when you are ready!

Edit:
The posts about formatting messages in Telegram were split from this topic, and now can be found here: viewtopic.php?f=76&t=94268
rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Binance API- Passing Security (Header or body)

29 Sep 2021, 04:05

gregster wrote:
18 May 2018, 23:59
<The Binance API script in the above post.>
Hi @gregster , I ran the code few months back it was working okay, but I just tried to re-run it now, it seems not working anymore.

I have tried of both real account, as well as testnet API keys, both not working (Spot and Future)
(1) TestNet - Spot: https://testnet.binance.vision/
(2) TestNet - Future: https://testnet.binancefuture.com/

Any chance the script also works with you?

The base URL I used for each are:

Code: Select all

; Spot - Live:
url := "https://api.binance.com/api/v3/account?" queryString "&signature=" signature

; Spot - TestNet:
url := "https://testnet.binance.vision/api/v3/account?" queryString "&signature=" signature

; Future - Live:
url := "https://fapi.binance.com/fapi/v1/account?" queryString "&signature=" signature

; Future - TestNet:
url := "https://testnet.binancefuture.com/fapi/v1/account?" queryString "&signature=" signature
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: Binance API- Passing Security (Header or body)

29 Sep 2021, 20:59

I have no verified Binance account and no API keys, iirc. So, I can't really test; no idea what might have changed.
What means 'not working' ? Usually, you'll get some error message as a response if you are making an invaild API call. It might give a hint.
rc76
Posts: 144
Joined: 07 Nov 2020, 01:45

Re: Binance API- Passing Security (Header or body)

29 Sep 2021, 21:45

Apology, its my stupid mistake that made the code not working. Its working alright now.

My issue was - I tried to make all the code into a hotkey statement but left some part of URL definition outside the statement, such as:

Code: Select all

APIKey := "xxx"
SecretKey := "xxx"

queryString := "timestamp=" DateDiff(A_NowUTC, 1970, "Seconds") * 1000
signature :=HMAC(SecretKey , queryString, "SHA256")       ; not 512 !!

oHTTP:=ComObjCreate("WinHttp.WinHttpRequest.5.1")
url := "https://testnet.binance.vision/api/v3/account?" queryString "&signature=" signature

1::
<all the code, beside API definition>
return
Therefore, when I run it, the msgbox always return empty, so I didn't know what went wrong.

Now I put everything side the 1:: statement, it works perfect!

Thank you so much @gregster
scriptomundo
Posts: 44
Joined: 08 Nov 2021, 23:40

Re: Binance API- Passing Security (Header or body)

16 Jan 2022, 07:27

Code: Select all

#include JSON.ahk		; https://autohotkey.com/boards/viewtopic.php?t=627  optional, but highly recommended

APIKey := "redacted"
SecretKey := "redacted"

queryString := "FTX-TS=" DateDiff(A_NowUTC, 1970, "Seconds") * 1000
signature :=HMAC(SecretKey , queryString, "SHA256")				; not 512 !!

oHTTP:=ComObjCreate("WinHttp.WinHttpRequest.5.1")	
url := "https://ftx.com/api/?" queryString "&FTX-SIGN=" signature		; binance GET requests need all info in a query string





oHTTP.Open("GET", url , False)													;GET request 
oHTTP.SetRequestHeader("FTX-KEY", APIKey)							
oHTTP.Send()															; no request body here
response := oHTTP.ResponseText											; response as one huge string

clipboard := response
msgbox % response


/*
Example JSON response from FTX:
{
  "success": true,
  "result": [
    {
      "cost": -31.7906,
      "cumulativeBuySize": 1.2,
      "cumulativeSellSize": 0.0,
      "entryPrice": 138.22,
      "estimatedLiquidationPrice": 152.1,
      "future": "ETH-PERP",
      "initialMarginRequirement": 0.1,
      "longOrderSize": 1744.55,
      "maintenanceMarginRequirement": 0.04,
      "netSize": -0.23,
      "openSize": 1744.32,
      "realizedPnl": 3.39441714,
      "recentAverageOpenPrice": 135.31,
      "recentBreakEvenPrice": 135.31,
      "recentPnl": 3.1134,
      "shortOrderSize": 1732.09,
      "side": "sell",
      "size": 0.23,
      "unrealizedPnl": 0,
      "collateralUsed": 3.17906
    }
  ]
}
*/



; Example how to parse the JSON response. This will only work, if you included Coco's JSON library:
obj := json.load(response)
msgbox % "makerCommission: " obj.makerCommission "`ntakerCommission: " obj.makerCommission "`ncanTrade: " obj.canTrade  "       (1 = true)`n`netc. pp."
ExitApp
;--------------------------
Esc::ExitApp
;-------------------------------------------------------------------------------------------------------------------------------------------
; provided by jeeswg ;based on:
;convert common date formats (Excel / FileTime / Unix) - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=44727
DateDiff(DateTime1, DateTime2, TimeUnits)
{
    EnvSub DateTime1, %DateTime2%, %TimeUnits%
    return DateTime1
}
;-------------------------------------------------------------------------------------------------------------------------------------------
; Hash functions by just me (?)
HMAC(Key, Message, Algo := "MD5")
{
    static Algorithms := {MD2:    {ID: 0x8001, Size:  64}
                        , MD4:    {ID: 0x8002, Size:  64}
                        , MD5:    {ID: 0x8003, Size:  64}
                        , SHA:    {ID: 0x8004, Size:  64}
                        , SHA256: {ID: 0x800C, Size:  64}
                        , SHA384: {ID: 0x800D, Size: 128}
                        , SHA512: {ID: 0x800E, Size: 128}}
    static iconst := 0x36
    static oconst := 0x5C
    if (!(Algorithms.HasKey(Algo)))
    {
        return ""
    }
    Hash := KeyHashLen := InnerHashLen := ""
    HashLen := 0
    AlgID := Algorithms[Algo].ID
    BlockSize := Algorithms[Algo].Size
    MsgLen := StrPut(Message, "UTF-8") - 1
    KeyLen := StrPut(Key, "UTF-8") - 1
    VarSetCapacity(K, KeyLen + 1, 0)
    StrPut(Key, &K, KeyLen, "UTF-8")
    if (KeyLen > BlockSize)
    {
        CalcAddrHash(&K, KeyLen, AlgID, KeyHash, KeyHashLen)
    }

    VarSetCapacity(ipad, BlockSize + MsgLen, iconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ iconst, ipad, i, "UChar")
        i++
    }
    if (MsgLen)
    {
        StrPut(Message, &ipad + BlockSize, MsgLen, "UTF-8")
    }
    CalcAddrHash(&ipad, BlockSize + MsgLen, AlgID, InnerHash, InnerHashLen)

    VarSetCapacity(opad, BlockSize + InnerHashLen, oconst)
    Addr := KeyLen > BlockSize ? &KeyHash : &K
    Length := KeyLen > BlockSize ? KeyHashLen : KeyLen
    i := 0
    while (i < Length)
    {
        NumPut(NumGet(Addr + 0, i, "UChar") ^ oconst, opad, i, "UChar")
        i++
    }
    Addr := &opad + BlockSize
    i := 0
    while (i < InnerHashLen)
    {
        NumPut(NumGet(InnerHash, i, "UChar"), Addr + i, 0, "UChar")
        i++
    }
    return CalcAddrHash(&opad, BlockSize + InnerHashLen, AlgID)
}

; CalcAddrHash ======================================================================
CalcAddrHash(addr, length, algid, byref hash = 0, byref hashlength = 0)
{
    static h := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"]
    static b := h.minIndex()
    hProv := hHash := o := ""
    if (DllCall("advapi32\CryptAcquireContext", "Ptr*", hProv, "Ptr", 0, "Ptr", 0, "UInt", 24, "UInt", 0xf0000000))
    {
        if (DllCall("advapi32\CryptCreateHash", "Ptr", hProv, "UInt", algid, "UInt", 0, "UInt", 0, "Ptr*", hHash))
        {
            if (DllCall("advapi32\CryptHashData", "Ptr", hHash, "Ptr", addr, "UInt", length, "UInt", 0))
            {
                if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", 0, "UInt*", hashlength, "UInt", 0))
                {
                    VarSetCapacity(hash, hashlength, 0)
                    if (DllCall("advapi32\CryptGetHashParam", "Ptr", hHash, "UInt", 2, "Ptr", &hash, "UInt*", hashlength, "UInt", 0))
                    {
                        loop % hashlength
                        {
                            v := NumGet(hash, A_Index - 1, "UChar")
                            o .= h[(v >> 4) + b] h[(v & 0xf) + b]
                        }
                    }
                }
            }
            DllCall("advapi32\CryptDestroyHash", "Ptr", hHash)
        }
        DllCall("advapi32\CryptReleaseContext", "Ptr", hProv, "UInt", 0)
    }
    return o
}
I've been tinkering with this script for a while and I think I've managed to authenticate for a different exchange api (FTX)! The response is: {"success":true,"result":true} Thanks for the code @gregster , never would have been able to get to this step on my own!

Unfortunately I have been having trouble trying to work out how to get data response once connected.
I'm trying to use:
https://docs.ftx.com/#get-account-information

And changed the reference url to:

Code: Select all

https://ftx.com/api/positions/?
But this returns a 404. Can anyone see what I might be doing wrong? I'm in unfamiliar territory and my eyes are bleeding at this stage.

https://blog.ftx.com/blog/api-authentication/
https://docs.ftx.com/#authentication
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: Binance API- Passing Security (Header or body)

19 Jan 2022, 00:17

scriptomundo wrote:
16 Jan 2022, 07:27
I'm trying to use:
https://docs.ftx.com/#get-account-information
According to the FTX docs, you will need different headers, and a differently contructed signature.
I was able to succesfully test authenticated GET calls with an empty and unverified test account I created, but couldn't really test POST calls.

GET call (tested) - for /account (and for, commented out, /markets)

Code: Select all

; optional
;#include JSON.ahk		; https://autohotkey.com/boards/viewtopic.php?t=627  

; my data
apikey := "My API Key"
secret := "My API secret"

timestamp := DateDiff(A_NowUTC, 1970, "Seconds") * 1000
signature_payload := timestamp . "GET/api/account"						
;signature_payload := timestamp . "GET/api/markets"	

signature := HMAC(secret, signature_payload, "SHA256")	

oHTTP := ComObjCreate("WinHttp.WinHttpRequest.5.1")	

url := "https://ftx.com/api/account"							; GET /account
;url := "https://ftx.com/api/markets"								; GET /markets

oHTTP.Open("GET", url , False)													;GET request
oHTTP.SetRequestHeader("FTX-KEY", apikey)							
oHTTP.SetRequestHeader("FTX-SIGN", signature)				
oHTTP.SetRequestHeader("FTX-TS", timestamp)							
oHTTP.Send()															
response := oHTTP.ResponseText											; response as one huge string - process with JSON.ahk, if you like

msgbox % response

; optional, if you are using json.ahk
/*
obj := json.load(response)
msgbox % obj.result.username					; returns username from /account json response
*/
ExitApp
You will need the DateDiff(), HMAC() and CalcAddrHash() functions from this post.
If you want to load the received json string into an AHK object (recommended) for better handling, you should also add something like the JSON.ahk library (commented out, for now)


POST call (not tested)
Just example code, for changing the account leverage. Be careful. Don't post any unintended changes or orders; please adjust the json string.
You will need to add an additional json string to send data (the individual json string formats are documented in the FTX API docs).

Code: Select all

; my data
apikey := "My API Key"
secret := "My API secret"

timestamp := DateDiff(A_NowUTC, 1970, "Seconds") * 1000
json_string = {"leverage": 10,}			; for this, the legacy assignment with = instead of := is actually nicer

signature_payload := timestamp . "POST/api/account/leverage" . json_string
signature := HMAC(secret, signature_payload, "SHA256")	

oHTTP := ComObjCreate("WinHttp.WinHttpRequest.5.1")	

url := "https://ftx.com/api/account/leverage"							; change account leverage

oHTTP.Open("POST", url, False)													; POST request
oHTTP.SetRequestHeader("FTX-KEY", apikey)							
oHTTP.SetRequestHeader("FTX-SIGN", signature)				
oHTTP.SetRequestHeader("FTX-TS", timestamp)							
oHTTP.Send()															
response := oHTTP.ResponseText											; response as one huge string - process with JSON.ahk, if you like

msgbox % response
ExitApp
Of course, loading the json response into an AHK object is again recommended (see commented out code in GET example).

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: TAC109 and 129 guests