public async Task <object> GenerateNonceAsync() { await new SynchronizationContextRemover(); if (NonceOffset.Ticks == 0) { await OnGetNonceOffset(); } object nonce; while (true) { lock (this) { // some API (Binance) have a problem with requests being after server time, subtract of offset can help DateTime now = CryptoUtility.UtcNow - NonceOffset; switch (NonceStyle) { case NonceStyle.Ticks: nonce = now.Ticks; break; case NonceStyle.TicksString: case NonceStyle.Iso8601: nonce = now.Ticks.ToStringInvariant(); break; case NonceStyle.TicksThenIncrement: if (lastNonce == 0m) { nonce = now.Ticks; } else { nonce = (long)(lastNonce + 1m); } break; case NonceStyle.UnixMilliseconds: nonce = (long)now.UnixTimestampFromDateTimeMilliseconds(); break; case NonceStyle.UnixMillisecondsString: nonce = ((long)now.UnixTimestampFromDateTimeMilliseconds()).ToStringInvariant(); break; case NonceStyle.UnixMillisecondsThenIncrement: if (lastNonce == 0m) { nonce = (long)now.UnixTimestampFromDateTimeMilliseconds(); } else { nonce = (long)(lastNonce + 1m); } break; case NonceStyle.UnixSeconds: nonce = now.UnixTimestampFromDateTimeSeconds(); break; case NonceStyle.UnixSecondsString: nonce = now.UnixTimestampFromDateTimeSeconds().ToStringInvariant(); break; case NonceStyle.Int32File: case NonceStyle.Int64File: { // why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window... // making it required to increment by 1 is also a pain - especially when restarting a process or rebooting. string tempFile = Path.Combine(Path.GetTempPath(), (PublicApiKey?.ToUnsecureString() ?? "unknown_pub_key") + ".nonce"); if (!File.Exists(tempFile)) { File.WriteAllText(tempFile, "0"); } unchecked { long longNonce = File.ReadAllText(tempFile).ConvertInvariant <long>() + 1; long maxValue = (NonceStyle == NonceStyle.Int32File ? int.MaxValue : long.MaxValue); if (longNonce < 1 || longNonce > maxValue) { throw new APIException($"Nonce {longNonce.ToStringInvariant()} is out of bounds, valid ranges are 1 to {maxValue.ToStringInvariant()}, " + $"please regenerate new API keys. Please contact {Name} API support and ask them to change to a sensible nonce algorithm."); } File.WriteAllText(tempFile, longNonce.ToStringInvariant()); nonce = longNonce; } break; } case NonceStyle.ExpiresUnixMilliseconds: nonce = (long)now.UnixTimestampFromDateTimeMilliseconds(); break; case NonceStyle.ExpiresUnixSeconds: nonce = (long)now.UnixTimestampFromDateTimeSeconds(); break; default: throw new InvalidOperationException("Invalid nonce style: " + NonceStyle); } // check for duplicate nonce decimal convertedNonce = nonce.ConvertInvariant <decimal>(); if (lastNonce != convertedNonce || NonceStyle == NonceStyle.ExpiresUnixSeconds || NonceStyle == NonceStyle.ExpiresUnixMilliseconds) { lastNonce = convertedNonce; break; } } // wait 1 millisecond for a new nonce await Task.Delay(1); } return(nonce); }
protected override async Task ProcessRequestAsync(HttpWebRequest request, Dictionary <string, object> payload) { // only authenticated requests write json, everything uses GET and url params if (CanMakeAuthenticatedRequest(payload)) { request.Headers["Authorization"] = CryptoUtility.BasicAuthenticationString(PublicApiKey.ToUnsecureString(), PrivateApiKey.ToUnsecureString()); if (request.Method == "POST") { await CryptoUtility.WritePayloadJsonToRequestAsync(request, payload); } } }
protected override async Task <IWebSocket> OnUserDataWebSocketAsync(Action <object> callback) { return(await ConnectPublicWebSocketAsync("/", async (_socket, msg) => { var token = msg.ToStringFromUTF8(); var response = JsonConvert.DeserializeObject <BaseMessage>(token); switch (response.Type) { case ResponseType.Subscriptions: var subscription = JsonConvert.DeserializeObject <Subscription>(token); if (subscription.Channels == null || !subscription.Channels.Any()) { Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() no channels subscribed"); } else { Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() subscribed to " + $"{string.Join(",", subscription.Channels.Select(c => c.ToString()))}"); } break; case ResponseType.Ticker: throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()"); case ResponseType.Snapshot: throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()"); case ResponseType.L2Update: throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()"); case ResponseType.Heartbeat: var heartbeat = JsonConvert.DeserializeObject <Heartbeat>(token); Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() heartbeat received {heartbeat}"); break; case ResponseType.Received: var received = JsonConvert.DeserializeObject <Received>(token); callback(received.ExchangeOrderResult); break; case ResponseType.Open: var open = JsonConvert.DeserializeObject <Open>(token); callback(open.ExchangeOrderResult); break; case ResponseType.Done: var done = JsonConvert.DeserializeObject <Done>(token); callback(done.ExchangeOrderResult); break; case ResponseType.Match: var match = JsonConvert.DeserializeObject <Match>(token); callback(match.ExchangeOrderResult); break; case ResponseType.LastMatch: //var lastMatch = JsonConvert.DeserializeObject<LastMatch>(token); throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()"); case ResponseType.Error: var error = JsonConvert.DeserializeObject <Error>(token); throw new APIException($"{error.Reason}: {error.Message}"); case ResponseType.Change: var change = JsonConvert.DeserializeObject <Change>(token); callback(change.ExchangeOrderResult); break; case ResponseType.Activate: var activate = JsonConvert.DeserializeObject <Activate>(token); callback(activate.ExchangeOrderResult); break; case ResponseType.Status: //var status = JsonConvert.DeserializeObject<Status>(token); throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()"); default: throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()"); } }, async (_socket) => { var marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); var nonce = await GetNoncePayloadAsync(); string timestamp = nonce["nonce"].ToStringInvariant(); byte[] secret = CryptoUtility.ToBytesBase64Decode(PrivateApiKey); string toHash = timestamp + "GET" + "/users/self/verify"; var subscribeRequest = new { type = "subscribe", channels = new object[] { new { name = "user", product_ids = marketSymbols, } }, signature = CryptoUtility.SHA256SignBase64(toHash, secret), // signature base 64 string key = PublicApiKey.ToUnsecureString(), passphrase = CryptoUtility.ToUnsecureString(Passphrase), timestamp = timestamp }; await _socket.SendMessageAsync(subscribeRequest); })); }
protected override async Task ProcessRequestAsync(HttpWebRequest request, Dictionary <string, object> payload) { // Only Private APIs are POST and need Authorization if (CanMakeAuthenticatedRequest(payload) && request.Method == "POST") { string requestContentBase64String = string.Empty; string nonce = payload["nonce"] as string; payload.Remove("nonce"); string jsonContent = CryptoUtility.GetJsonForPayload(payload); if (!String.IsNullOrEmpty(jsonContent)) { using (MD5 md5 = MD5.Create()) { requestContentBase64String = Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(jsonContent))); } } else { request.ContentLength = 0; } string baseSig = string.Concat(PublicApiKey.ToUnsecureString(), request.Method, Uri.EscapeDataString(request.RequestUri.AbsoluteUri).ToLower(), nonce, requestContentBase64String); string signature = CryptoUtility.SHA256SignBase64(baseSig, Convert.FromBase64String(PrivateApiKey.ToUnsecureString())); request.Headers.Add(HttpRequestHeader.Authorization, string.Format("amx {0}:{1}:{2}", PublicApiKey.ToUnsecureString(), signature, nonce)); // Cryptopia is very picky on how the payload is passed. There might be a better way to do this, but this works... using (Stream stream = await request.GetRequestStreamAsync()) { byte[] content = Encoding.UTF8.GetBytes(jsonContent); stream.Write(content, 0, content.Length); } } }
/// <summary> /// ASYNC - Generate a nonce /// </summary> /// <returns>Nonce</returns> public async Task <object> GenerateNonceAsync() { await new SynchronizationContextRemover(); if (NonceOffset.Ticks == 0) { await OnGetNonceOffset(); } // exclusive lock, no two nonces must match lock (this) { object nonce; while (true) { // some API (Binance) have a problem with requests being after server time, subtract of one second fixes it DateTime now = DateTime.UtcNow - NonceOffset; Task.Delay(1).Wait(); switch (NonceStyle) { case NonceStyle.Ticks: nonce = now.Ticks; break; case NonceStyle.TicksString: nonce = now.Ticks.ToStringInvariant(); break; case NonceStyle.UnixMilliseconds: nonce = (long)now.UnixTimestampFromDateTimeMilliseconds(); break; case NonceStyle.UnixMillisecondsString: nonce = ((long)now.UnixTimestampFromDateTimeMilliseconds()).ToStringInvariant(); break; case NonceStyle.UnixSeconds: nonce = now.UnixTimestampFromDateTimeSeconds(); break; case NonceStyle.UnixSecondsString: nonce = now.UnixTimestampFromDateTimeSeconds().ToStringInvariant(); break; case NonceStyle.IntegerFile: { // why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window... string tempFile = Path.Combine(Path.GetTempPath(), PublicApiKey.ToUnsecureString() + ".nonce"); if (!File.Exists(tempFile)) { File.WriteAllText(tempFile, "0"); } unchecked { int intNonce = int.Parse(File.ReadAllText(tempFile), CultureInfo.InvariantCulture) + 1; if (intNonce < 1) { throw new APIException("Nonce is out of bounds of a signed 32 bit integer (1 - " + int.MaxValue.ToStringInvariant() + "), please regenerate new API keys. Please contact the API support and ask them to change this horrible nonce behavior."); } nonce = (long)intNonce; File.WriteAllText(tempFile, intNonce.ToStringInvariant()); } } break; default: throw new InvalidOperationException("Invalid nonce style: " + NonceStyle); } // check for duplicate nonce decimal convertedNonce = nonce.ConvertInvariant <decimal>(); if (lastNonce != convertedNonce) { lastNonce = convertedNonce; break; } } return(nonce); } }
protected override Uri ProcessRequestUrl(UriBuilder url, Dictionary <string, object> payload) { if (CanMakeAuthenticatedRequest(payload)) { // payload is ignored, except for the nonce which is added to the url query - HitBTC puts all the "post" parameters in the url query instead of the request body var query = HttpUtility.ParseQueryString(url.Query); string newQuery = "nonce=" + payload["nonce"].ToString() + "&apikey=" + PublicApiKey.ToUnsecureString() + (query.Count == 0 ? string.Empty : "&" + query.ToString()) + (payload.Count > 1 ? "&" + GetFormForPayload(payload, false) : string.Empty); url.Query = newQuery; return(url.Uri); } return(base.ProcessRequestUrl(url, payload)); }