packages feed

coinbase-pro 0.7.2.0 → 0.8.0.0

raw patch · 25 files changed

+334/−146 lines, 25 filesdep +uuidPVP ok

version bump matches the API change (PVP)

Dependencies added: uuid

API changes (from Hackage documentation)

- CoinbasePro.Request: runSandbox :: ClientM a -> IO a
- CoinbasePro.Request: runSandboxWithManager :: Manager -> ClientM a -> IO a
- CoinbasePro.Request: runSandbox_ :: ClientM a -> IO ()
- CoinbasePro.WebSocketFeed.Channel.Full.Received: instance Data.Aeson.Types.ToJSON.ToJSON CoinbasePro.WebSocketFeed.Channel.Full.Received.Received
- CoinbasePro.WebSocketFeed.Request: WSConnection :: HostName -> PortNumber -> WSConnection
- CoinbasePro.WebSocketFeed.Request: [host] :: WSConnection -> HostName
- CoinbasePro.WebSocketFeed.Request: [port] :: WSConnection -> PortNumber
- CoinbasePro.WebSocketFeed.Request: data WSConnection
- CoinbasePro.WebSocketFeed.Request: instance GHC.Classes.Eq CoinbasePro.WebSocketFeed.Request.WSConnection
- CoinbasePro.WebSocketFeed.Request: instance GHC.Show.Show CoinbasePro.WebSocketFeed.Request.WSConnection
- CoinbasePro.WebSocketFeed.Request: wsEndpoint :: WSConnection
+ CoinbasePro.Authenticated: getClientOrder :: ClientOrderId -> CBAuthT ClientM Order
+ CoinbasePro.Authenticated: getOrder :: OrderId -> CBAuthT ClientM Order
+ CoinbasePro.Authenticated.API: getClientOrder :: ClientOrderId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM Order
+ CoinbasePro.Authenticated.API: getOrder :: OrderId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM Order
+ CoinbasePro.Authenticated.Headers: instance Data.Aeson.Types.ToJSON.ToJSON CoinbasePro.Authenticated.Headers.CBAccessKey
+ CoinbasePro.Authenticated.Headers: instance Data.Aeson.Types.ToJSON.ToJSON CoinbasePro.Authenticated.Headers.CBAccessPassphrase
+ CoinbasePro.Authenticated.Headers: instance Data.Aeson.Types.ToJSON.ToJSON CoinbasePro.Authenticated.Headers.CBAccessSign
+ CoinbasePro.Authenticated.Headers: instance Data.Aeson.Types.ToJSON.ToJSON CoinbasePro.Authenticated.Headers.CBAccessTimeStamp
+ CoinbasePro.Authenticated.Orders: [bClientOid] :: PlaceOrderBody -> Maybe ClientOrderId
+ CoinbasePro.Authenticated.Request: mkCBAccessSign :: CBSecretKey -> CBAccessTimeStamp -> Method -> RequestPath -> Body -> CBAccessSign
+ CoinbasePro.Authenticated.Request: mkCBAccessTimeStamp :: IO CBAccessTimeStamp
+ CoinbasePro.Environment: Production :: Environment
+ CoinbasePro.Environment: Sandbox :: Environment
+ CoinbasePro.Environment: WSConnection :: HostName -> PortNumber -> WSConnection
+ CoinbasePro.Environment: [host] :: WSConnection -> HostName
+ CoinbasePro.Environment: [port] :: WSConnection -> PortNumber
+ CoinbasePro.Environment: apiEndpoint :: Environment -> String
+ CoinbasePro.Environment: data Environment
+ CoinbasePro.Environment: data WSConnection
+ CoinbasePro.Environment: instance GHC.Classes.Eq CoinbasePro.Environment.WSConnection
+ CoinbasePro.Environment: instance GHC.Show.Show CoinbasePro.Environment.WSConnection
+ CoinbasePro.Environment: wsEndpoint :: Environment -> WSConnection
+ CoinbasePro.Request: type Runner a = ClientM a -> IO a
+ CoinbasePro.Types: ClientOrderId :: UUID -> ClientOrderId
+ CoinbasePro.Types: [unClientOrderId] :: ClientOrderId -> UUID
+ CoinbasePro.Types: instance Data.Aeson.Types.FromJSON.FromJSON CoinbasePro.Types.ClientOrderId
+ CoinbasePro.Types: instance Data.Aeson.Types.ToJSON.ToJSON CoinbasePro.Types.ClientOrderId
+ CoinbasePro.Types: instance GHC.Classes.Eq CoinbasePro.Types.ClientOrderId
+ CoinbasePro.Types: instance GHC.Classes.Ord CoinbasePro.Types.ClientOrderId
+ CoinbasePro.Types: instance GHC.Show.Show CoinbasePro.Types.ClientOrderId
+ CoinbasePro.Types: instance Web.Internal.HttpApiData.ToHttpApiData CoinbasePro.Types.ClientOrderId
+ CoinbasePro.Types: newtype ClientOrderId
+ CoinbasePro.Types: type ProfileId = Text
+ CoinbasePro.Types: type UserId = Text
+ CoinbasePro.WebSocketFeed.Channel.Full.Change: [profileId] :: Change -> Maybe ProfileId
+ CoinbasePro.WebSocketFeed.Channel.Full.Change: [userId] :: Change -> Maybe UserId
+ CoinbasePro.WebSocketFeed.Channel.Full.Done: [profileId] :: Done -> Maybe ProfileId
+ CoinbasePro.WebSocketFeed.Channel.Full.Done: [userId] :: Done -> Maybe UserId
+ CoinbasePro.WebSocketFeed.Channel.Full.Match: [profileId] :: Match -> Maybe ProfileId
+ CoinbasePro.WebSocketFeed.Channel.Full.Match: [userId] :: Match -> Maybe UserId
+ CoinbasePro.WebSocketFeed.Channel.Full.Open: [profileId] :: Open -> Maybe ProfileId
+ CoinbasePro.WebSocketFeed.Channel.Full.Open: [userId] :: Open -> Maybe UserId
+ CoinbasePro.WebSocketFeed.Channel.Full.Received: [clientOid] :: Received -> Maybe ClientOrderId
+ CoinbasePro.WebSocketFeed.Channel.Full.Received: [profileId] :: Received -> Maybe ProfileId
+ CoinbasePro.WebSocketFeed.Channel.Full.Received: [userId] :: Received -> Maybe UserId
+ CoinbasePro.WebSocketFeed.Request: AuthenticatedWebSocketFeedRequest :: WebSocketFeedRequest -> CBAccessSign -> CBAccessKey -> CBAccessPassphrase -> CBAccessTimeStamp -> AuthenticatedWebSocketFeedRequest
+ CoinbasePro.WebSocketFeed.Request: [cbKey] :: AuthenticatedWebSocketFeedRequest -> CBAccessKey
+ CoinbasePro.WebSocketFeed.Request: [cbPassphrase] :: AuthenticatedWebSocketFeedRequest -> CBAccessPassphrase
+ CoinbasePro.WebSocketFeed.Request: [cbSig] :: AuthenticatedWebSocketFeedRequest -> CBAccessSign
+ CoinbasePro.WebSocketFeed.Request: [cbTimestamp] :: AuthenticatedWebSocketFeedRequest -> CBAccessTimeStamp
+ CoinbasePro.WebSocketFeed.Request: [request] :: AuthenticatedWebSocketFeedRequest -> WebSocketFeedRequest
+ CoinbasePro.WebSocketFeed.Request: data AuthenticatedWebSocketFeedRequest
+ CoinbasePro.WebSocketFeed.Request: instance Data.Aeson.Types.ToJSON.ToJSON CoinbasePro.WebSocketFeed.Request.AuthenticatedWebSocketFeedRequest
+ CoinbasePro.WebSocketFeed.Request: instance GHC.Classes.Eq CoinbasePro.WebSocketFeed.Request.AuthenticatedWebSocketFeedRequest
- CoinbasePro.Authenticated: placeOrder :: ProductId -> Side -> Size -> Price -> Bool -> Maybe OrderType -> Maybe STP -> Maybe TimeInForce -> CBAuthT ClientM Order
+ CoinbasePro.Authenticated: placeOrder :: Maybe ClientOrderId -> ProductId -> Side -> Size -> Price -> Bool -> Maybe OrderType -> Maybe STP -> Maybe TimeInForce -> CBAuthT ClientM Order
- CoinbasePro.Authenticated.Orders: PlaceOrderBody :: ProductId -> Side -> Size -> Price -> Bool -> Maybe OrderType -> Maybe STP -> Maybe TimeInForce -> PlaceOrderBody
+ CoinbasePro.Authenticated.Orders: PlaceOrderBody :: Maybe ClientOrderId -> ProductId -> Side -> Size -> Price -> Bool -> Maybe OrderType -> Maybe STP -> Maybe TimeInForce -> PlaceOrderBody
- CoinbasePro.Authenticated.Request: runCbAuthT :: CoinbaseProCredentials -> CBAuthT ClientM a -> IO a
+ CoinbasePro.Authenticated.Request: runCbAuthT :: Runner a -> CoinbaseProCredentials -> CBAuthT ClientM a -> IO a
- CoinbasePro.Request: run :: ClientM a -> IO a
+ CoinbasePro.Request: run :: Environment -> ClientM a -> IO a
- CoinbasePro.Request: runWithManager :: Manager -> ClientM a -> IO a
+ CoinbasePro.Request: runWithManager :: Manager -> Environment -> ClientM a -> IO a
- CoinbasePro.Request: run_ :: ClientM a -> IO ()
+ CoinbasePro.Request: run_ :: Environment -> ClientM a -> IO ()
- CoinbasePro.WebSocketFeed: subscribeToFeed :: [ProductId] -> [ChannelName] -> IO (InputStream ChannelMessage)
+ CoinbasePro.WebSocketFeed: subscribeToFeed :: [ProductId] -> [ChannelName] -> Environment -> Maybe CoinbaseProCredentials -> IO (InputStream ChannelMessage)
- CoinbasePro.WebSocketFeed.Channel.Full.Activate: Activate :: ProductId -> UTCTime -> UserId -> ProfileId -> OrderId -> StopType -> Side -> Price -> Size -> Funds -> TakerFeeRate -> Bool -> Activate
+ CoinbasePro.WebSocketFeed.Channel.Full.Activate: Activate :: ProductId -> UTCTime -> OrderId -> StopType -> Side -> Price -> Size -> Funds -> TakerFeeRate -> Bool -> UserId -> ProfileId -> Activate
- CoinbasePro.WebSocketFeed.Channel.Full.Change: Change :: UTCTime -> Sequence -> OrderId -> ProductId -> Maybe Size -> Maybe Size -> Maybe Funds -> Maybe Funds -> Maybe Price -> Side -> Change
+ CoinbasePro.WebSocketFeed.Channel.Full.Change: Change :: UTCTime -> Sequence -> OrderId -> ProductId -> Maybe Size -> Maybe Size -> Maybe Funds -> Maybe Funds -> Maybe Price -> Side -> Maybe UserId -> Maybe ProfileId -> Change
- CoinbasePro.WebSocketFeed.Channel.Full.Done: Done :: UTCTime -> ProductId -> Sequence -> Maybe Price -> OrderId -> Reason -> Side -> Maybe Size -> Done
+ CoinbasePro.WebSocketFeed.Channel.Full.Done: Done :: UTCTime -> ProductId -> Sequence -> Maybe Price -> OrderId -> Reason -> Side -> Maybe Size -> Maybe UserId -> Maybe ProfileId -> Done
- CoinbasePro.WebSocketFeed.Channel.Full.Match: Match :: TradeId -> Sequence -> OrderId -> OrderId -> UTCTime -> ProductId -> Size -> Price -> Side -> Match
+ CoinbasePro.WebSocketFeed.Channel.Full.Match: Match :: TradeId -> Sequence -> OrderId -> OrderId -> UTCTime -> ProductId -> Size -> Price -> Side -> Maybe UserId -> Maybe ProfileId -> Match
- CoinbasePro.WebSocketFeed.Channel.Full.Open: Open :: UTCTime -> ProductId -> Sequence -> OrderId -> Price -> Maybe Size -> Side -> Open
+ CoinbasePro.WebSocketFeed.Channel.Full.Open: Open :: UTCTime -> ProductId -> Sequence -> OrderId -> Price -> Maybe Size -> Side -> Maybe UserId -> Maybe ProfileId -> Open
- CoinbasePro.WebSocketFeed.Channel.Full.Received: Received :: UTCTime -> ProductId -> Sequence -> OrderId -> Maybe Size -> Maybe Price -> Maybe Funds -> Side -> OrderType -> Received
+ CoinbasePro.WebSocketFeed.Channel.Full.Received: Received :: UTCTime -> ProductId -> Sequence -> OrderId -> Maybe ClientOrderId -> Maybe Size -> Maybe Price -> Maybe Funds -> Side -> OrderType -> Maybe UserId -> Maybe ProfileId -> Received

