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 +4/−4
- changelog.md +41/−0
- coinbase-pro.cabal +7/−3
- src/example/request/Main.hs +12/−11
- src/example/stream/Main.hs +3/−1
- src/lib/CoinbasePro/Authenticated.hs +31/−4
- src/lib/CoinbasePro/Authenticated/API.hs +9/−2
- src/lib/CoinbasePro/Authenticated/Headers.hs +17/−0
- src/lib/CoinbasePro/Authenticated/Orders.hs +5/−3
- src/lib/CoinbasePro/Authenticated/Request.hs +5/−3
- src/lib/CoinbasePro/Environment.hs +46/−0
- src/lib/CoinbasePro/Headers.hs +1/−1
- src/lib/CoinbasePro/Request.hs +20/−50
- src/lib/CoinbasePro/Types.hs +24/−4
- src/lib/CoinbasePro/Unauthenticated.hs +0/−1
- src/lib/CoinbasePro/WebSocketFeed.hs +34/−10
- src/lib/CoinbasePro/WebSocketFeed/Channel.hs +1/−0
- src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Activate.hs +4/−6
- src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Change.hs +4/−2
- src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Done.hs +4/−2
- src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Match.hs +4/−2
- src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Open.hs +4/−2
- src/lib/CoinbasePro/WebSocketFeed/Channel/Full/Received.hs +23/−7
- src/lib/CoinbasePro/WebSocketFeed/Channel/Status.hs +0/−4
- src/lib/CoinbasePro/WebSocketFeed/Request.hs +31/−24
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 ]