Search code examples
c#.nettcpclientfix-protocolgdax-api

How to send FIX logon message with C# .NET CORE 2.0 to GDAX


I'm I'm trying to establish a FIX 4.2 session to fix.gdax.com (docs: https://docs.gdax.com/#fix-api) using C# with .Net Core 2.0. when i try to logon i receive 0 bytes as response as server. I really dont know what i have wrong, this is the code:

    private async Task ConnectToGdaxFix()
    {
        _socketTcpClient = new TcpClient();
        _socketTcpClient.NoDelay = true;
        _bufferEnd = 0;
        await _socketTcpClient.ConnectAsync(_gdaxFixGateway, _gdaxFixPort);
        _sslStream = new SslStream(_socketTcpClient.GetStream());
        await _sslStream.AuthenticateAsClientAsync(_gdaxFixGateway);
        byte[] buffer = new byte[2048];
        var message = await FixSendLoginMessage();
        var bytes = await _sslStream.ReadAsync(buffer, 0, buffer.Length);
    }

    private async Task<string> FixSendLoginMessage()
    {
        var sendTime = DateTime.UtcNow.ToString("yyyyMMdd-HH:mm:ss.fff");
        string msgType = "A";
        var messageSeqNumber = (++messageSeqNum).ToString();
        var senderCompId = "apiKey";
        var targetCompId = "Coinbase";
        var password = "passphrase";
        var accessSign = CreateAccessSign(
                sendTime,
                msgType,
                messageSeqNumber,
                senderCompId,
                targetCompId,
                password
            );
        var logonMessage = $"98=0{'\x01'}108=30{'\x01'}554={password}{'\x01'}96={accessSign}{'\x01'}8013=Y";
        var headerAfter9Tag = $"35={msgType}{'\x01'}34={messageSeqNumber}{'\x01'}52={sendTime}{'\x01'}49={senderCompId}{'\x01'}56={targetCompId}";
        var length = logonMessage.Length + headerAfter9Tag.Length + 1;
        var messageHeader = $"8=FIX.4.2{'\x01'}9={length}{'\x01'}{headerAfter9Tag}";
        var messageWithHeader = $"{messageHeader}{'\x01'}{logonMessage}{'\x01'}";
        int sum = 0;
        int len = messageWithHeader.Length;
        for (int i = 0; i < len; i++)
        {
            sum += Convert.ToChar(messageWithHeader.Substring(i, 1));
        }
        sum = sum % 256;
        var messageWithChecksum = $"{messageWithHeader}10={sum.ToString("000")}{'\x01'}";
        byte[] buffer = Encoding.ASCII.GetBytes(messageWithChecksum);
        await _sslStream.WriteAsync(buffer, 0, buffer.Length);
        await _sslStream.FlushAsync();
        return messageWithChecksum;
    }

    private string CreateAccessSign(
            string sendingTime,
            string msgType,
            string msgSeqNumber,
            string senderCompId,
            string targetCompId,
            string password
        )
    {
        var stringToHash = string.Join('\x01', sendingTime, msgType, msgSeqNumber, senderCompId, targetCompId, password);
        var privateKeyAsBytes = Encoding.UTF8.GetBytes("privateKey");
        using (var hmac = new HMACSHA256(privateKeyAsBytes))
        {
            var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToHash));
            return Convert.ToBase64String(signature);
        }
    }

Can anyone please help me to see what is wrong with the logon message or ssl encryption?

This is the message sent to Gdax(the characters of the sensitive data have been replaced and the \x01 is the | to simplify the view): 8=FIX.4.2|9=161|35=A|34=1|52=20180124-00:50:34.083|49=426123d789fa8e5c3782c549kj9de06e|56=Coinbase|98=0|108=30|554=outswrt|96=qkE5KPMLjn+Ef9Zgk1/kvL0Etem6bK2llINwMjOkDy9=|8013=Y|10=028


Solution

  • CreateAccessSign is wrong, privateKeyAsBytes need to use Convert.FromBase64String:

        var privateKeyAsBytes = Convert.FromBase64String("privateKey");
    

    FixSendLoginMessage is wrong, this is the correct implementation:

        private async Task<string> FixSendLoginMessage()
    {
        var sendTime = DateTime.UtcNow.ToString("yyyyMMdd-HH:mm:ss.fff");
        string msgType = "A";
        var messageSeqNumber = (++messageSeqNum).ToString();
        var senderCompId = "apiKey";
        var targetCompId = "Coinbase";
        var password = "passphrase";
        var accessSign = CreateAccessSign(
                sendTime,
                msgType,
                messageSeqNumber,
                senderCompId,
                targetCompId,
                password
            );
        var logonMessage = $"98=0{'\x01'}108=30{'\x01'}554={password}{'\x01'}96={accessSign}{'\x01'}8013=Y{'\x01'}";
        var headerAfter9Tag = $"35={msgType}{'\x01'}34={messageSeqNumber}{'\x01'}52={sendTime}{'\x01'}49={senderCompId}{'\x01'}56={targetCompId}{'\x01'}";
        var length = logonMessage.Length + headerAfter9Tag.Length;
        var messageHeader = $"8=FIX.4.2{'\x01'}9={length}{'\x01'}{headerAfter9Tag}";
        var messageWithHeader = $"{messageHeader}{logonMessage}";
        int sum = 0;
        int len = messageWithHeader.Length;
        for (int i = 0; i < len; i++)
        {
            sum += Convert.ToChar(messageWithHeader.Substring(i, 1));
        }
        sum = sum % 256;
        var messageWithChecksum = $"{messageWithHeader}10={sum.ToString("000")}{'\x01'}";
        byte[] buffer = Encoding.ASCII.GetBytes(messageWithChecksum);
        await _sslStream.WriteAsync(buffer, 0, buffer.Length);
        await _sslStream.FlushAsync();
        return messageWithChecksum;
    }