Files

README.md view
@@ -21,12 +21,12 @@         - [x] Get An Account         - [ ] Get Account History         - [ ] Get Holds-    - [ ] Orders+    - [x] Orders         - [x] Place a New Order         - [x] Cancel an Order         - [x] Cancel all         - [x] List Orders-        - [ ] Get an Order+        - [x] Get an Order     - [x] Fills         - [x] List Fills     - [ ] Deposits@@ -53,7 +53,7 @@         - [x] The status channel         - [x] The ticker channel         - [x] The level2 channel-        - [ ] The user channel+        - [x] The user channel         - [x] The matches channel         - [x] The full channel - FIX API@@ -88,7 +88,7 @@ ```haskell main :: IO () main = do-    msgs <- subscribeToFeed [ProductId "BTC-USD"] [Ticker]+    msgs <- subscribeToFeed [ProductId "BTC-USD"] [Ticker] Nothing     forever $ Streams.read msgs >>= print ``` 
changelog.md view
@@ -1,3 +1,44 @@+# Version 0.8.0.0++## Breaking API changes:++- `Environment` concept has been introduced. This allows the user to specify an endpoint (production, sandbox)+  in which to query or stream.++    - `run` (and associated functions) now take an `Environment` as the first parameter. `runSandbox` and associated+      functions have been removed.+    - `runWithManager` now has different parameter ordering. Signature is `runWithManager :: Manager -> Environment -> ClientM a -> IO a`+    - `runCbAuthT` now takes a `Runner a`, where `Runner a = ClientM a -> IO a`; allows users to specify an environment in which to run++- `placeOrder` now has `Maybe ClientOrderId` as the first parameter that allows users to assign `client_oid` to+  submitted orders. The `client_oid` field is visible on the `Full` channel.++- `CoinbasePro.WebSocketFeed.subscribeToFeed` has two additional parameters:++    - `Environment`+    - `Maybe CoinbaseProCredentials`: allows users to authenticate on subscription. See `User`++- Moved `UserId` and `ProfileId` to `CoinbasePro.Types` from `CoinbasePro.WebSocketFeed.Channel.Full.Activate`++## Additional Features:++- Added `User` channel functionality that allows users to see `user_id` and `profile_id` on messages that pertain to the subscriber.+  See [documentation](https://docs.pro.coinbase.com/#the-user-channel) for more details++    - `user_id` and `profile_id` fields are now parsed from `Full` channel messages if available.++- `CoinbasePro.Types.ClientOrderId` has been introduced to specify `client_oid` in `placeOrder` API request.+  - `client_oid` is now parsed from `CoinbasePro.WebSocketFeed.Channel.Full.Received.++- `CoinbasePro.Authenticated.getOrder` allows users to query status of order++- `CoinbasePro.Authenticated.getClientOrder` allows users to query status of order using `client_oid`+++## Misc++- minor cleanups of unused imports+ # Version 0.7.2.0  - Upgraded to stack lts-14.17 
coinbase-pro.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: e58a9cfeb1d43417b9df67df9f0855f31e070618162e2ef3836257eb606a9670+-- hash: a870948505578ac331822c4d53b73343bd8cea3178cd634aace31f0a118981a3  name:           coinbase-pro-version:        0.7.2.0+version:        0.8.0.0 synopsis:       Client for Coinbase Pro description:    Client for Coinbase Pro REST and Websocket APIs category:       Web, Finance@@ -15,7 +15,7 @@ bug-reports:    https://github.com/mdunnio/coinbase-pro/issues author:         Michael Dunn <michaelsdunn1@gmail.com> maintainer:     Michael Dunn <michaelsdunn1@gmail.com>-copyright:      2019 Michael Dunn <michaelsdunn1@gmail.com>+copyright:      2020 Michael Dunn <michaelsdunn1@gmail.com> license:        MIT license-file:   LICENSE build-type:     Simple@@ -36,6 +36,7 @@       CoinbasePro.Authenticated.Headers       CoinbasePro.Authenticated.Orders       CoinbasePro.Authenticated.Request+      CoinbasePro.Environment       CoinbasePro.Headers       CoinbasePro.MarketData.AggregateOrderBook       CoinbasePro.MarketData.FullOrderBook@@ -88,6 +89,7 @@     , transformers >=0.5 && <0.6     , unagi-streams >=0.2 && <0.3     , unordered-containers >=0.2 && <0.3+    , uuid >=1.3 && <1.4     , vector >=0.12 && <0.13     , websockets >=0.12 && <0.13     , wuss >=1.1 && <1.2@@ -126,6 +128,7 @@     , transformers >=0.5 && <0.6     , unagi-streams >=0.2 && <0.3     , unordered-containers >=0.2 && <0.3+    , uuid >=1.3 && <1.4     , vector >=0.12 && <0.13     , websockets >=0.12 && <0.13     , wuss >=1.1 && <1.2@@ -164,6 +167,7 @@     , transformers >=0.5 && <0.6     , unagi-streams >=0.2 && <0.3     , unordered-containers >=0.2 && <0.3+    , uuid >=1.3 && <1.4     , vector >=0.12 && <0.13     , websockets >=0.12 && <0.13     , wuss >=1.1 && <1.2
src/example/request/Main.hs view
@@ -9,6 +9,7 @@ import           CoinbasePro.Authenticated.Headers import           CoinbasePro.Authenticated.Orders import           CoinbasePro.Authenticated.Request+import           CoinbasePro.Environment import           CoinbasePro.MarketData.Types       hiding (time) import           CoinbasePro.Request import           CoinbasePro.Types                  hiding (time)@@ -17,21 +18,21 @@  main :: IO () main = do-    run (stats btcusd) >>= print-    run (candles btcusd Nothing Nothing Minute) >>= print-    run (trades btcusd) >>= print-    run time >>= print-    run products >>= print-    run (aggregateOrderBook btcusd (Just Best)) >>= print-    run (aggregateOrderBook btcusd (Just TopFifty)) >>= print-    run (fullOrderBook btcusd) >>= print-    runCbAuthT cpc $ do+    run Production (stats btcusd) >>= print+    run Production (candles btcusd Nothing Nothing Minute) >>= print+    run Production (trades btcusd) >>= print+    run Production time >>= print+    run Production products >>= print+    run Production (aggregateOrderBook btcusd (Just Best)) >>= print+    run Production (aggregateOrderBook btcusd (Just TopFifty)) >>= print+    run Production (fullOrderBook btcusd) >>= print+    runCbAuthT (run Production) cpc $ do         accounts >>= liftIO . print         account aid >>= liftIO . print         fills (Just btcusd) Nothing >>= liftIO . print         listOrders (Just [All]) (Just btcusd) >>= liftIO . print-        placeOrder btcusd Sell (Size 0.001) (Price 99999.00) True Nothing Nothing Nothing >>= liftIO . print-        placeOrder btcusd Buy (Size 1.0) (Price 1.00) True Nothing Nothing Nothing >>= liftIO . print+        placeOrder Nothing btcusd Sell (Size 0.001) (Price 99999.00) True Nothing Nothing Nothing >>= liftIO . print+        placeOrder Nothing btcusd Buy (Size 1.0) (Price 1.00) True Nothing Nothing Nothing >>= liftIO . print         cancelAll (Just btcusd) >>= liftIO . print   where     accessKey  = CBAccessKey "accesskey"
src/example/stream/Main.hs view
@@ -5,11 +5,13 @@ import           Control.Monad                     (forever) import qualified System.IO.Streams                 as Streams +import           CoinbasePro.Environment           (Environment (..)) import           CoinbasePro.Types                 (ProductId (..)) import           CoinbasePro.WebSocketFeed         (subscribeToFeed) import           CoinbasePro.WebSocketFeed.Request (ChannelName (..)) + main :: IO () main = do-    msgs <- subscribeToFeed [ProductId "BTC-USD"] [Status]+    msgs <- subscribeToFeed [ProductId "BTC-USD"] [Full] Sandbox Nothing     forever $ Streams.read msgs >>= print
src/lib/CoinbasePro/Authenticated.hs view
@@ -6,6 +6,8 @@   ( accounts   , account   , listOrders+  , getOrder+  , getClientOrder   , placeOrder   , cancelOrder   , cancelAll@@ -23,6 +25,7 @@ import qualified Data.Set                           as S import           Data.Text                          (Text, pack, toLower,                                                      unpack)+import           Data.UUID                          (toString) import           Network.HTTP.Types                 (SimpleQuery,                                                      SimpleQueryItem,                                                      methodDelete, methodGet,@@ -42,7 +45,8 @@ import           CoinbasePro.Request                (RequestPath)  -import           CoinbasePro.Types                  (OrderId (..), OrderType,+import           CoinbasePro.Types                  (ClientOrderId (..),+                                                     OrderId (..), OrderType,                                                      Price, ProductId (..),                                                      Side, Size) @@ -76,12 +80,35 @@     defaultStatus = fromMaybe [All]  +-- | https://docs.pro.coinbase.com/#get-an-order+getOrder :: OrderId -> CBAuthT ClientM Order+getOrder oid = authRequest methodGet (mkRequestPath "/orders") "" $ API.getOrder oid+  where+    mkRequestPath rp = rp ++ "/" ++ unpack (unOrderId oid)+++-- | https://docs.pro.coinbase.com/#get-an-order+getClientOrder :: ClientOrderId -> CBAuthT ClientM Order+getClientOrder cloid = authRequest methodGet (mkRequestPath "/orders/client:") "" $ API.getClientOrder cloid+  where+    mkRequestPath rp = rp ++ toString (unClientOrderId cloid)++ -- | https://docs.pro.coinbase.com/?javascript#place-a-new-order-placeOrder :: ProductId -> Side -> Size -> Price -> Bool -> Maybe OrderType -> Maybe STP -> Maybe TimeInForce -> CBAuthT ClientM Order-placeOrder prid sd sz price po ot stp tif =+placeOrder :: Maybe ClientOrderId+           -> ProductId+           -> Side+           -> Size+           -> Price+           -> Bool+           -> Maybe OrderType+           -> Maybe STP+           -> Maybe TimeInForce+           -> CBAuthT ClientM Order+placeOrder clordid prid sd sz price po ot stp tif =     authRequest methodPost "/orders" (LC8.unpack $ encode body) $ API.placeOrder body   where-    body = PlaceOrderBody prid sd sz price po ot stp tif+    body = PlaceOrderBody clordid prid sd sz price po ot stp tif   -- | https://docs.pro.coinbase.com/?javascript#cancel-an-order
src/lib/CoinbasePro/Authenticated/API.hs view
@@ -6,6 +6,8 @@     ( accounts     , singleAccount     , listOrders+    , getOrder+    , getClientOrder     , placeOrder     , cancelOrder     , cancelAll@@ -29,13 +31,16 @@                                                      Status (..)) import           CoinbasePro.Authenticated.Request  (AuthDelete, AuthGet,                                                      AuthPost)-import           CoinbasePro.Types                  (OrderId (..),+import           CoinbasePro.Types                  (ClientOrderId (..),+                                                     OrderId (..),                                                      ProductId (..))   type API =    "accounts" :> AuthGet [Account]          :<|> "accounts" :> Capture "account-id" AccountId :> AuthGet Account          :<|> "orders" :> QueryParams "status" Status :> QueryParam "product_id" ProductId :> AuthGet [Order]+         :<|> "orders" :> Capture "order_id" OrderId :> AuthGet Order+         :<|> "orders" :> Capture "client_oid" ClientOrderId :> AuthGet Order          :<|> "orders" :> ReqBody '[JSON] PlaceOrderBody :> AuthPost Order          :<|> "orders" :> Capture "order_id" OrderId :> AuthDelete NoContent          :<|> "orders" :> QueryParam "product_id" ProductId :> AuthDelete [OrderId]@@ -51,10 +56,12 @@ accounts :: AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM [Account] singleAccount :: AccountId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM Account listOrders :: [Status] -> Maybe ProductId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM [Order]+getOrder :: OrderId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM Order+getClientOrder :: ClientOrderId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM Order placeOrder :: PlaceOrderBody -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM Order cancelOrder :: OrderId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM NoContent cancelAll :: Maybe ProductId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM [OrderId] fills :: Maybe ProductId -> Maybe OrderId -> AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM [Fill] fees :: AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM Fees trailingVolume :: AuthenticatedRequest (AuthProtect "CBAuth") -> ClientM [TrailingVolume]-accounts :<|> singleAccount :<|> listOrders :<|> placeOrder :<|> cancelOrder :<|> cancelAll :<|> fills :<|> fees :<|> trailingVolume = client api+accounts :<|> singleAccount :<|> listOrders :<|> getOrder :<|> getClientOrder :<|> placeOrder :<|> cancelOrder :<|> cancelAll :<|> fills :<|> fees :<|> trailingVolume = client api
src/lib/CoinbasePro/Authenticated/Headers.hs view
@@ -5,6 +5,7 @@     , CBAccessPassphrase(..)     ) where +import           Data.Aeson         (ToJSON (..), Value (String)) import           Data.ByteString    (ByteString) import           Data.Text          (pack) import           Data.Text.Encoding (decodeUtf8)@@ -20,6 +21,10 @@     toQueryParam (CBAccessKey k) = pack k  +instance ToJSON CBAccessKey where+    toJSON (CBAccessKey k) = String $ pack k++ newtype CBAccessSign = CBAccessSign ByteString     deriving (Eq, Show) @@ -29,6 +34,10 @@     toQueryParam (CBAccessSign s) = decodeUtf8 s  +instance ToJSON CBAccessSign where+    toJSON (CBAccessSign s) = String $ decodeUtf8 s++ newtype CBAccessTimeStamp = CBAccessTimeStamp String     deriving (Eq, Show) @@ -38,8 +47,16 @@     toQueryParam (CBAccessTimeStamp ts) = pack ts  +instance ToJSON CBAccessTimeStamp where+    toJSON (CBAccessTimeStamp ts) = String $ pack ts++ newtype CBAccessPassphrase = CBAccessPassphrase String     deriving (Eq, Show)+++instance ToJSON CBAccessPassphrase where+    toJSON (CBAccessPassphrase p) = String $ pack p   instance ToHttpApiData CBAccessPassphrase where
src/lib/CoinbasePro/Authenticated/Orders.hs view
@@ -23,8 +23,9 @@ import           Data.Text         (pack, toLower, unpack) import           Web.HttpApiData   (ToHttpApiData (..)) -import           CoinbasePro.Types (CreatedAt, OrderId, OrderType, Price,-                                    ProductId, Side, Size, filterOrderFieldName)+import           CoinbasePro.Types (ClientOrderId, CreatedAt, OrderId,+                                    OrderType, Price, ProductId, Side, Size,+                                    filterOrderFieldName)   -- TODO: All is not a status@@ -110,7 +111,8 @@   data PlaceOrderBody = PlaceOrderBody-    { bProductId :: ProductId+    { bClientOid :: Maybe ClientOrderId+    , bProductId :: ProductId     , bSide      :: Side     , bSize      :: Size     , bPrice     :: Price
src/lib/CoinbasePro/Authenticated/Request.hs view
@@ -16,6 +16,8 @@     , AuthDelete      , authRequest+    , mkCBAccessSign+    , mkCBAccessTimeStamp     ) where  import           Control.Monad.IO.Class            (MonadIO, liftIO)@@ -47,7 +49,7 @@                                                     CBAccessSign (..),                                                     CBAccessTimeStamp (..)) import           CoinbasePro.Headers               (userAgent)-import           CoinbasePro.Request               (Body, RequestPath, run)+import           CoinbasePro.Request               (Body, RequestPath, Runner)   newtype CBSecretKey = CBSecretKey String@@ -65,8 +67,8 @@     deriving (Functor, Applicative, Monad, MonadIO, MonadTrans)  -runCbAuthT :: CoinbaseProCredentials -> CBAuthT ClientM a -> IO a-runCbAuthT cpc = run . flip runReaderT cpc . unCbAuth+runCbAuthT :: Runner a -> CoinbaseProCredentials -> CBAuthT ClientM a -> IO a+runCbAuthT runEnv cpc = runEnv . flip runReaderT cpc . unCbAuth   type instance AuthClientData (AuthProtect "CBAuth") = (CBAccessKey, CBAccessSign, CBAccessTimeStamp, CBAccessPassphrase)
+ src/lib/CoinbasePro/Environment.hs view
@@ -0,0 +1,46 @@+module CoinbasePro.Environment+    ( Environment (..)+    , WSConnection (..)++    , apiEndpoint+    , wsEndpoint+    ) where+++import           Network.Socket          (HostName)+import           Network.Socket.Internal (PortNumber)+++data Environment = Production | Sandbox+++apiEndpoint :: Environment -> String+apiEndpoint Production = productionAPIEndpoint+apiEndpoint Sandbox    = sandboxAPIEndpoint+++productionAPIEndpoint :: String+productionAPIEndpoint = "api.pro.coinbase.com"+++sandboxAPIEndpoint :: String+sandboxAPIEndpoint = "api-public.sandbox.pro.coinbase.com"+++data WSConnection = WSConnection+    { host :: HostName+    , port :: PortNumber+    } deriving (Eq, Show)+++wsEndpoint :: Environment -> WSConnection+wsEndpoint Production = productionWSEndpoint+wsEndpoint Sandbox    = sandboxWSEndpoint+++productionWSEndpoint :: WSConnection+productionWSEndpoint = WSConnection "ws-feed.pro.coinbase.com" 443+++sandboxWSEndpoint :: WSConnection+sandboxWSEndpoint = WSConnection "ws-feed-public.sandbox.pro.coinbase.com" 443
src/lib/CoinbasePro/Headers.hs view
@@ -24,7 +24,7 @@   userAgent :: UserAgent-userAgent = UserAgent "coinbase-pro/0.7"+userAgent = UserAgent "coinbase-pro/0.8"   type UserAgentHeader = RequiredHeader "User-Agent" UserAgent
src/lib/CoinbasePro/Request.hs view
@@ -1,7 +1,6 @@-{-# LANGUAGE DataKinds         #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies      #-}-{-# LANGUAGE TypeOperators     #-}+{-# LANGUAGE DataKinds     #-}+{-# LANGUAGE TypeFamilies  #-}+{-# LANGUAGE TypeOperators #-}  module CoinbasePro.Request     ( RequestPath@@ -10,15 +9,11 @@     , CBGet     , CBRequest -    -- * Production     , run     , run_     , runWithManager -    -- * Sandbox-    , runSandbox-    , runSandbox_-    , runSandboxWithManager+    , Runner     ) where  import           Control.Exception       (throw)@@ -28,6 +23,7 @@ import           Servant.API             ((:>), Get, JSON) import           Servant.Client +import           CoinbasePro.Environment (Environment, apiEndpoint) import           CoinbasePro.Headers     (UserAgent, UserAgentHeader)  @@ -41,19 +37,24 @@ type Body        = String  +type Runner a = ClientM a -> IO a++ ------------------------------------------------------------------------------ -- | Runs a coinbase pro HTTPS request and returns the result `a` ----- > run products >>= print+-- > run Production products >>= print ---run :: ClientM a -> IO a-run f = flip runWithManager f =<< newManager tlsManagerSettings+run :: Environment -> ClientM a -> IO a+run env f = do+    mgr <- newManager tlsManagerSettings+    runWithManager mgr env f   ------------------------------------------------------------------------------ -- | Same as 'run', except uses `()` instead of a type `a`-run_ :: ClientM a -> IO ()-run_ = void . run+run_ :: Environment -> ClientM a -> IO ()+run_ = (void .) . run   ------------------------------------------------------------------------------@@ -63,43 +64,12 @@ -- @ -- do $ -- mgr  <- newManager tlsManagerSettings--- prds <- runWithManager mgr products--- print prds--- @----runWithManager :: Manager -> ClientM a -> IO a-runWithManager mgr f = either throw return =<<-    runClientM f (mkClientEnv mgr (BaseUrl Https production 443 mempty))-  where-    production = "api.pro.coinbase.com"----------------------------------------------------------------------------------- | Runs a coinbase pro HTTPS request in sandbox and returns the result 'a'------ > run products >>= print----runSandbox :: ClientM a -> IO a-runSandbox f = flip runSandboxWithManager f =<< newManager tlsManagerSettings------------------------------------------------------------------------------------ | Same as 'runSandbox', except uses '()' instead of a type 'a'-runSandbox_ :: ClientM a -> IO ()-runSandbox_ = void . runSandbox------------------------------------------------------------------------------------ | Allows the user to use their own 'Network.HTTP.Client.Types.ManagerSettings`------ @--- do $--- mgr  <- newManager tlsManagerSettings--- prds <- runSandboxWithManager mgr products+-- prds <- runWithManager mgr Production products -- print prds -- @ ---runSandboxWithManager :: Manager -> ClientM a -> IO a-runSandboxWithManager mgr f = either throw return =<<-    runClientM f (mkClientEnv mgr (BaseUrl Https sandbox 443 mempty))+runWithManager :: Manager -> Environment -> ClientM a -> IO a+runWithManager mgr env f = either throw return =<<+    runClientM f (mkClientEnv mgr (BaseUrl Https api 443 mempty))   where-    sandbox = "api-public.sandbox.pro.coinbase.com"+    api = apiEndpoint env
src/lib/CoinbasePro/Types.hs view
@@ -5,9 +5,12 @@  module CoinbasePro.Types     ( OrderId (..)+    , ClientOrderId (..)     , Price (..)     , ProductId (..)     , Sequence+    , UserId+    , ProfileId     , Side (..)     , Size (..)     , Volume (..)@@ -23,9 +26,9 @@     , filterOrderFieldName     ) where -import           Data.Aeson            (FromJSON, ToJSON, Value (Null),-                                        parseJSON, toJSON, withArray,-                                        withObject, withText, (.:), (.:?))+import           Data.Aeson            (FromJSON, ToJSON, parseJSON, toJSON,+                                        withArray, withObject, withText, (.:),+                                        (.:?)) import qualified Data.Aeson            as A import           Data.Aeson.Casing     (camelCase, snakeCase) import           Data.Aeson.TH         (constructorTagModifier, defaultOptions,@@ -34,13 +37,17 @@ import           Data.Text             (Text, pack, toLower, unpack) import           Data.Time.Clock       (UTCTime) import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import           Data.UUID             (UUID, toText) import qualified Data.Vector           as V import           Servant.API import           Text.Printf           (printf)  -type Sequence = Int+type UserId    = Text+type ProfileId = Text+type Sequence  = Int + data Side = Buy | Sell     deriving (Eq, Ord, Show) @@ -64,6 +71,19 @@     { fieldLabelModifier = snakeCase     , unwrapUnaryRecords = True     } ''OrderId+++newtype ClientOrderId = ClientOrderId { unClientOrderId :: UUID }+    deriving (Eq, Ord, Show)+++instance ToHttpApiData ClientOrderId where+    toUrlPiece = ("client:" <>) . toText . unClientOrderId+++deriveJSON defaultOptions+    { unwrapUnaryRecords = True+    } ''ClientOrderId   newtype ProductId = ProductId { unProductId :: Text }
src/lib/CoinbasePro/Unauthenticated.hs view
@@ -22,7 +22,6 @@                                                             CBTime,                                                             FullBookLevel (..),                                                             Product, Trade)-import           CoinbasePro.Request                       (run) import           CoinbasePro.Types                         (Candle,                                                             CandleGranularity,                                                             Currency, ProductId,
src/lib/CoinbasePro/WebSocketFeed.hs view
@@ -4,34 +4,58 @@  import           Control.Concurrent                 (forkIO) import           Control.Monad                      (forever)+import           Control.Monad.IO.Class             (liftIO) import           Data.Aeson                         (eitherDecode', encode) import           Data.Either                        (either)+import           Data.Maybe                         (maybe)+import           Network.HTTP.Types                 (methodGet) import qualified Network.WebSockets                 as WS import qualified System.IO.Streams                  as Streams import           System.IO.Streams.Concurrent.Unagi (makeChanPipe) import qualified Wuss                               as WU +import           CoinbasePro.Authenticated.Request  (CoinbaseProCredentials (..),+                                                     mkCBAccessSign,+                                                     mkCBAccessTimeStamp)+import           CoinbasePro.Environment            (Environment,+                                                     WSConnection (..),+                                                     wsEndpoint) import           CoinbasePro.Types                  (ProductId) import           CoinbasePro.WebSocketFeed.Channel  (ChannelMessage (..))-import           CoinbasePro.WebSocketFeed.Request  (ChannelName (..),+import           CoinbasePro.WebSocketFeed.Request  (AuthenticatedWebSocketFeedRequest (..),+                                                     ChannelName (..),                                                      RequestMessageType (..),-                                                     WebSocketFeedRequest (..),-                                                     wsEndpoint)-import qualified CoinbasePro.WebSocketFeed.Request  as WR+                                                     WebSocketFeedRequest (..))  -subscribeToFeed :: [ProductId] -> [ChannelName] -> IO (Streams.InputStream ChannelMessage)-subscribeToFeed prids channels = do+subscribeToFeed :: [ProductId] -> [ChannelName] -> Environment -> Maybe CoinbaseProCredentials -> IO (Streams.InputStream ChannelMessage)+subscribeToFeed prds channels env = subscribe (wsEndpoint env) prds channels+++subscribe :: WSConnection -> [ProductId] -> [ChannelName] -> Maybe CoinbaseProCredentials -> IO (Streams.InputStream ChannelMessage)+subscribe wsConn prids channels cpc = do     (is, os) <- makeChanPipe+    req      <- mkWsRequest cpc+     _ <- forkIO . WU.runSecureClient wsHost wsPort "/" $ \conn -> do-        WS.sendTextData conn $ encode request+        WS.sendTextData conn req         forever $ parseFeed conn >>= Streams.writeTo os . Just+     return is   where-    wsHost = WR.host wsEndpoint-    wsPort = WR.port wsEndpoint+    wsHost = host wsConn+    wsPort = port wsConn -    request = WebSocketFeedRequest Subscribe prids channels+    mkWsRequest = maybe (return $ encode wsRequest) (fmap encode . authWsRequest)++    wsRequest = WebSocketFeedRequest Subscribe prids channels++    authWsRequest cpc' = do+        ts <- liftIO mkCBAccessTimeStamp+        let cbs = mkCBAccessSign (cbSecretKey cpc') ts methodGet authSubscriptionPath ""+        return $ AuthenticatedWebSocketFeedRequest wsRequest cbs (cbAccessKey cpc') (cbAccessPassphrase cpc') ts++    authSubscriptionPath = "/users/self/verify"   parseFeed :: WS.Connection -> IO ChannelMessage
src/lib/CoinbasePro/WebSocketFeed/Channel.hs view
@@ -23,6 +23,7 @@ import           CoinbasePro.WebSocketFeed.Channel.Ticker        (Ticker (..)) import           CoinbasePro.WebSocketFeed.Response              (Subscription) + data ChannelMessage =       ActivateMessage Activate     | ChangeMessage Change
src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Activate.hs view
@@ -11,12 +11,10 @@ import           Data.Text         (Text) import           Data.Time.Clock   (UTCTime) -import           CoinbasePro.Types (Funds, OrderId, Price, ProductId, Side,-                                    Size)+import           CoinbasePro.Types (Funds, OrderId, Price, ProductId, ProfileId,+                                    Side, Size, UserId)  -type UserId       = Int-type ProfileId    = Text type StopType     = Text type TakerFeeRate = Double @@ -24,8 +22,6 @@ data Activate = Activate     { productId    :: ProductId     , timestamp    :: UTCTime-    , userId       :: UserId-    , profileId    :: ProfileId     , orderId      :: OrderId     , stopType     :: StopType     , side         :: Side@@ -34,6 +30,8 @@     , funds        :: Funds     , takerFeeRate :: TakerFeeRate     , private      :: Bool+    , userId       :: UserId+    , profileId    :: ProfileId     } deriving (Eq, Ord, Show)  
src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Change.hs view
@@ -10,8 +10,8 @@                                     fieldLabelModifier) import           Data.Time.Clock   (UTCTime) -import           CoinbasePro.Types (Funds, OrderId, Price, ProductId, Sequence,-                                    Side, Size)+import           CoinbasePro.Types (Funds, OrderId, Price, ProductId, ProfileId,+                                    Sequence, Side, Size, UserId)   data Change = Change@@ -25,6 +25,8 @@     , oldFunds  :: Maybe Funds     , price     :: Maybe Price     , side      :: Side+    , userId    :: Maybe UserId+    , profileId :: Maybe ProfileId     } deriving (Eq, Ord, Show)  
src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Done.hs view
@@ -13,8 +13,8 @@ import           Data.Text         (Text) import           Data.Time.Clock   (UTCTime) -import           CoinbasePro.Types (OrderId, Price, ProductId, Sequence, Side,-                                    Size)+import           CoinbasePro.Types (OrderId, Price, ProductId, ProfileId,+                                    Sequence, Side, Size, UserId)   type Reason = Text@@ -36,6 +36,8 @@     , reason        :: Reason     , side          :: Side     , remainingSize :: Maybe Size+    , userId        :: Maybe UserId+    , profileId     :: Maybe ProfileId     } deriving (Eq, Ord, Show)  
src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Match.hs view
@@ -10,8 +10,8 @@                                     fieldLabelModifier) import           Data.Time.Clock   (UTCTime) -import           CoinbasePro.Types (OrderId, Price, ProductId, Sequence, Side,-                                    Size)+import           CoinbasePro.Types (OrderId, Price, ProductId, ProfileId,+                                    Sequence, Side, Size, UserId)   type TradeId = Int@@ -27,6 +27,8 @@     , size         :: Size     , price        :: Price     , side         :: Side+    , userId       :: Maybe UserId+    , profileId    :: Maybe ProfileId     } deriving (Eq, Ord, Show)  
src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Open.hs view
@@ -10,8 +10,8 @@                                     fieldLabelModifier) import           Data.Time.Clock   (UTCTime) -import           CoinbasePro.Types (OrderId, Price, ProductId, Sequence, Side,-                                    Size)+import           CoinbasePro.Types (OrderId, Price, ProductId, ProfileId,+                                    Sequence, Side, Size, UserId)   data Open = Open@@ -22,6 +22,8 @@     , price         :: Price     , remainingSize :: Maybe Size     , side          :: Side+    , userId        :: Maybe UserId+    , profileId     :: Maybe ProfileId     } deriving (Eq, Ord, Show)  
src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Received.hs view
@@ -1,17 +1,16 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell   #-}  module CoinbasePro.WebSocketFeed.Channel.Full.Received     ( Received (..)     ) where -import           Data.Aeson.Casing (snakeCase)-import           Data.Aeson.TH     (defaultOptions, deriveJSON,-                                    fieldLabelModifier)+import           Data.Aeson        (FromJSON (..), withObject, (.:), (.:?)) import           Data.Time.Clock   (UTCTime)+import           Data.UUID         (fromString) -import           CoinbasePro.Types (Funds, OrderId, OrderType, Price, ProductId,-                                    Sequence, Side, Size)+import           CoinbasePro.Types (ClientOrderId (..), Funds, OrderId,+                                    OrderType, Price, ProductId, ProfileId,+                                    Sequence, Side, Size, UserId)   data Received = Received@@ -19,12 +18,29 @@     , productId :: ProductId     , sequence  :: Sequence     , orderId   :: OrderId+    , clientOid :: Maybe ClientOrderId     , size      :: Maybe Size     , price     :: Maybe Price     , funds     :: Maybe Funds     , side      :: Side     , orderType :: OrderType+    , userId    :: Maybe UserId+    , profileId :: Maybe ProfileId     } deriving (Eq, Ord, Show)  -deriveJSON defaultOptions {fieldLabelModifier = snakeCase} ''Received+instance FromJSON Received where+    parseJSON = withObject "received" $ \o -> do+        t     <- o .: "time"+        prd   <- o .: "product_id"+        sq    <- o .: "sequence"+        oid   <- o .: "order_id"+        cloid <- (ClientOrderId <$>) . fromString <$> (o .: "client_oid")+        sz    <- o .:? "size"+        px    <- o .:? "price"+        fs    <- o .:? "funds"+        sd    <- o .: "side"+        ot    <- o .: "order_type"+        ui    <- o .:? "user_id"+        pfid  <- o .:? "profile_id"+        return $ Received t prd sq oid cloid sz px fs sd ot ui pfid
src/lib/CoinbasePro/WebSocketFeed/Channel/Status.hs view
@@ -6,10 +6,6 @@  import           Data.Aeson                   (FromJSON, parseJSON, withObject,                                                (.:))-import           Data.Aeson.Casing            (snakeCase)-import           Data.Aeson.TH                (defaultOptions, deriveJSON,-                                               fieldLabelModifier)-import           Data.Text                    (Text)  import           CoinbasePro.MarketData.Types (Product) import           CoinbasePro.Types            (Currency)
src/lib/CoinbasePro/WebSocketFeed/Request.hs view
@@ -1,33 +1,21 @@+{-# LANGUAGE LambdaCase        #-} {-# LANGUAGE OverloadedStrings #-}  module CoinbasePro.WebSocketFeed.Request-  ( WSConnection (..)-  , RequestMessageType (..)+  ( RequestMessageType (..)   , ChannelName(..)   , WebSocketFeedRequest (..)--  , wsEndpoint+  , AuthenticatedWebSocketFeedRequest (..)   ) where -import           Data.Aeson              (FromJSON (..), ToJSON (..), object,-                                          withText, (.=))-import           Network.Socket          (HostName)-import           Network.Socket.Internal (PortNumber)--import           CoinbasePro.Types       (ProductId)---data WSConnection = WSConnection-    { host :: HostName-    , port :: PortNumber-    } deriving (Eq, Show)-+import           Data.Aeson                        (FromJSON (..), ToJSON (..),+                                                    object, withText, (.=)) -wsEndpoint :: WSConnection-wsEndpoint = WSConnection h p-  where-    h = "ws-feed.pro.coinbase.com"-    p = 443+import           CoinbasePro.Authenticated.Headers (CBAccessKey (..),+                                                    CBAccessPassphrase (..),+                                                    CBAccessSign (..),+                                                    CBAccessTimeStamp (..))+import           CoinbasePro.Types                 (ProductId)   data RequestMessageType = Subscribe | Unsubscribe@@ -62,8 +50,7 @@   instance FromJSON ChannelName where-    parseJSON = withText "channel name" $ \t ->-      case t of+    parseJSON = withText "channel name" $ \case         "heartbeat" -> return Heartbeat         "status"    -> return Status         "ticker"    -> return Ticker@@ -85,4 +72,24 @@         [ "type" .= show rmt         , "product_ids" .= rpi         , "channels" .= rc+        ]++data AuthenticatedWebSocketFeedRequest = AuthenticatedWebSocketFeedRequest+    { request      :: WebSocketFeedRequest+    , cbSig        :: CBAccessSign+    , cbKey        :: CBAccessKey+    , cbPassphrase :: CBAccessPassphrase+    , cbTimestamp  :: CBAccessTimeStamp+    } deriving (Eq)+++instance ToJSON AuthenticatedWebSocketFeedRequest where+    toJSON (AuthenticatedWebSocketFeedRequest req s k p t) = object+        [ "type" .= show (reqMsgType req)+        , "product_ids" .= reqProductIds req+        , "channels" .= reqChannels req+        , "signature" .= s+        , "key" .= k+        , "passphrase" .= p+        , "timestamp" .= t         ]