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:
- Streaming
- Unauthenticated API
- 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!