Skip to content

Commit 46259fc

Browse files
authored
implemented OnUserDataWebSocketAsync() on Coinbase (#665)
- removed try/catch from APIRequestMaker - fixed console BuyOption DumpResponse() - fixed OnGetOpenOrderDetailsAsync() statuses - fixed OnGetMarketSymbolsAsync() to only return active symbols - added RequestMaker to IBaseAPI - improved docs on ExchangeOrderResult
1 parent d9fc563 commit 46259fc

File tree

12 files changed

+448
-43
lines changed

12 files changed

+448
-43
lines changed

src/ExchangeSharp/API/Common/APIRequestMaker.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,11 @@ public InternalHttpWebResponse(HttpResponseMessage response)
125125

126126
public IReadOnlyList<string> GetHeader(string name)
127127
{
128-
try
128+
if (response.Headers.TryGetValues(name: name, out var header))
129129
{
130-
return response.Headers.GetValues(name).ToArray(); // throws InvalidOperationException when name not exist
130+
return header.ToArray();
131131
}
132-
catch (Exception)
132+
else
133133
{
134134
return CryptoUtility.EmptyStringArray;
135135
}

src/ExchangeSharp/API/Common/BaseAPI.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,6 @@ public virtual Task<IWebSocket> ConnectPublicWebSocketAsync
575575
/// <param name="url">The sub url for the web socket, or null for none</param>
576576
/// <param name="messageCallback">Callback for messages</param>
577577
/// <param name="connectCallback">Connect callback</param>
578-
/// <param name="textMessageCallback">Text Message callback</param>
579578
/// <returns>Web socket - dispose of the wrapper to shutdown the socket</returns>
580579
public virtual Task<IWebSocket> ConnectPrivateWebSocketAsync
581580
(

src/ExchangeSharp/API/Common/IBaseAPI.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ namespace ExchangeSharp
3434
public interface IBaseAPI : IAPIRequestHandler, INamed
3535
{
3636
#region Properties
37+
/// <summary>
38+
/// API request maker
39+
/// </summary>
40+
IAPIRequestMaker RequestMaker { get; set; }
41+
3742
// BaseUrl is in IAPIRequestHandler
3843

3944
/// <summary>

src/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ protected internal override async Task<IEnumerable<ExchangeMarket>> OnGetMarketS
205205

206206
protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
207207
{
208-
return (await GetMarketSymbolsMetadataAsync()).Select(market => market.MarketSymbol);
208+
return (await GetMarketSymbolsMetadataAsync()).Where(market => market.IsActive).Select(market => market.MarketSymbol);
209209
}
210210

211211
protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnGetCurrenciesAsync()
@@ -436,6 +436,99 @@ private ExchangeTrade ParseTradeWebSocket(JToken token)
436436
return token.ParseTradeCoinbase("size", "price", "side", "time", TimestampType.Iso8601, "trade_id");
437437
}
438438

439+
protected override async Task<IWebSocket> OnUserDataWebSocketAsync(Action<object> callback)
440+
{
441+
return await ConnectPublicWebSocketAsync("/", async (_socket, msg) =>
442+
{
443+
var token = msg.ToStringFromUTF8();
444+
var response = JsonConvert.DeserializeObject<BaseMessage>(token);
445+
switch (response.Type)
446+
{
447+
case ResponseType.Subscriptions:
448+
var subscription = JsonConvert.DeserializeObject<Subscription>(token);
449+
if (subscription.Channels == null || !subscription.Channels.Any())
450+
{
451+
Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() no channels subscribed");
452+
}
453+
else
454+
{
455+
Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() subscribed to " +
456+
$"{string.Join(",", subscription.Channels.Select(c => c.ToString()))}");
457+
}
458+
break;
459+
case ResponseType.Ticker:
460+
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
461+
case ResponseType.Snapshot:
462+
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
463+
case ResponseType.L2Update:
464+
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
465+
case ResponseType.Heartbeat:
466+
var heartbeat = JsonConvert.DeserializeObject<Heartbeat>(token);
467+
Trace.WriteLine($"{nameof(OnUserDataWebSocketAsync)}() heartbeat received {heartbeat}");
468+
break;
469+
case ResponseType.Received:
470+
var received = JsonConvert.DeserializeObject<Received>(token);
471+
callback(received.ExchangeOrderResult);
472+
break;
473+
case ResponseType.Open:
474+
var open = JsonConvert.DeserializeObject<Open>(token);
475+
callback(open.ExchangeOrderResult);
476+
break;
477+
case ResponseType.Done:
478+
var done = JsonConvert.DeserializeObject<Done>(token);
479+
callback(done.ExchangeOrderResult);
480+
break;
481+
case ResponseType.Match:
482+
var match = JsonConvert.DeserializeObject<Match>(token);
483+
callback(match.ExchangeOrderResult);
484+
break;
485+
case ResponseType.LastMatch:
486+
//var lastMatch = JsonConvert.DeserializeObject<LastMatch>(token);
487+
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
488+
case ResponseType.Error:
489+
var error = JsonConvert.DeserializeObject<Error>(token);
490+
throw new APIException($"{error.Reason}: {error.Message}");
491+
case ResponseType.Change:
492+
var change = JsonConvert.DeserializeObject<Change>(token);
493+
callback(change.ExchangeOrderResult);
494+
break;
495+
case ResponseType.Activate:
496+
var activate = JsonConvert.DeserializeObject<Activate>(token);
497+
callback(activate.ExchangeOrderResult);
498+
break;
499+
case ResponseType.Status:
500+
//var status = JsonConvert.DeserializeObject<Status>(token);
501+
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
502+
default:
503+
throw new NotImplementedException($"Not expecting type {response.Type} in {nameof(OnUserDataWebSocketAsync)}()");
504+
}
505+
}, async (_socket) =>
506+
{
507+
var marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
508+
var nonce = await GetNoncePayloadAsync();
509+
string timestamp = nonce["nonce"].ToStringInvariant();
510+
byte[] secret = CryptoUtility.ToBytesBase64Decode(PrivateApiKey);
511+
string toHash = timestamp + "GET" + "/users/self/verify";
512+
var subscribeRequest = new
513+
{
514+
type = "subscribe",
515+
channels = new object[]
516+
{
517+
new
518+
{
519+
name = "user",
520+
product_ids = marketSymbols,
521+
}
522+
},
523+
signature = CryptoUtility.SHA256SignBase64(toHash, secret), // signature base 64 string
524+
key = PublicApiKey.ToUnsecureString(),
525+
passphrase = CryptoUtility.ToUnsecureString(Passphrase),
526+
timestamp = timestamp
527+
};
528+
await _socket.SendMessageAsync(subscribeRequest);
529+
});
530+
}
531+
439532
protected override async Task OnGetHistoricalTradesAsync(Func<IEnumerable<ExchangeTrade>, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null, int? limit = null)
440533
{
441534
/*
@@ -627,7 +720,7 @@ protected override async Task<ExchangeOrderResult> OnGetOrderDetailsAsync(string
627720
protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDetailsAsync(string marketSymbol = null)
628721
{
629722
List<ExchangeOrderResult> orders = new List<ExchangeOrderResult>();
630-
JArray array = await MakeJsonRequestAsync<JArray>("orders?status=open,pending,active" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "&product_id=" + marketSymbol), null, await GetNoncePayloadAsync(), "GET");
723+
JArray array = await MakeJsonRequestAsync<JArray>("orders?status=open&status=pending&status=active" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "&product_id=" + marketSymbol), null, await GetNoncePayloadAsync(), "GET");
631724
foreach (JToken token in array)
632725
{
633726
orders.Add(ParseOrder(token));

src/ExchangeSharp/API/Exchanges/Coinbase/Models/Request/Channel.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
MIT LICENSE
33
44
Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
@@ -25,5 +25,7 @@ internal class Channel
2525

2626
[JsonProperty("product_ids")]
2727
public List<string> ProductIds { get; set; }
28-
}
29-
}
28+
29+
public override string ToString() => $"{Name} channel w/ {ProductIds.Count} symbols";
30+
}
31+
}

0 commit comments

Comments
 (0)