Coinbase-Pro Haskell Client Version 0.9.2.0 Release

The new release of the coinbase-pro haskell client is feature complete! Check it out on github: https://github.com/mdunnio/coinbase-pro or hackage: https://hackage.haskell.org/package/coinbase-pro

The main objective of this project was to provide a nice, clean example of what a client side integration library should look like using servant-client. I've been fairly happy with the results. I think there is more opportunity to remove some boiler-plate code (some hand-rolled ToJSON/FromJSON instances), but I utilize the TemplateHaskell language extension as much as possible.

Design

I designed the library with the end user in mind. The objective was to allow the user to start doing REST queries or websocket streaming with the fewest lines of code possible. I split up the codebase into three distinct sections:

  1. Streaming
  2. Unauthenticated API
  3. Authenticated API

I split up the unauthenticated and authenticated APIs, because the auth APIs needed a bit more plumbing to include the submission of the proper credentials and signatures that coinbase-pro requires.

Streaming

Here is an example of running a full order book fed for BTC-USD:

main :: IO ()
main = do
    msgs <- subscribeToFeed [ProductId "BTC-USD"] [Ticker] Nothing
    forever $ Streams.read msgs >>= print

Unauthenticated Requests

Here is an example of doing an unauthenticated request that gets you the latest trades for a product:

run Sandbox (trades (ProductId "BTC-USD")) >>= print

Authenticated Requests

Authenticated requests presented more of a challenge than streaming and unauthenticated request due to the necessity to always have some credentials in-scope. I solved this problem using a ReaderT that allows the state to be passed along in a greater monad stack:

newtype CBAuthT m a = CBAuthT { unCbAuth :: ReaderT CoinbaseProCredentials m a }
    deriving (Functor, Applicative, Monad, MonadIO, MonadTrans)

A runCbAuthT function is also defined that allows the user to specify CoinbaseProCredentials and an Environment. Users can then sequence authenticated requests as they see fit within the CBAuthT and ClientM monads. ClientM is required for servant-client.

runCbAuthT :: Runner a -> CoinbaseProCredentials -> CBAuthT ClientM a -> IO a
runCbAuthT runEnv cpc = runEnv . flip runReaderT cpc . unCbAuth

Here is an example of doing an authenticated request to get a list of fills for a given product:

runCbAuthT (run Sandbox) cpc $ do
    fills (Just btcusd) Nothing >>= liftIO . print
  where
    accessKey  = CBAccessKey "<access-key>"
    secretKey  = CBSecretKey "<secret-key>"
    passphrase = CBAccessPassphrase "<passphrase>"
    cpc        = CoinbaseProCredentials accessKey secretKey passphrase

Environments

Coinbase-Pro has a sandbox environment for users to test API integration. Thus the Environment sum type was created:

data Environment = Production | Sandbox

Now, whenever a user wants to make a request, they can just specify which environment they want to run in via the run function:

------------------------------------------------------------------------------
-- | Runs a coinbase pro HTTPS request and returns the result `a`
--
-- > run Production products >>= print
--
run :: Environment -> ClientM a -> IO a
run env f = do
    mgr <- newManager tlsManagerSettings
    runWithManager mgr env f

Looking Ahead

I want to take some time and see if I can generate swagger docs from my client API. Considering the API types for the client and the server side are the same, I should be able to. If that's the case, my swagger docs might be a better source of documentation than the actual coinbase-pro docs themselves.

I must admit, even though API integration can get tedious, I enjoyed the work I've done here. I hope to continue tinkering and making some of the datatypes more robust and adding more documentation and testing. It's quite clear that servant is a very powerful library and worth the additional complexity required to use it. I hope this library can inspire others to create clean and simple REST/Streaming APIs with servant and servant-client. Thanks for reading!