Skip to content

implemented OnUserDataWebSocketAsync() on Coinbase #665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/ExchangeSharp/API/Common/APIRequestMaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ public InternalHttpWebResponse(HttpResponseMessage response)

public IReadOnlyList<string> GetHeader(string name)
{
try
if (response.Headers.TryGetValues(name: name, out var header))
{
return response.Headers.GetValues(name).ToArray(); // throws InvalidOperationException when name not exist
return header.ToArray();
}
catch (Exception)
else
{
return CryptoUtility.EmptyStringArray;
}
Expand Down
1 change: 0 additions & 1 deletion src/ExchangeSharp/API/Common/BaseAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,6 @@ public virtual Task<IWebSocket> ConnectPublicWebSocketAsync
/// <param name="url">The sub url for the web socket, or null for none</param>
/// <param name="messageCallback">Callback for messages</param>
/// <param name="connectCallback">Connect callback</param>
/// <param name="textMessageCallback">Text Message callback</param>
/// <returns>Web socket - dispose of the wrapper to shutdown the socket</returns>
public virtual Task<IWebSocket> ConnectPrivateWebSocketAsync
(
Expand Down
5 changes: 5 additions & 0 deletions src/ExchangeSharp/API/Common/IBaseAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ namespace ExchangeSharp
public interface IBaseAPI : IAPIRequestHandler, INamed
{
#region Properties
/// <summary>
/// API request maker
/// </summary>
IAPIRequestMaker RequestMaker { get; set; }

// BaseUrl is in IAPIRequestHandler

/// <summary>
Expand Down
97 changes: 95 additions & 2 deletions src/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ protected internal override async Task<IEnumerable<ExchangeMarket>> OnGetMarketS

protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
{
return (await GetMarketSymbolsMetadataAsync()).Select(market => market.MarketSymbol);
return (await GetMarketSymbolsMetadataAsync()).Where(market => market.IsActive).Select(market => market.MarketSymbol);
}

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

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 OnGetHistoricalTradesAsync(Func<IEnumerable<ExchangeTrade>, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null, int? limit = null)
{
/*
Expand Down Expand Up @@ -627,7 +720,7 @@ protected override async Task<ExchangeOrderResult> OnGetOrderDetailsAsync(string
protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDetailsAsync(string marketSymbol = null)
{
List<ExchangeOrderResult> orders = new List<ExchangeOrderResult>();
JArray array = await MakeJsonRequestAsync<JArray>("orders?status=open,pending,active" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "&product_id=" + marketSymbol), null, await GetNoncePayloadAsync(), "GET");
JArray array = await MakeJsonRequestAsync<JArray>("orders?status=open&status=pending&status=active" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "&product_id=" + marketSymbol), null, await GetNoncePayloadAsync(), "GET");
foreach (JToken token in array)
{
orders.Add(ParseOrder(token));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
MIT LICENSE

Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
Expand All @@ -25,5 +25,7 @@ internal class Channel

[JsonProperty("product_ids")]
public List<string> ProductIds { get; set; }
}
}

public override string ToString() => $"{Name} channel w/ {ProductIds.Count} symbols";
}
}
Loading