packages feed

coinbase-pro-0.9.0.0: src/lib/CoinbasePro/Authenticated.hs

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies      #-}
{-# LANGUAGE TypeOperators     #-}

module CoinbasePro.Authenticated
  ( accounts
  , account
  , accountHistory
  , accountHolds
  , listOrders
  , getOrder
  , getClientOrder
  , placeOrder
  , cancelOrder
  , cancelAll
  , fills
  , fees
  , trailingVolume
  , limits
  , deposits
  , withdrawals
  , transfer
  , makeDeposit
  , makeCoinbaseDeposit
  , cryptoDepositAddress
  , makeWithdrawal
  , makeCoinbaseWithdrawal
  , makeCryptoWithdrawal
  , withdrawalFeeEstimate
  , paymentMethods
  , coinbaseAccounts
  ) where

import           Control.Monad                              (void)
import           Data.Aeson                                 (encode)
import qualified Data.ByteString.Builder                    as BB
import qualified Data.ByteString.Lazy.Char8                 as LC8
import           Data.Maybe                                 (fromMaybe)
import qualified Data.Set                                   as S
import           Data.Text                                  (Text, pack)
import           Data.Text.Encoding                         (encodeUtf8)
import           Data.Time.Clock                            (UTCTime)
import           Data.UUID                                  (toText)
import           Network.HTTP.Types                         (SimpleQuery,
                                                             SimpleQueryItem,
                                                             encodePathSegments,
                                                             methodDelete,
                                                             methodGet,
                                                             methodPost,
                                                             renderQuery,
                                                             simpleQueryToQuery)
import           Servant.Client                             (ClientM)

import qualified CoinbasePro.Authenticated.API              as API
import           CoinbasePro.Authenticated.Accounts         (Account,
                                                             AccountHistory,
                                                             AccountId (..),
                                                             Fees, Hold,
                                                             TrailingVolume (..))
import           CoinbasePro.Authenticated.CoinbaseAccounts (CoinbaseAccount)
import           CoinbasePro.Authenticated.Deposit          (CoinbaseDepositRequest (..),
                                                             CryptoDepositAddress,
                                                             DepositRequest (..),
                                                             DepositResponse)
import           CoinbasePro.Authenticated.Fills            (Fill)
import           CoinbasePro.Authenticated.Limits           (Limits)
import           CoinbasePro.Authenticated.Orders           (Order,
                                                             PlaceOrderBody (..),
                                                             STP, Status (..),
                                                             Statuses (..),
                                                             TimeInForce,
                                                             statuses)
import           CoinbasePro.Authenticated.Payment          (PaymentMethod,
                                                             PaymentMethodId (..))
import           CoinbasePro.Authenticated.Request          (CBAuthT (..),
                                                             authRequest)
import           CoinbasePro.Authenticated.Transfer         (Transfer,
                                                             TransferType (..))
import           CoinbasePro.Authenticated.Withdrawal       (CoinbaseWithdrawalRequest (..),
                                                             CryptoWithdrawalRequest (..),
                                                             CryptoWithdrawalResponse,
                                                             WithdrawalFeeEstimateResponse (..),
                                                             WithdrawalRequest (..),
                                                             WithdrawalResponse)
import           CoinbasePro.Request                        (RequestPath,
                                                             emptyBody)


import           CoinbasePro.Types                          (ClientOrderId (..),
                                                             CryptoAddress (..),
                                                             CurrencyType (..),
                                                             OrderId (..),
                                                             OrderType, Price,
                                                             ProductId (..),
                                                             ProfileId, Side,
                                                             Size)

accountsPath :: Text
accountsPath = "accounts"


ordersPath :: Text
ordersPath = "orders"


encodeRequestPath :: [Text] -> RequestPath
encodeRequestPath = LC8.toStrict . BB.toLazyByteString . encodePathSegments


mkSimpleQueryItem :: Show a => Text -> a -> SimpleQueryItem
mkSimpleQueryItem s a = (encodeUtf8 s, encodeUtf8 $ pack (show a))


optionalQuery :: Show a => Text -> Maybe a -> [SimpleQueryItem]
optionalQuery t = maybe [] (return . mkSimpleQueryItem t)


mkProductQuery :: Maybe ProductId -> [SimpleQueryItem]
mkProductQuery = optionalQuery "product_id"


mkOrderIdQuery :: Maybe OrderId -> SimpleQuery
mkOrderIdQuery = optionalQuery "order_id"


-- | https://docs.pro.coinbase.com/#accounts
accounts :: CBAuthT ClientM [Account]
accounts = authRequest methodGet requestPath emptyBody API.accounts
  where
    requestPath = encodeRequestPath [accountsPath]


-- | https://docs.pro.coinbase.com/#get-an-account
account :: AccountId -> CBAuthT ClientM Account
account aid@(AccountId t) = authRequest methodGet requestPath emptyBody $ API.singleAccount aid
  where
    requestPath = encodeRequestPath [accountsPath, t]


-- | https://docs.pro.coinbase.com/#get-account-history
accountHistory :: AccountId -> CBAuthT ClientM [AccountHistory]
accountHistory aid@(AccountId t) = authRequest methodGet requestPath emptyBody $ API.accountHistory aid
  where
    ledgerPath  = "ledger"
    requestPath = encodeRequestPath [accountsPath, t, ledgerPath]


-- | https://docs.pro.coinbase.com/#get-holds
accountHolds :: AccountId -> CBAuthT ClientM [Hold]
accountHolds aid@(AccountId t) = authRequest methodGet requestPath emptyBody $ API.accountHolds aid
  where
    holdsPath   = "holds"
    requestPath = encodeRequestPath [accountsPath, t, holdsPath]


-- | https://docs.pro.coinbase.com/#list-orders
listOrders :: Maybe [Status] -> Maybe ProductId -> CBAuthT ClientM [Order]
listOrders st prid = authRequest methodGet requestPath emptyBody $ API.listOrders (defaultStatus st) prid
  where
    query       = renderQuery True . simpleQueryToQuery $ orderQuery st prid
    requestPath = encodeRequestPath [ordersPath] <> query

    orderQuery :: Maybe [Status] -> Maybe ProductId -> SimpleQuery
    orderQuery ss p = statusQuery ss <> mkProductQuery p

    statusQuery :: Maybe [Status] -> [SimpleQueryItem]
    statusQuery ss = mkSimpleQueryItem "status" <$> S.toList (unStatuses . statuses $ defaultStatus ss)

    defaultStatus :: Maybe [Status] -> [Status]
    defaultStatus = fromMaybe [All]


-- | https://docs.pro.coinbase.com/#get-an-order
getOrder :: OrderId -> CBAuthT ClientM Order
getOrder oid = authRequest methodGet requestPath emptyBody $ API.getOrder oid
  where
    requestPath = encodeRequestPath [ordersPath, unOrderId oid]


-- | https://docs.pro.coinbase.com/#get-an-order
getClientOrder :: ClientOrderId -> CBAuthT ClientM Order
getClientOrder cloid = authRequest methodGet requestPath emptyBody $ API.getClientOrder cloid
  where
    oid         = toText $ unClientOrderId cloid
    requestPath = encodeRequestPath [ordersPath, "client:" <> oid]


-- | https://docs.pro.coinbase.com/#place-a-new-order
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 requestPath seBody $ API.placeOrder body
  where
    requestPath = encodeRequestPath [ordersPath]
    body        = PlaceOrderBody clordid prid sd sz price po ot stp tif
    seBody      = LC8.toStrict $ encode body


-- | https://docs.pro.coinbase.com/#cancel-an-order
cancelOrder :: OrderId -> CBAuthT ClientM ()
cancelOrder oid = void . authRequest methodDelete requestPath emptyBody $ API.cancelOrder oid
  where
    requestPath = encodeRequestPath [ordersPath, unOrderId oid]


-- | https://docs.pro.coinbase.com/#cancel-all
cancelAll :: Maybe ProductId -> CBAuthT ClientM [OrderId]
cancelAll prid = authRequest methodDelete requestPath emptyBody (API.cancelAll prid)
  where
    query       = renderQuery True . simpleQueryToQuery $ mkProductQuery prid
    requestPath = encodeRequestPath [ordersPath] <> query


-- | https://docs.pro.coinbase.com/#fills
fills :: Maybe ProductId -> Maybe OrderId -> CBAuthT ClientM [Fill]
fills prid oid = authRequest methodGet requestPath emptyBody (API.fills prid oid)
  where
    fillsPath   = "fills"
    query       = renderQuery True . simpleQueryToQuery $ mkSimpleQuery prid oid
    requestPath = encodeRequestPath [fillsPath] <> query

    mkSimpleQuery :: Maybe ProductId -> Maybe OrderId -> SimpleQuery
    mkSimpleQuery p o = mkProductQuery p <> mkOrderIdQuery o


-- | https://docs.pro.coinbase.com/#get-current-fees
fees :: CBAuthT ClientM Fees
fees = authRequest methodGet feesRequestPath emptyBody API.fees
  where
    feesPath        = "fees"
    feesRequestPath = encodeRequestPath [feesPath]


-- | https://docs.pro.coinbase.com/#trailing-volume
trailingVolume :: CBAuthT ClientM [TrailingVolume]
trailingVolume = authRequest methodGet requestPath emptyBody API.trailingVolume
  where
    requestPath = encodeRequestPath ["users", "self", "trailing-volume"]


-- | https://docs.pro.coinbase.com/#get-current-exchange-limits
limits :: CBAuthT ClientM Limits
limits = authRequest methodGet requestPath emptyBody API.limits
  where
    requestPath = encodeRequestPath ["users", "self", "exchange-limits"]


-- | https://docs.pro.coinbase.com/#list-deposits
deposits :: Maybe ProfileId
         -> Maybe UTCTime
         -> Maybe UTCTime
         -> Maybe Int
         -> CBAuthT ClientM [Transfer]
deposits = transfers DepositTransferType


-- | https://docs.pro.coinbase.com/#list-withdrawals
withdrawals :: Maybe ProfileId
            -> Maybe UTCTime
            -> Maybe UTCTime
            -> Maybe Int
            -> CBAuthT ClientM [Transfer]
withdrawals = transfers WithdrawTransferType


transfers :: TransferType
          -> Maybe ProfileId
          -> Maybe UTCTime
          -> Maybe UTCTime
          -> Maybe Int
          -> CBAuthT ClientM [Transfer]
transfers tt prof before after lm = authRequest methodGet requestPath emptyBody $
    API.transfers tt prof before after lm
  where
    typeQ   = return $ mkSimpleQueryItem "type" tt
    profQ   = optionalQuery "profile_id" prof
    beforeQ = optionalQuery "before" before
    afterQ  = optionalQuery "after" after
    lmQ     = optionalQuery "limit" lm

    query       = renderQuery True . simpleQueryToQuery $ typeQ <> profQ <> beforeQ <> afterQ <> lmQ
    requestPath = encodeRequestPath ["transfers"] <> query


-- | https://docs.pro.coinbase.com/#single-deposit
-- | https://docs.pro.coinbase.com/#single-withdrawal
transfer :: PaymentMethodId -> CBAuthT ClientM Transfer
transfer pmt@(PaymentMethodId p) = authRequest methodGet requestPath emptyBody $ API.transfer pmt
  where
    requestPath = encodeRequestPath ["transfers", p]


-- | https://docs.pro.coinbase.com/#payment-method
makeDeposit :: Double
            -> Text
            -> PaymentMethodId
            -> CBAuthT ClientM DepositResponse
makeDeposit amt cur pmi =
    authRequest methodPost requestPath seBody $ API.makeDeposit body
  where
    requestPath = encodeRequestPath ["deposits", "payment-method"]
    body        = DepositRequest amt cur pmi
    seBody      = LC8.toStrict $ encode body


-- | https://docs.pro.coinbase.com/#coinbase
makeCoinbaseDeposit :: Double
                    -> Text
                    -> AccountId
                    -> CBAuthT ClientM DepositResponse
makeCoinbaseDeposit amt cur act =
    authRequest methodPost requestPath seBody $ API.makeCoinbaseDeposit body
  where
    requestPath = encodeRequestPath ["deposits", "coinbase-account"]
    body        = CoinbaseDepositRequest amt cur act
    seBody      = LC8.toStrict $ encode body


-- | https://docs.pro.coinbase.com/#generate-a-crypto-deposit-address
cryptoDepositAddress :: AccountId -> CBAuthT ClientM CryptoDepositAddress
cryptoDepositAddress act =
    authRequest methodPost requestPath emptyBody $ API.cryptoDepositAddress act
  where
    requestPath = encodeRequestPath ["coinbase-accounts", pack $ show act, "addresses"]


-- | https://docs.pro.coinbase.com/#payment-method55
makeWithdrawal :: Double
               -> Text
               -> PaymentMethodId
               -> CBAuthT ClientM WithdrawalResponse
makeWithdrawal amt cur pmi =
    authRequest methodPost requestPath seBody $ API.makeWithdrawal body
  where
    requestPath = encodeRequestPath ["withdrawals", "payment-method"]
    body        = WithdrawalRequest amt cur pmi
    seBody      = LC8.toStrict $ encode body


-- | https://docs.pro.coinbase.com/#coinbase56
makeCoinbaseWithdrawal :: Double
                       -> Text
                       -> AccountId
                       -> CBAuthT ClientM WithdrawalResponse
makeCoinbaseWithdrawal amt cur act =
    authRequest methodPost requestPath seBody $ API.makeCoinbaseWithdrawal body
  where
    requestPath = encodeRequestPath ["withdrawals", "coinbase-account"]
    body        = CoinbaseWithdrawalRequest amt cur act
    seBody      = LC8.toStrict $ encode body


-- | https://docs.pro.coinbase.com/#crypto
makeCryptoWithdrawal :: Double
                     -> Text
                     -> Text
                     -> CBAuthT ClientM CryptoWithdrawalResponse
makeCryptoWithdrawal amt cur addr =
    authRequest methodPost requestPath seBody $ API.makeCryptoWithdrawal body
  where
    requestPath = encodeRequestPath ["withdrawals", "crypto"]
    body        = CryptoWithdrawalRequest amt cur addr
    seBody      = LC8.toStrict $ encode body


-- | https://docs.pro.coinbase.com/#fee-estimate
withdrawalFeeEstimate :: CurrencyType
                      -> CryptoAddress
                      -> CBAuthT ClientM WithdrawalFeeEstimateResponse
withdrawalFeeEstimate cur addr =
    authRequest methodGet requestPath emptyBody $ API.withdrawalFeeEstimate cur addr
  where
    curQ  = return $ mkSimpleQueryItem "currency" cur
    addrQ = return $ mkSimpleQueryItem "crypto_address" addr

    query       = renderQuery True . simpleQueryToQuery $ curQ <> addrQ
    requestPath = encodeRequestPath ["withdrawals", "fee-estimate"] <> query


-- | https://docs.pro.coinbase.com/#list-payment-methods
paymentMethods :: CBAuthT ClientM [PaymentMethod]
paymentMethods = authRequest methodGet requestPath emptyBody API.paymentMethods
  where
    requestPath = encodeRequestPath ["payment-methods"]


-- | https://docs.pro.coinbase.com/#list-accounts64
coinbaseAccounts :: CBAuthT ClientM [CoinbaseAccount]
coinbaseAccounts = authRequest methodGet requestPath emptyBody API.coinbaseAccounts
  where
    requestPath = encodeRequestPath ["coinbase-accounts"]