bitx-bitcoin 0.1.0.0 → 0.2.0.0
raw patch · 17 files changed
+978/−444 lines, 17 filesdep +scientificdep −DecimalPVP ok
version bump matches the API change (PVP)
Dependencies added: scientific
Dependencies removed: Decimal
API changes (from Hackage documentation)
- Network.Bitcoin.BitX.Private: getAllOrders :: BitXAuth -> Maybe CcyPair -> Maybe RequestStatus -> IO (BitXAPIResponse [PrivateOrder])
- Network.Bitcoin.BitX.Private: getOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse PrivateOrderWithTrades)
- Network.Bitcoin.BitX.Private: getWithdrawalRequest :: BitXAuth -> Text -> IO (BitXAPIResponse WithdrawalRequest)
- Network.Bitcoin.BitX.Private: getWithdrawalRequests :: BitXAuth -> IO (BitXAPIResponse [WithdrawalRequest])
- Network.Bitcoin.BitX.Private: newWithdrawalRequest :: BitXAuth -> NewWithdrawal -> IO (BitXAPIResponse WithdrawalRequest)
- Network.Bitcoin.BitX.Private: postOrder :: BitXAuth -> OrderRequest -> IO (BitXAPIResponse OrderID)
- Network.Bitcoin.BitX.Private: stopOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse RequestSuccess)
- Network.Bitcoin.BitX.Private.Auth: authGrant :: BitXClientAuth -> Text -> IO (BitXAPIResponse BitXAuth)
+ Network.Bitcoin.BitX.Private: newAccount :: BitXAuth -> Account -> IO (BitXAPIResponse Account)
+ Network.Bitcoin.BitX.Private.Order: getAllOrders :: BitXAuth -> Maybe CcyPair -> Maybe RequestStatus -> IO (BitXAPIResponse [PrivateOrder])
+ Network.Bitcoin.BitX.Private.Order: getOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse PrivateOrderWithTrades)
+ Network.Bitcoin.BitX.Private.Order: postOrder :: BitXAuth -> OrderRequest -> IO (BitXAPIResponse OrderID)
+ Network.Bitcoin.BitX.Private.Order: stopOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse RequestSuccess)
+ Network.Bitcoin.BitX.Private.Withdrawal: getWithdrawalRequest :: BitXAuth -> Text -> IO (BitXAPIResponse WithdrawalRequest)
+ Network.Bitcoin.BitX.Private.Withdrawal: getWithdrawalRequests :: BitXAuth -> IO (BitXAPIResponse [WithdrawalRequest])
+ Network.Bitcoin.BitX.Private.Withdrawal: newWithdrawalRequest :: BitXAuth -> NewWithdrawal -> IO (BitXAPIResponse WithdrawalRequest)
+ Network.Bitcoin.BitX.Types: type Account = Record3 "currency" Asset "id" Text "name" Text
- Network.Bitcoin.BitX.Types: type Balance = Record5 "asset" Asset "balance" Decimal "id" AccountID "reserved" Decimal "unconfirmed" Decimal
+ Network.Bitcoin.BitX.Types: type Balance = Record5 "asset" Asset "balance" Scientific "id" AccountID "reserved" Scientific "unconfirmed" Scientific
- Network.Bitcoin.BitX.Types: type BitcoinSendRequest = Record5 "address" Text "amount" Decimal "currency" Asset "description" (Maybe Text) "message" (Maybe Text)
+ Network.Bitcoin.BitX.Types: type BitcoinSendRequest = Record5 "address" Text "amount" Scientific "currency" Asset "description" (Maybe Text) "message" (Maybe Text)
- Network.Bitcoin.BitX.Types: type FundingAddress = Record4 "address" Text "asset" Asset "totalReceived" Decimal "totalUnconfirmed" Decimal
+ Network.Bitcoin.BitX.Types: type FundingAddress = Record4 "address" Text "asset" Asset "totalReceived" Scientific "totalUnconfirmed" Scientific
- Network.Bitcoin.BitX.Types: type NewWithdrawal = Record2 "amount" Decimal "type" WithdrawalType
+ Network.Bitcoin.BitX.Types: type NewWithdrawal = Record2 "amount" Scientific "type" WithdrawalType
- Network.Bitcoin.BitX.Types: type Order = Record2 "price" Decimal "volume" Decimal
+ Network.Bitcoin.BitX.Types: type Order = Record2 "price" Scientific "volume" Scientific
- Network.Bitcoin.BitX.Types: type OrderQuote = Record9 "baseAmount" Decimal "counterAmount" Decimal "createdAt" UTCTime "discarded" Bool "exercised" Bool "expiresAt" UTCTime "id" Text "pair" CcyPair "type" QuoteType
+ Network.Bitcoin.BitX.Types: type OrderQuote = Record9 "baseAmount" Scientific "counterAmount" Scientific "createdAt" UTCTime "discarded" Bool "exercised" Bool "expiresAt" UTCTime "id" Text "pair" CcyPair "type" QuoteType
- Network.Bitcoin.BitX.Types: type OrderRequest = Record4 "pair" CcyPair "price" Decimal "type" OrderType "volume" Decimal
+ Network.Bitcoin.BitX.Types: type OrderRequest = Record4 "pair" CcyPair "price" Scientific "type" OrderType "volume" Scientific
- Network.Bitcoin.BitX.Types: type PrivateOrder = Record12 "base" Decimal "counter" Decimal "creationTimestamp" UTCTime "expirationTimestamp" UTCTime "feeBase" Decimal "feeCounter" Decimal "id" OrderID "limitPrice" Decimal "limitVolume" Decimal "pair" CcyPair "state" RequestStatus "type" OrderType
+ Network.Bitcoin.BitX.Types: type PrivateOrder = Record12 "base" Scientific "counter" Scientific "creationTimestamp" UTCTime "expirationTimestamp" UTCTime "feeBase" Scientific "feeCounter" Scientific "id" OrderID "limitPrice" Scientific "limitVolume" Scientific "pair" CcyPair "state" RequestStatus "type" OrderType
- Network.Bitcoin.BitX.Types: type PrivateOrderWithTrades = Record13 "base" Decimal "counter" Decimal "creationTimestamp" UTCTime "expirationTimestamp" UTCTime "feeBase" Decimal "feeCounter" Decimal "id" OrderID "limitPrice" Decimal "limitVolume" Decimal "pair" CcyPair "state" RequestStatus "trades" [Trade] "type" OrderType
+ Network.Bitcoin.BitX.Types: type PrivateOrderWithTrades = Record13 "base" Scientific "counter" Scientific "creationTimestamp" UTCTime "expirationTimestamp" UTCTime "feeBase" Scientific "feeCounter" Scientific "id" OrderID "limitPrice" Scientific "limitVolume" Scientific "pair" CcyPair "state" RequestStatus "trades" [Trade] "type" OrderType
- Network.Bitcoin.BitX.Types: type QuoteRequest = Record3 "baseAmount" Decimal "pair" CcyPair "type" QuoteType
+ Network.Bitcoin.BitX.Types: type QuoteRequest = Record3 "baseAmount" Scientific "pair" CcyPair "type" QuoteType
- Network.Bitcoin.BitX.Types: type Ticker = Record6 "ask" Decimal "bid" Decimal "lastTrade" Decimal "pair" CcyPair "rolling24HourVolume" Decimal "timestamp" UTCTime
+ Network.Bitcoin.BitX.Types: type Ticker = Record6 "ask" Scientific "bid" Scientific "lastTrade" Scientific "pair" CcyPair "rolling24HourVolume" Scientific "timestamp" UTCTime
- Network.Bitcoin.BitX.Types: type Trade = Record3 "price" Decimal "timestamp" UTCTime "volume" Decimal
+ Network.Bitcoin.BitX.Types: type Trade = Record3 "price" Scientific "timestamp" UTCTime "volume" Scientific
- Network.Bitcoin.BitX.Types: type Transaction = Record8 "available" Decimal "availableDelta" Decimal "balance" Decimal "balanceDelta" Decimal "currency" Asset "description" Text "rowIndex" Int "timestamp" UTCTime
+ Network.Bitcoin.BitX.Types: type Transaction = Record8 "available" Scientific "availableDelta" Scientific "balance" Scientific "balanceDelta" Scientific "currency" Asset "description" Text "rowIndex" Int "timestamp" UTCTime
Files
- CHANGES +9/−0
- README.md +31/−1
- bitx-bitcoin.cabal +18/−10
- src/Network/Bitcoin/BitX.hs +6/−2
- src/Network/Bitcoin/BitX/Internal.hs +16/−21
- src/Network/Bitcoin/BitX/Private.hs +19/−90
- src/Network/Bitcoin/BitX/Private/Auth.hs +0/−73
- src/Network/Bitcoin/BitX/Private/Order.hs +82/−0
- src/Network/Bitcoin/BitX/Private/Withdrawal.hs +64/−0
- src/Network/Bitcoin/BitX/Public.hs +16/−6
- src/Network/Bitcoin/BitX/Types.hs +89/−74
- src/Network/Bitcoin/BitX/Types/Internal.hs +182/−167
- test/Network/Bitcoin/BitX/Spec/Common.hs +12/−0
- test/Network/Bitcoin/BitX/Spec/Specs/AesonRecordSpec.hs +225/−0
- test/Network/Bitcoin/BitX/Spec/Specs/JsonSpec.hs +116/−0
- test/Network/Bitcoin/BitX/Spec/Specs/NetSpec.hs +31/−0
- test/Network/Bitcoin/BitX/Spec/Specs/PostSpec.hs +62/−0
+ CHANGES view
@@ -0,0 +1,9 @@+v0.2.0.0+* Replaced Decimal with Scientific. BREAKING CHANGE.+* Divided much of the Private module into smaller sub-modules. BREAKING CHANGE.+* Added the new endpoint for creating accounts.+* Remove ill-thought-out Auth module.+* Lots of internal improvements.++v0.1.0.0+* Initial release!
README.md view
@@ -1,5 +1,35 @@ [](https://travis-ci.org/tebello-thejane/bitx-haskell) +[](https://hackage.haskell.org/package/bitx-bitcoin) +[](http://creativecommons.org/publicdomain/zero/1.0/) + (Hopefully useful) Haskell bindings to the BitX bitcoin exchange's API. -**This library is not yet complete.** +As a minimal example, to get the current selling price (in South African Rand) of bitcoin on the BitX exchange, do the following: + +```haskell +{-# LANGUAGE QuasiQuotes #-} + +import Record.Lens (view) +import Record (lens) +import Network.Bitcoin.BitX (BitXAPIResponse(..), getTicker, CcyPair(..)) +import Data.Text (unpack) +import Network.HTTP.Types.Status (Status(..)) +import Network.HTTP.Conduit (responseStatus) + +main :: IO () +main = do + bitXResponse <- getTicker XBTZAR + case bitXResponse of + ValidResponse tic -> print (view [lens| ask |] tic) + ErrorResponse err -> + error $ "BitX error received: \"" ++ (unpack (view [lens| error |] err)) ++ "\"" + ExceptionResponse ex -> + error $ "Exception was thrown: \"" ++ (unpack ex) ++ "\"" + UnparseableResponse resp -> + error $ "Bad HTTP response; HTTP status code was: \"" ++ (show . statusCode . responseStatus $ resp) ++ "\"" +``` + +Note that the code snippet above depends on [http-types](https://hackage.haskell.org/package/http-types), [http-conduit](https://hackage.haskell.org/package/http-conduit), [record](https://hackage.haskell.org/package/record), and finally **bitx-bitcoin**. + +Note that this library **will not** build on Windows currently, due to networking dependencies which have no Windows support.
bitx-bitcoin.cabal view
@@ -1,15 +1,12 @@--- Initial bitx-bitcoin.cabal generated by cabal init. For further--- documentation, see http://haskell.org/cabal/users-guide/- name: bitx-bitcoin-version: 0.1.0.0+version: 0.2.0.0 synopsis: A Haskell library for working with the BitX bitcoin exchange. description: Haskell bindings to the BitX REST API, as described here: <https://bitx.co/api>. . Note that since this library interfaces directly with a financial API, great care- must be taken in its use. In particular, the author cannot be held account for any+ must be taken in its use. In particular, the author cannot be held accountable for any financial losses as a result of programming error, whether that error is in your code, the code of the author of this library, or BitX's code. This is just common sense. .@@ -27,15 +24,18 @@ maintainer: Tebello Thejane <zyxoas+hackage@gmail.com> -category: Web+category: Web, Bitcoin, Finance build-type: Custom extra-source-files: README.md+ CHANGES cabal-version: >=1.10 +homepage: https://github.com/tebello-thejane/bitx-haskell+ -------------------------------------------------------------------------------- library@@ -45,7 +45,9 @@ Network.Bitcoin.BitX.Private Network.Bitcoin.BitX.Types Network.Bitcoin.BitX.Private.Quote- Network.Bitcoin.BitX.Private.Auth+ -- Network.Bitcoin.BitX.Private.Auth+ Network.Bitcoin.BitX.Private.Order+ Network.Bitcoin.BitX.Private.Withdrawal Network.Bitcoin.BitX.Response ghc-options: -Wall@@ -59,7 +61,6 @@ -- LANGUAGE extensions used by modules in this package. -- other-extensions: - -- Other library packages from which modules are imported. build-depends: base >=4.5 && <5, aeson >= 0.8.0.0, record >= 0.3.1.1,@@ -67,9 +68,9 @@ time, http-conduit >= 2.0.0, bytestring >= 0.10.0.0,- Decimal >= 0.3.1, network >= 2.0,- split >= 0.2.0.0+ split >= 0.2.0.0,+ scientific default-language: Haskell2010 hs-source-dirs: src@@ -101,3 +102,10 @@ record, bytestring, time+ other-modules:+ Network.Bitcoin.BitX.Spec.Common+ Network.Bitcoin.BitX.Spec.Specs.AesonRecordSpec+ Network.Bitcoin.BitX.Spec.Specs.NetSpec+ Network.Bitcoin.BitX.Spec.Specs.PostSpec+ Network.Bitcoin.BitX.Spec.Specs.JsonSpec+
src/Network/Bitcoin/BitX.hs view
@@ -20,7 +20,9 @@ module Network.Bitcoin.BitX.Private, module Network.Bitcoin.BitX.Public, module Network.Bitcoin.BitX.Private.Quote,- module Network.Bitcoin.BitX.Private.Auth,+ --module Network.Bitcoin.BitX.Private.Auth,+ module Network.Bitcoin.BitX.Private.Order,+ module Network.Bitcoin.BitX.Private.Withdrawal, module Network.Bitcoin.BitX.Response, BitXAesRecordConvert(..), POSTEncodeable(..)@@ -31,6 +33,8 @@ import Network.Bitcoin.BitX.Private import Network.Bitcoin.BitX.Public import Network.Bitcoin.BitX.Private.Quote-import Network.Bitcoin.BitX.Private.Auth+--import Network.Bitcoin.BitX.Private.Auth+import Network.Bitcoin.BitX.Private.Order+import Network.Bitcoin.BitX.Private.Withdrawal import Network.Bitcoin.BitX.Types.Internal import Network.Bitcoin.BitX.Response
src/Network/Bitcoin/BitX/Internal.hs view
@@ -26,6 +26,7 @@ import qualified Data.Text.Encoding as Txt import qualified Data.Text as Txt import Network.Bitcoin.BitX.Response+import Control.Applicative ((<$>), (<|>)) bitXAPIPrefix :: String bitXAPIPrefix = "https://api.mybitx.com/api/"@@ -40,7 +41,7 @@ userSecret . fromJust . NetCon.parseUrl $ (bitXAPIRoot ++ verb) :: IO (Either SomeException (Response BL.ByteString))- consumeResponseBody_ response+ return $ consumeResponseBody_ response where userID = Txt.encodeUtf8 $ (view [lens| id |] auth) userSecret = Txt.encodeUtf8 $ (view [lens| secret |] auth)@@ -54,7 +55,7 @@ . NetCon.urlEncodedBody (postEncode encrec) . fromJust . NetCon.parseUrl $ (bitXAPIRoot ++ verb) :: IO (Either SomeException (Response BL.ByteString))- consumeResponseBody_ response+ return $ consumeResponseBody_ response where userID = Txt.encodeUtf8 $ (view [lens| id |] auth) userSecret = Txt.encodeUtf8 $ (view [lens| secret |] auth)@@ -67,7 +68,7 @@ userID userSecret $ initReq :: IO (Either SomeException (Response BL.ByteString))- consumeResponseBody_ response+ return $ consumeResponseBody_ response where userID = Txt.encodeUtf8 $ (view [lens| id |] auth) userSecret = Txt.encodeUtf8 $ (view [lens| secret |] auth)@@ -77,32 +78,26 @@ resp <- try . NetCon.withManager . NetCon.httpLbs . fromJust . NetCon.parseUrl $ (bitXAPIRoot ++ verb) :: IO (Either SomeException (Response BL.ByteString))- consumeResponse resp+ return $ consumeResponse resp consumeResponse :: BitXAesRecordConvert rec aes => Either SomeException (NetCon.Response BL.ByteString)- -> IO (BitXAPIResponse rec)+ -> BitXAPIResponse rec consumeResponse resp = case resp of- Left ex -> return $ ExceptionResponse . Txt.pack . show $ ex+ Left ex -> ExceptionResponse . Txt.pack . show $ ex Right k -> bitXErrorOrPayload k consumeResponseBody_ :: BitXAesRecordConvert rec aes => Either SomeException (NetCon.Response BL.ByteString)- -> IO (BitXAPIResponse rec)+ -> BitXAPIResponse rec consumeResponseBody_ resp = case resp of- Left ex -> return $ ExceptionResponse . Txt.pack . show $ ex+ Left ex -> ExceptionResponse . Txt.pack . show $ ex Right k -> bitXErrorOrPayload k -bitXErrorOrPayload :: BitXAesRecordConvert rec aes => Response BL.ByteString -> IO (BitXAPIResponse rec)-bitXErrorOrPayload resp = do- let respTE = Aeson.decode body -- is it a BitX error?- case respTE of- Just e -> return . ErrorResponse . aesToRec $ e- Nothing -> do- let respTT = Aeson.decode body- case respTT of- Just t -> return . ValidResponse . aesToRec $ t- Nothing -> return . UnparseableResponse $ resp- where- body = NetCon.responseBody resp-+bitXErrorOrPayload :: BitXAesRecordConvert rec aes => Response BL.ByteString -> BitXAPIResponse rec+bitXErrorOrPayload resp = fromJust $+ ErrorResponse . aesToRec <$> Aeson.decode body -- is it a BitX error?+ <|> ValidResponse . aesToRec <$> Aeson.decode body+ <|> Just (UnparseableResponse resp)+ where+ body = NetCon.responseBody resp
src/Network/Bitcoin/BitX/Private.hs view
@@ -45,78 +45,44 @@ module Network.Bitcoin.BitX.Private (- getAllOrders,- postOrder,- stopOrder,- getOrder,+ newAccount, getBalances, getFundingAddress, newFundingAddress,- getWithdrawalRequests,- newWithdrawalRequest,- getWithdrawalRequest, sendToAddress,- --cancelWithdrawalRequest, getTransactions,- getPendingTransactions+ getPendingTransactions,++ module Network.Bitcoin.BitX.Private.Order,+ module Network.Bitcoin.BitX.Private.Quote,+ --module Network.Bitcoin.BitX.Private.Auth+ module Network.Bitcoin.BitX.Private.Withdrawal ) where import Network.Bitcoin.BitX.Internal import Network.Bitcoin.BitX.Types import Network.Bitcoin.BitX.Types.Internal import qualified Data.Text as Txt-import Data.Text (Text) import Control.Monad (liftM) import Network.Bitcoin.BitX.Response -{- | Returns a list of the most recently placed orders.--If the second parameter is @Nothing@ then this will return orders for all markets, whereas if it is-@Just cpy@ for some @CcyPair cpy@ then the results will be specific to that market.--If the third parameter is @Nothing@ then this will return orders in all states, whereas if it is-@Just COMPLETE@ or @Just PENDING@ then it will return only completed or pending orders, respectively.--This list is truncated after 100 items.--@Perm_R_Orders@ permission is required.- -}--getAllOrders :: BitXAuth -> Maybe CcyPair -> Maybe RequestStatus -> IO (BitXAPIResponse [PrivateOrder])-getAllOrders auth pair status = simpleBitXGetAuth_ auth url- where- url = "listorders" ++ case (pair, status) of- (Nothing, Nothing) -> ""- (Just pr, Nothing) -> "?pair=" ++ show pr- (Nothing, Just st) -> "?state=" ++ show st- (Just pr, Just st) -> "?pair=" ++ show pr ++ "&state=" ++ show st--{- | Create a new order.--__Warning! Orders cannot be reversed once they have executed. Please ensure your program has been__-__thoroughly tested before submitting orders.__--@Perm_W_Orders@ permission is required.- -}--postOrder :: BitXAuth -> OrderRequest -> IO (BitXAPIResponse OrderID)-postOrder auth oreq = simpleBitXPOSTAuth_ auth oreq "postorder"--{- | Request to stop an order.+import Network.Bitcoin.BitX.Private.Order+--import Network.Bitcoin.BitX.Private.Auth+import Network.Bitcoin.BitX.Private.Quote+import Network.Bitcoin.BitX.Private.Withdrawal -@Perm_W_Orders@ permission is required.- -}+{- | Create an additional account for the specified currency -stopOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse RequestSuccess)-stopOrder auth oid = simpleBitXPOSTAuth_ auth oid "stoporder"+Note that the 'id' field of the second parameter can be left blank. The call will return an `Account`+object resembling the parameter, but with the 'id' field filled in with the newly created account's+id. -{- | Get an order by its ID+You must be verified to trade the currency in question in order to be able to create an account. -@Perm_R_Orders@ permission is required.- -}+@Perm_W_Addresses@ permission required. -} -getOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse PrivateOrderWithTrades)-getOrder auth oid = simpleBitXGetAuth_ auth $ "orders/" ++ Txt.unpack oid+newAccount :: BitXAuth -> Account -> IO (BitXAPIResponse Account)+newAccount auth acc = simpleBitXPOSTAuth_ auth acc "accounts" {- | Return account balances @@ -151,43 +117,6 @@ newFundingAddress :: BitXAuth -> Asset -> IO (BitXAPIResponse FundingAddress) newFundingAddress auth asset = simpleBitXPOSTAuth_ auth asset "funding_address"--{- | List withdrawal requests--Returns a list of withdrawal requests.--@Perm_R_Withdrawals@ permission required.-}--getWithdrawalRequests :: BitXAuth -> IO (BitXAPIResponse [WithdrawalRequest])-getWithdrawalRequests auth = simpleBitXGetAuth_ auth "withdrawals/"--{- | Request a withdrawal--Creates a new withdrawal request.--@Perm_W_Withdrawals@ permission required.-}--newWithdrawalRequest :: BitXAuth -> NewWithdrawal -> IO (BitXAPIResponse WithdrawalRequest)-newWithdrawalRequest auth nwithd = simpleBitXPOSTAuth_ auth nwithd "withdrawals"--{- | Get the status of a withdrawal request--Returns the status of a particular withdrawal request.--@Perm_R_Withdrawals@ permission required.-}--getWithdrawalRequest :: BitXAuth -> Text -- ^ The withdrawal ID- -> IO (BitXAPIResponse WithdrawalRequest)-getWithdrawalRequest auth wthid = simpleBitXGetAuth_ auth $ "withdrawals/" ++ Txt.unpack wthid--{- | Cancel a withdrawal request--This can only be done if the request is still in state PENDING.--@Perm_W_Withdrawals@ permission required.-}----cancelWithdrawalRequest :: BitXAuth -> String -> IO (Maybe (Either BitXError WithdrawalRequest))---cancelWithdrawalRequest auth wthid = simpleBitXMETHAuth_ auth "DELETE" $ "withdrawals/" ++ wthid {- | Send Bitcoin from your account to a Bitcoin address or email address.
− src/Network/Bitcoin/BitX/Private/Auth.hs
@@ -1,73 +0,0 @@-{-# LANGUAGE OverloadedStrings, QuasiQuotes, DataKinds #-}---------------------------------------------------------------------------------- |--- Module : Network.Bitcoin.BitX.Private.Auth--- Copyright : No Rights Reserved--- License : Public Domain------ Maintainer : Tebello Thejane <zyxoas+hackage@gmail.com>--- Stability : Experimental--- Portability : non-portable (GHC Extensions)------ The single function in this module, allows your application to use the--- private BitX API via OAuth2.------ To use OAuth2, you first need to contact the BitX team <support@bitx.co>,--- and provide them with--- * Application name--- * Application description--- * Website URL--- * Permissions required--- * Redirect URL--- * Your BitX account username------ Next, the user needs to be sent to <https://bitx.co/oauth2/authorize?client_id=your_client_id&scope=your_requested_permissions&state=your_unique_state>,--- where they can confirm the request. If they do confirm the request, they--- will be sent to your redirect URL, with an authorisation code as one of--- the GET parameter: <https://example.com/callback?code=authorization_code&state=your_unique_state>------ The permissions are explained in the "Network.Bitcoin.BitX.Private" module.-----------------------------------------------------------------------------------module Network.Bitcoin.BitX.Private.Auth- (- authGrant- ) where--import Network.Bitcoin.BitX.Types-import Network.Bitcoin.BitX.Internal-import qualified Network.HTTP.Conduit as NetCon-import Network.HTTP.Conduit (Response(..))-import Control.Exception (try, SomeException)-import qualified Data.ByteString.Lazy as BL-import Data.Maybe (fromJust)-import Network (withSocketsDo)-import Record (lens)-import Record.Lens (view)-import qualified Data.Text.Encoding as Txt-import Data.Text (Text)-import Network.Bitcoin.BitX.Response--{- | Grant--Once your application has received an authorization code, it can exchange it for an API key. This is-done by calling the grant endpoint. The resulting API key can be used to access the BitX API calls-for which it has the appropriate permissions.--}--authGrant :: BitXClientAuth -> Text -> IO (BitXAPIResponse BitXAuth)-authGrant cauth authCode = withSocketsDo $ do- response <- try . NetCon.withManager . NetCon.httpLbs . NetCon.applyBasicAuth- userID- userSecret- . NetCon.urlEncodedBody- [("grant_type", "authorization_code"),- ("code", Txt.encodeUtf8 authCode)]- . fromJust . NetCon.parseUrl $ bitXAPIPrefix ++ "oauth2/grant"- :: IO (Either SomeException (Response BL.ByteString))- consumeResponseBody_ response- where- userID = Txt.encodeUtf8 $ view [lens| id |] cauth- userSecret = Txt.encodeUtf8 $ view [lens| secret |] cauth
+ src/Network/Bitcoin/BitX/Private/Order.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Network.Bitcoin.BitX.Private.Order+-- Copyright : No Rights Reserved+-- License : Public Domain+--+-- Maintainer : Tebello Thejane <zyxoas+hackage@gmail.com>+-- Stability : Experimental+-- Portability : non-portable (GHC Extensions)+--+-- Creating and working with orders+--+-- Trading on the market is done by submitting trade orders. After a new order has been created,+-- it is submitted for processing by the order matching engine. The order then either matches+-- against an existing order in the order book and is filled or it rests in the order book until it+-- is stopped.+--+-----------------------------------------------------------------------------++module Network.Bitcoin.BitX.Private.Order+ (+ getAllOrders,+ postOrder,+ stopOrder,+ getOrder+ ) where++import Network.Bitcoin.BitX.Internal+import Network.Bitcoin.BitX.Types+import qualified Data.Text as Txt+import Network.Bitcoin.BitX.Response++{- | Returns a list of the most recently placed orders.++If the second parameter is @Nothing@ then this will return orders for all markets, whereas if it is+@Just cpy@ for some @CcyPair cpy@ then the results will be specific to that market.++If the third parameter is @Nothing@ then this will return orders in all states, whereas if it is+@Just COMPLETE@ or @Just PENDING@ then it will return only completed or pending orders, respectively.++This list is truncated after 100 items.++@Perm_R_Orders@ permission is required.+ -}++getAllOrders :: BitXAuth -> Maybe CcyPair -> Maybe RequestStatus -> IO (BitXAPIResponse [PrivateOrder])+getAllOrders auth pair status = simpleBitXGetAuth_ auth url+ where+ url = "listorders" ++ case (pair, status) of+ (Nothing, Nothing) -> ""+ (Just pr, Nothing) -> "?pair=" ++ show pr+ (Nothing, Just st) -> "?state=" ++ show st+ (Just pr, Just st) -> "?pair=" ++ show pr ++ "&state=" ++ show st++{- | Create a new order.++__Warning! Orders cannot be reversed once they have executed. Please ensure your program has been__+__thoroughly tested before submitting orders.__++@Perm_W_Orders@ permission is required.+ -}++postOrder :: BitXAuth -> OrderRequest -> IO (BitXAPIResponse OrderID)+postOrder auth oreq = simpleBitXPOSTAuth_ auth oreq "postorder"++{- | Request to stop an order.++@Perm_W_Orders@ permission is required.+ -}++stopOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse RequestSuccess)+stopOrder auth oid = simpleBitXPOSTAuth_ auth oid "stoporder"++{- | Get an order by its ID++@Perm_R_Orders@ permission is required.+ -}++getOrder :: BitXAuth -> OrderID -> IO (BitXAPIResponse PrivateOrderWithTrades)+getOrder auth oid = simpleBitXGetAuth_ auth $ "orders/" ++ Txt.unpack oid
+ src/Network/Bitcoin/BitX/Private/Withdrawal.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Network.Bitcoin.BitX.Private.Withdrawal+-- Copyright : No Rights Reserved+-- License : Public Domain+--+-- Maintainer : Tebello Thejane <zyxoas+hackage@gmail.com>+-- Stability : Experimental+-- Portability : non-portable (GHC Extensions)+--+-----------------------------------------------------------------------------++module Network.Bitcoin.BitX.Private.Withdrawal+ (+ getWithdrawalRequests,+ newWithdrawalRequest,+ getWithdrawalRequest+ --cancelWithdrawalRequest+ ) where++import Network.Bitcoin.BitX.Internal+import Network.Bitcoin.BitX.Types+import qualified Data.Text as Txt+import Data.Text (Text)+import Network.Bitcoin.BitX.Response++{- | List withdrawal requests++Returns a list of withdrawal requests.++@Perm_R_Withdrawals@ permission required.-}++getWithdrawalRequests :: BitXAuth -> IO (BitXAPIResponse [WithdrawalRequest])+getWithdrawalRequests auth = simpleBitXGetAuth_ auth "withdrawals"++{- | Request a withdrawal++Creates a new withdrawal request.++@Perm_W_Withdrawals@ permission required.-}++newWithdrawalRequest :: BitXAuth -> NewWithdrawal -> IO (BitXAPIResponse WithdrawalRequest)+newWithdrawalRequest auth nwithd = simpleBitXPOSTAuth_ auth nwithd "withdrawals"++{- | Get the status of a withdrawal request++Returns the status of a particular withdrawal request.++@Perm_R_Withdrawals@ permission required.-}++getWithdrawalRequest :: BitXAuth -> Text -- ^ The withdrawal ID+ -> IO (BitXAPIResponse WithdrawalRequest)+getWithdrawalRequest auth wthid = simpleBitXGetAuth_ auth $ "withdrawals/" ++ Txt.unpack wthid++--{- | Cancel a withdrawal request++--This can only be done if the request is still in state PENDING.++-- @Perm_W_Withdrawals@ permission required.-}++--cancelWithdrawalRequest :: BitXAuth -> String -> IO (Maybe (Either BitXError WithdrawalRequest))+--cancelWithdrawalRequest auth wthid = simpleBitXMETHAuth_ auth "DELETE" $ "withdrawals/" ++ wthid
src/Network/Bitcoin/BitX/Public.hs view
@@ -15,15 +15,25 @@ -- @ --{-\# LANGUAGE QuasiQuotes \#-} -----import Record.Lens---import Record---import Network.Bitcoin.BitX+--import Record.Lens (view)+--import Record (lens)+--import Network.Bitcoin.BitX (BitXAPIResponse(..), getTicker, CcyPair(..))+--import Data.Text (unpack)+--import Network.HTTP.Types.Status (Status(..))+--import Network.HTTP.Conduit (responseStatus) --+--main :: IO () --main = do--- bitXResponse <- 'getTicker' 'XBTZAR'+-- bitXResponse <- getTicker XBTZAR -- case bitXResponse of--- 'ValidResponse' tic -> print ('view' [lens| ask |] tic)--- _ -> error "Ah well..."+-- ValidResponse tic -> print (view [lens| ask |] tic)+-- ErrorResponse err ->+-- error $ "BitX error received: \"" ++ (unpack (view [lens| error |] err)) ++ "\""+-- ExceptionResponse ex ->+-- error $ "Exception was thrown: \"" ++ (unpack ex) ++ "\""+-- UnparseableResponse resp ->+-- error $ "Bad HTTP response; HTTP status code was: \""+-- ++ (show . statusCode . responseStatus $ resp) ++ "\"" -- @ -- -----------------------------------------------------------------------------
src/Network/Bitcoin/BitX/Types.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE DeriveGeneric, DefaultSignatures, QuasiQuotes, OverloadedStrings, DataKinds, MultiParamTypeClasses #-} - ----------------------------------------------------------------------------- -- | -- Module : Network.Bitcoin.BitX.Types@@ -19,7 +18,7 @@ -- to avoid using record field prefixes and other awkward hacks to get around -- the fact that Haskell does not yet have a real records' system. ----- For example, the declaration of `BitXError` is+-- For example, the declaration of `BitXAuth` is -- -- @ -- type BitXAuth =@@ -81,7 +80,8 @@ OrderQuote, QuoteType(..), BitXClientAuth,- Transaction+ Transaction,+ Account ) where import Data.Aeson (FromJSON(..))@@ -89,7 +89,7 @@ import Data.Time.Clock import Record import GHC.Generics (Generic)-import Data.Decimal+import Data.Scientific (Scientific) -- | A possible error which the BitX API might return, -- instead of returning the requested data. Note that as yet there is no@@ -115,21 +115,21 @@ -- @ --type Ticker = -- [record|--- {ask :: 'Decimal',+-- {ask :: 'Scientific', -- timestamp :: 'UTCTime',--- bid :: 'Decimal',--- rolling24HourVolume :: 'Decimal',--- lastTrade :: 'Decimal',+-- bid :: 'Scientific',+-- rolling24HourVolume :: 'Scientific',+-- lastTrade :: 'Scientific', -- pair :: 'CcyPair'} |] -- @ type Ticker = [record|- {ask :: Decimal,+ {ask :: Scientific, timestamp :: UTCTime,- bid :: Decimal,- rolling24HourVolume :: Decimal,- lastTrade :: Decimal,+ bid :: Scientific,+ rolling24HourVolume :: Scientific,+ lastTrade :: Scientific, pair :: CcyPair} |] -- | A currency pair@@ -175,14 +175,14 @@ -- @ --type Order = -- [record|--- {volume :: 'Decimal',--- price :: 'Decimal'} |]+-- {volume :: 'Scientific',+-- price :: 'Scientific'} |] -- @ type Order = [record|- {volume :: Decimal,- price :: Decimal} |]+ {volume :: Scientific,+ price :: Scientific} |] -- | Convenient type alias for a bid order type Bid = Order@@ -192,9 +192,9 @@ type Trade = [record|- {volume :: Decimal,+ {volume :: Scientific, timestamp :: UTCTime,- price :: Decimal} |]+ price :: Scientific} |] -- | An auth type used by all private API calls, after authorisation. --@@ -217,14 +217,14 @@ -- @ --type PrivateOrder = -- [record|--- {base :: 'Decimal',--- counter :: 'Decimal',+-- {base :: 'Scientific',+-- counter :: 'Scientific', -- creationTimestamp :: 'UTCTime', -- expirationTimestamp :: 'UTCTime',--- feeBase :: 'Decimal',--- feeCounter :: 'Decimal',--- limitPrice :: 'Decimal',--- limitVolume :: 'Decimal',+-- feeBase :: 'Scientific',+-- feeCounter :: 'Scientific',+-- limitPrice :: 'Scientific',+-- limitVolume :: 'Scientific', -- id :: 'OrderID', -- pair :: 'CcyPair', -- state :: 'RequestStatus',@@ -232,14 +232,14 @@ -- @ type PrivateOrder = [record|- {base :: Decimal,- counter :: Decimal,+ {base :: Scientific,+ counter :: Scientific, creationTimestamp :: UTCTime, expirationTimestamp :: UTCTime,- feeBase :: Decimal,- feeCounter :: Decimal,- limitPrice :: Decimal,- limitVolume :: Decimal,+ feeBase :: Scientific,+ feeCounter :: Scientific,+ limitPrice :: Scientific,+ limitVolume :: Scientific, id :: OrderID, pair :: CcyPair, state :: RequestStatus,@@ -251,14 +251,14 @@ -- @ --type PrivateOrderWithTrades = -- [record|--- {base :: 'Decimal',--- counter :: 'Decimal',+-- {base :: 'Scientific',+-- counter :: 'Scientific', -- creationTimestamp :: 'UTCTime', -- expirationTimestamp :: 'UTCTime',--- feeBase :: 'Decimal',--- feeCounter :: 'Decimal',--- limitPrice :: 'Decimal',--- limitVolume :: 'Decimal',+-- feeBase :: 'Scientific',+-- feeCounter :: 'Scientific',+-- limitPrice :: 'Scientific',+-- limitVolume :: 'Scientific', -- id :: 'OrderID', -- pair :: 'CcyPair', -- state :: 'RequestStatus',@@ -267,14 +267,14 @@ -- @ type PrivateOrderWithTrades = [record|- {base :: Decimal,- counter :: Decimal,+ {base :: Scientific,+ counter :: Scientific, creationTimestamp :: UTCTime, expirationTimestamp :: UTCTime,- feeBase :: Decimal,- feeCounter :: Decimal,- limitPrice :: Decimal,- limitVolume :: Decimal,+ feeBase :: Scientific,+ feeCounter :: Scientific,+ limitPrice :: Scientific,+ limitVolume :: Scientific, id :: OrderID, pair :: CcyPair, state :: RequestStatus,@@ -288,10 +288,10 @@ -- [record| -- {rowIndex :: 'Int', -- timestamp :: 'UTCTime',--- balance :: 'Decimal',--- available :: 'Decimal',--- balanceDelta :: 'Decimal',--- availableDelta :: 'Decimal',+-- balance :: 'Scientific',+-- available :: 'Scientific',+-- balanceDelta :: 'Scientific',+-- availableDelta :: 'Scientific', -- currency :: 'Asset', -- description :: 'Text'}|] -- @@@ -299,10 +299,10 @@ [record| {rowIndex :: Int, timestamp :: UTCTime,- balance :: Decimal,- available :: Decimal,- balanceDelta :: Decimal,- availableDelta :: Decimal,+ balance :: Scientific,+ available :: Scientific,+ balanceDelta :: Scientific,+ availableDelta :: Scientific, currency :: Asset, description :: Text}|] @@ -330,15 +330,15 @@ -- [record| -- {pair :: 'CcyPair', -- type :: 'OrderType',--- volume :: 'Decimal',--- price :: 'Decimal' } |]+-- volume :: 'Scientific',+-- price :: 'Scientific' } |] -- @ type OrderRequest = [record| {pair :: CcyPair, type :: OrderType,- volume :: Decimal,- price :: Decimal } |]+ volume :: Scientific,+ price :: Scientific } |] type AccountID = Text @@ -349,17 +349,17 @@ -- [record| -- {id :: 'AccountID', -- asset :: 'Asset',--- balance :: 'Decimal',--- reserved :: 'Decimal',--- unconfirmed :: 'Decimal' } |]+-- balance :: 'Scientific',+-- reserved :: 'Scientific',+-- unconfirmed :: 'Scientific' } |] -- @ type Balance = [record| {id :: AccountID, asset :: Asset,- balance :: Decimal,- reserved :: Decimal,- unconfirmed :: Decimal } |]+ balance :: Scientific,+ reserved :: Scientific,+ unconfirmed :: Scientific } |] -- | A registered address for an acocunt. --@@ -368,15 +368,15 @@ -- [record| -- {asset :: 'Asset', -- address :: 'Text',--- totalReceived :: 'Decimal',--- totalUnconfirmed :: 'Decimal'} |]+-- totalReceived :: 'Scientific',+-- totalUnconfirmed :: 'Scientific'} |] -- @ type FundingAddress = [record| {asset :: Asset, address :: Text,- totalReceived :: Decimal,- totalUnconfirmed :: Decimal} |]+ totalReceived :: Scientific,+ totalUnconfirmed :: Scientific} |] -- | The state of a request to withdraw from an account. --@@ -397,19 +397,19 @@ --type NewWithdrawal = -- [record| -- {type :: 'WithdrawalType',--- amount :: 'Decimal' } |]+-- amount :: 'Scientific' } |] -- @ type NewWithdrawal = [record| {type :: WithdrawalType,- amount :: Decimal } |]+ amount :: Scientific } |] -- | A request to send bitcoin to a bitcoin address or email address. -- -- @ --type BitcoinSendRequest = -- [record|--- {amount :: 'Decimal',+-- {amount :: 'Scientific', -- currency :: 'Asset', -- address :: 'Text', -- description :: 'Maybe' 'Text',@@ -417,7 +417,7 @@ -- @ type BitcoinSendRequest = [record|- {amount :: Decimal,+ {amount :: Scientific, currency :: Asset, address :: Text, description :: Maybe Text,@@ -430,13 +430,13 @@ -- [record| -- {type :: 'QuoteType', -- pair :: 'CcyPair',--- baseAmount :: 'Decimal'} |]+-- baseAmount :: 'Scientific'} |] -- @ type QuoteRequest = [record| {type :: QuoteType, pair :: CcyPair,- baseAmount :: Decimal} |]+ baseAmount :: Scientific} |] -- | A temporarily locked in quote. --@@ -446,8 +446,8 @@ -- {id :: 'Text', -- type :: 'QuoteType', -- pair :: 'CcyPair',--- baseAmount :: 'Decimal',--- counterAmount :: 'Decimal',+-- baseAmount :: 'Scientific',+-- counterAmount :: 'Scientific', -- createdAt :: 'UTCTime', -- expiresAt :: 'UTCTime', -- discarded :: 'Bool',@@ -458,12 +458,27 @@ {id :: Text, type :: QuoteType, pair :: CcyPair,- baseAmount :: Decimal,- counterAmount :: Decimal,+ baseAmount :: Scientific,+ counterAmount :: Scientific, createdAt :: UTCTime, expiresAt :: UTCTime, discarded :: Bool, exercised :: Bool} |]++-- | A registered account.+--+-- @+--type Account =+-- [record|+-- {id :: Text,+-- name :: Text,+-- currency :: Asset} |]+-- @+type Account =+ [record|+ {id :: Text,+ name :: Text,+ currency :: Asset} |] -- | The type of a withdrawal request. data WithdrawalType =
src/Network/Bitcoin/BitX/Types/Internal.hs view
@@ -1,7 +1,5 @@ {-# LANGUAGE QuasiQuotes, OverloadedStrings, TemplateHaskell, MultiParamTypeClasses,- FunctionalDependencies, FlexibleInstances, DataKinds, CPP #-}--{-# OPTIONS_GHC -fno-warn-unused-binds #-}+ FunctionalDependencies, FlexibleInstances, DataKinds, CPP, RecordWildCards #-} module Network.Bitcoin.BitX.Types.Internal (@@ -29,15 +27,19 @@ #else import Data.Monoid (mempty) #endif-import Data.Decimal (Decimal)+import Data.Scientific (Scientific)+--import Data.Scientific (Scientific) import Data.ByteString (ByteString) import Data.List.Split (splitOn)+#if MIN_VERSION_base(4,7,0)+import Data.Coerce+#endif timestampParse_ :: Integer -> UTCTime timestampParse_ = posixSecondsToUTCTime- . fromRational . toRational- . ( / 1000)- . (fromIntegral :: Integer -> Decimal)+ . realToFrac+ . ( / 1000)+ . (fromIntegral :: Integer -> Scientific) class (FromJSON aes) => BitXAesRecordConvert rec aes | rec -> aes where aesToRec :: aes -> rec@@ -49,20 +51,25 @@ showableToBytestring_ :: (Show a) => a -> ByteString showableToBytestring_ = Txt.encodeUtf8 . Txt.pack . show --- | Wrapper around Decimal and FromJSON instance, to facilitate automatic JSON instances+-- | Wrapper around Scientific and FromJSON instance, to facilitate automatic JSON instances -newtype QuotedDecimal = QuotedDecimal Decimal deriving (Read, Show)+newtype QuotedScientific = QuotedScientific Scientific deriving (Read, Show) -instance FromJSON QuotedDecimal where- parseJSON (String x) = return . QuotedDecimal . read . Txt.unpack $ x- parseJSON (Number x) = return . QuotedDecimal . read . show $ x+instance FromJSON QuotedScientific where+ parseJSON (String x) = return . QuotedScientific . read . Txt.unpack $ x+ parseJSON (Number x) = return . QuotedScientific . read . show $ x parseJSON _ = mempty ---instance ToJSON QuotedDecimal where--- toJSON (QuotedDecimal q) = Number . realToFrac $ q+--instance ToJSON QuotedScientific where+-- toJSON (QuotedScientific q) = Number . realToFrac $ q -qdToDecimal :: QuotedDecimal -> Decimal-qdToDecimal (QuotedDecimal dec) = dec+qsToScientific :: QuotedScientific -> Scientific+#if MIN_VERSION_base(4,7,0)+qsToScientific = coerce+{-# INLINE qsToScientific #-}+#else+qsToScientific (QuotedScientific sci) = sci+#endif -- | Wrapper around UTCTime and FromJSON instance, to facilitate automatic JSON instances @@ -90,7 +97,8 @@ orderTypeParse (OrderType_ "BID") = BID orderTypeParse (OrderType_ "ASK") = ASK orderTypeParse (OrderType_ "SELL") = ASK-orderTypeParse (OrderType_ _ ) = error "Yet another surprise from the BitX API..."+orderTypeParse (OrderType_ x ) =+ error $ "Yet another surprise from the BitX API: unexpected OrderType " ++ Txt.unpack x newtype RequestStatus_ = RequestStatus_ Text deriving (Read, Show)@@ -104,16 +112,17 @@ requestStatusParse (RequestStatus_ "COMPLETE") = COMPLETE requestStatusParse (RequestStatus_ "COMPLETED") = COMPLETE requestStatusParse (RequestStatus_ "CANCELLED") = CANCELLED-requestStatusParse (RequestStatus_ _ ) = error "Yet another surprise from the BitX API..."+requestStatusParse (RequestStatus_ x ) =+ error $ "Yet another surprise from the BitX API: unexpected RequestStatus " ++ Txt.unpack x -------------------------------------------- Ticker type ------------------------------------------- data Ticker_ = Ticker_ { ticker'timestamp :: TimestampMS- , ticker'bid :: QuotedDecimal- , ticker'ask :: QuotedDecimal- , ticker'last_trade :: QuotedDecimal- , ticker'rolling_24_hour_volume :: QuotedDecimal+ , ticker'bid :: QuotedScientific+ , ticker'ask :: QuotedScientific+ , ticker'last_trade :: QuotedScientific+ , ticker'rolling_24_hour_volume :: QuotedScientific , ticker'pair :: CcyPair } @@ -121,14 +130,13 @@ ''Ticker_) instance BitXAesRecordConvert Ticker Ticker_ where- aesToRec (Ticker_ ticker''timestamp ticker''bid ticker''ask ticker''lastTrade- ticker''rolling24HourVolume ticker''pair) =- [record| {timestamp = tsmsToUTCTime ticker''timestamp,- bid = qdToDecimal ticker''bid,- ask = qdToDecimal ticker''ask,- lastTrade = qdToDecimal ticker''lastTrade,- rolling24HourVolume = qdToDecimal ticker''rolling24HourVolume,- pair = ticker''pair} |]+ aesToRec (Ticker_ {..}) =+ [record| {timestamp = tsmsToUTCTime ticker'timestamp,+ bid = qsToScientific ticker'bid,+ ask = qsToScientific ticker'ask,+ lastTrade = qsToScientific ticker'last_trade,+ rolling24HourVolume = qsToScientific ticker'rolling_24_hour_volume,+ pair = ticker'pair} |] --------------------------------------------- Tickers type ----------------------------------------- @@ -140,8 +148,8 @@ ''Tickers_) instance BitXAesRecordConvert [Ticker] Tickers_ where- aesToRec (Tickers_ tickers''tickers) =- map aesToRec tickers''tickers+ aesToRec (Tickers_ {..}) =+ map aesToRec tickers'tickers -------------------------------------------- BitXError type ---------------------------------------- @@ -153,23 +161,23 @@ $(AesTH.deriveJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"} ''BitXError_) instance BitXAesRecordConvert BitXError BitXError_ where- aesToRec (BitXError_ bitXError''error bitXError''error_code) =- [record| {error = bitXError''error,- errorCode = bitXError''error_code} |]+ aesToRec (BitXError_ {..}) =+ [record| {error = bitXError'error,+ errorCode = bitXError'error_code} |] -------------------------------------------- Order type -------------------------------------------- data Order_ = Order_- { order'volume :: QuotedDecimal,- order'price :: QuotedDecimal+ { order'volume :: QuotedScientific,+ order'price :: QuotedScientific } $(AesTH.deriveFromJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"} ''Order_) instance BitXAesRecordConvert Order Order_ where- aesToRec (Order_ order''volume order''price) =- [record| {volume = qdToDecimal order''volume,- price = qdToDecimal order''price} |]+ aesToRec (Order_ {..}) =+ [record| {volume = qsToScientific order'volume,+ price = qsToScientific order'price} |] -------------------------------------------- Orderbook type ---------------------------------------- @@ -186,53 +194,52 @@ ''Orderbook_) instance BitXAesRecordConvert Orderbook Orderbook_ where- aesToRec (Orderbook_ orderbook''timestamp orderbook''bids orderbook''asks) =- [record| {timestamp = tsmsToUTCTime orderbook''timestamp,- bids = map aesToRec orderbook''bids,- asks = map aesToRec orderbook''asks} |]+ aesToRec (Orderbook_ {..}) =+ [record| {timestamp = tsmsToUTCTime orderbook'timestamp,+ bids = map aesToRec orderbook'bids,+ asks = map aesToRec orderbook'asks} |] -------------------------------------------- Trade type -------------------------------------------- data Trade_ = Trade_- { trade'volume :: QuotedDecimal+ { trade'volume :: QuotedScientific , trade'timestamp :: TimestampMS- , trade'price :: QuotedDecimal+ , trade'price :: QuotedScientific } $(AesTH.deriveFromJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"} ''Trade_) instance BitXAesRecordConvert Trade Trade_ where- aesToRec (Trade_ trade''volume trade''timestamp trade''price) =- [record| {volume = qdToDecimal trade''volume,- timestamp = tsmsToUTCTime trade''timestamp,- price = qdToDecimal trade''price} |]+ aesToRec (Trade_ {..}) =+ [record| {volume = qsToScientific trade'volume,+ timestamp = tsmsToUTCTime trade'timestamp,+ price = qsToScientific trade'price} |] ----------------------------------------- PublicTrades type ---------------------------------------- data PublicTrades_ = PublicTrades_ { publicTrades'trades :: [Trade_]- --, publicTrades'currency :: Asset } $(AesTH.deriveFromJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"} ''PublicTrades_) instance BitXAesRecordConvert [Trade] PublicTrades_ where- aesToRec (PublicTrades_ publicTrades''trades {-publicTrades''currency-}) =- map aesToRec publicTrades''trades+ aesToRec (PublicTrades_ {..}) =+ map aesToRec publicTrades'trades ------------------------------------------ PrivateOrder type --------------------------------------- data PrivateOrder_ = PrivateOrder_- { privateOrder'base :: QuotedDecimal- , privateOrder'counter :: QuotedDecimal+ { privateOrder'base :: QuotedScientific+ , privateOrder'counter :: QuotedScientific , privateOrder'creation_timestamp :: TimestampMS , privateOrder'expiration_timestamp :: TimestampMS- , privateOrder'fee_base :: QuotedDecimal- , privateOrder'fee_counter :: QuotedDecimal- , privateOrder'limit_price :: QuotedDecimal- , privateOrder'limit_volume :: QuotedDecimal+ , privateOrder'fee_base :: QuotedScientific+ , privateOrder'fee_counter :: QuotedScientific+ , privateOrder'limit_price :: QuotedScientific+ , privateOrder'limit_volume :: QuotedScientific , privateOrder'order_id :: OrderID , privateOrder'pair :: CcyPair , privateOrder'state :: RequestStatus_@@ -243,22 +250,19 @@ ''PrivateOrder_) instance BitXAesRecordConvert PrivateOrder PrivateOrder_ where- aesToRec (PrivateOrder_ privateOrder''base privateOrder''counter- privateOrder''creation_timestamp privateOrder''expiration_timestamp privateOrder''fee_base- privateOrder''fee_counter privateOrder''limit_price privateOrder''limit_volume- privateOrder''order_id privateOrder''pair privateOrder''state privateOrder''type) =- [record| {base = qdToDecimal privateOrder''base,- counter = qdToDecimal privateOrder''counter,- creationTimestamp = tsmsToUTCTime privateOrder''creation_timestamp,- expirationTimestamp = tsmsToUTCTime privateOrder''expiration_timestamp,- feeBase = qdToDecimal privateOrder''fee_base,- feeCounter = qdToDecimal privateOrder''fee_counter,- limitPrice = qdToDecimal privateOrder''limit_price,- limitVolume = qdToDecimal privateOrder''limit_volume,- id = privateOrder''order_id,- pair = privateOrder''pair,- state = requestStatusParse privateOrder''state,- type = orderTypeParse privateOrder''type} |]+ aesToRec (PrivateOrder_ {..}) =+ [record| {base = qsToScientific privateOrder'base,+ counter = qsToScientific privateOrder'counter,+ creationTimestamp = tsmsToUTCTime privateOrder'creation_timestamp,+ expirationTimestamp = tsmsToUTCTime privateOrder'expiration_timestamp,+ feeBase = qsToScientific privateOrder'fee_base,+ feeCounter = qsToScientific privateOrder'fee_counter,+ limitPrice = qsToScientific privateOrder'limit_price,+ limitVolume = qsToScientific privateOrder'limit_volume,+ id = privateOrder'order_id,+ pair = privateOrder'pair,+ state = requestStatusParse privateOrder'state,+ type = orderTypeParse privateOrder'type} |] ------------------------------------------ PrivateOrders type -------------------------------------- @@ -270,8 +274,8 @@ ''PrivateOrders_) instance BitXAesRecordConvert [PrivateOrder] PrivateOrders_ where- aesToRec (PrivateOrders_ privateOrders''orders) =- map aesToRec privateOrders''orders+ aesToRec (PrivateOrders_ {..}) =+ map aesToRec privateOrders'orders ------------------------------------------ OrderRequest type --------------------------------------- @@ -292,8 +296,8 @@ ''OrderIDRec_) instance BitXAesRecordConvert OrderID OrderIDRec_ where- aesToRec (OrderIDRec_ orderIDResponse''order_id) =- orderIDResponse''order_id+ aesToRec (OrderIDRec_ {..}) =+ orderIDResponse'order_id instance POSTEncodeable OrderID where postEncode oid =@@ -309,20 +313,20 @@ ''RequestSuccess_) instance BitXAesRecordConvert RequestSuccess RequestSuccess_ where- aesToRec (RequestSuccess_ requestSuccess''success) =- requestSuccess''success+ aesToRec (RequestSuccess_ {..}) =+ requestSuccess'success ------------------------------------- PrivateOrderWithTrades type ---------------------------------- data PrivateOrderWithTrades_ = PrivateOrderWithTrades_- { privateOrderWithTrades'base :: QuotedDecimal- , privateOrderWithTrades'counter :: QuotedDecimal+ { privateOrderWithTrades'base :: QuotedScientific+ , privateOrderWithTrades'counter :: QuotedScientific , privateOrderWithTrades'creation_timestamp :: TimestampMS , privateOrderWithTrades'expiration_timestamp :: TimestampMS- , privateOrderWithTrades'fee_base :: QuotedDecimal- , privateOrderWithTrades'fee_counter :: QuotedDecimal- , privateOrderWithTrades'limit_price :: QuotedDecimal- , privateOrderWithTrades'limit_volume :: QuotedDecimal+ , privateOrderWithTrades'fee_base :: QuotedScientific+ , privateOrderWithTrades'fee_counter :: QuotedScientific+ , privateOrderWithTrades'limit_price :: QuotedScientific+ , privateOrderWithTrades'limit_volume :: QuotedScientific , privateOrderWithTrades'order_id :: OrderID , privateOrderWithTrades'pair :: CcyPair , privateOrderWithTrades'state :: RequestStatus_@@ -334,79 +338,73 @@ ''PrivateOrderWithTrades_) instance BitXAesRecordConvert PrivateOrderWithTrades PrivateOrderWithTrades_ where- aesToRec (PrivateOrderWithTrades_ privateOrder''base privateOrder''counter- privateOrder''creation_timestamp privateOrder''expiration_timestamp privateOrder''fee_base- privateOrder''fee_counter privateOrder''limit_price privateOrder''limit_volume- privateOrder''order_id privateOrder''pair privateOrder''state privateOrder''type- privateOrderWithTrades''trades) =- [record| {base = qdToDecimal privateOrder''base,- counter = qdToDecimal privateOrder''counter,- creationTimestamp = tsmsToUTCTime privateOrder''creation_timestamp,- expirationTimestamp = tsmsToUTCTime privateOrder''expiration_timestamp,- feeBase = qdToDecimal privateOrder''fee_base,- feeCounter = qdToDecimal privateOrder''fee_counter,- limitPrice = qdToDecimal privateOrder''limit_price,- limitVolume = qdToDecimal privateOrder''limit_volume,- id = privateOrder''order_id,- pair = privateOrder''pair,- state = requestStatusParse privateOrder''state,- type = orderTypeParse privateOrder''type,- trades = map aesToRec privateOrderWithTrades''trades} |]+ aesToRec (PrivateOrderWithTrades_ {..}) =+ [record| {base = qsToScientific privateOrderWithTrades'base,+ counter = qsToScientific privateOrderWithTrades'counter,+ creationTimestamp = tsmsToUTCTime privateOrderWithTrades'creation_timestamp,+ expirationTimestamp = tsmsToUTCTime privateOrderWithTrades'expiration_timestamp,+ feeBase = qsToScientific privateOrderWithTrades'fee_base,+ feeCounter = qsToScientific privateOrderWithTrades'fee_counter,+ limitPrice = qsToScientific privateOrderWithTrades'limit_price,+ limitVolume = qsToScientific privateOrderWithTrades'limit_volume,+ id = privateOrderWithTrades'order_id,+ pair = privateOrderWithTrades'pair,+ state = requestStatusParse privateOrderWithTrades'state,+ type = orderTypeParse privateOrderWithTrades'type,+ trades = map aesToRec privateOrderWithTrades'trades} |] -------------------------------------------- Balance type ------------------------------------------ data Balance_ = Balance_ { balance'account_id :: AccountID , balance'asset :: Asset- , balance'balance :: QuotedDecimal- , balance'reserved :: QuotedDecimal- , balance'unconfirmed :: QuotedDecimal+ , balance'balance :: QuotedScientific+ , balance'reserved :: QuotedScientific+ , balance'unconfirmed :: QuotedScientific } $(AesTH.deriveFromJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"} ''Balance_) instance BitXAesRecordConvert Balance Balance_ where- aesToRec (Balance_ balance''account_id balance''asset balance''balance balance''reserved- balance''unconfirmed) =- [record| {id = balance''account_id,- asset = balance''asset,- balance = qdToDecimal balance''balance,- reserved = qdToDecimal balance''reserved,- unconfirmed = qdToDecimal balance''unconfirmed} |]+ aesToRec (Balance_ {..}) =+ [record| {id = balance'account_id,+ asset = balance'asset,+ balance = qsToScientific balance'balance,+ reserved = qsToScientific balance'reserved,+ unconfirmed = qsToScientific balance'unconfirmed} |] -------------------------------------------- Balances type ----------------------------------------- data Balances_ = Balances_- {balances'balance :: [Balance_]+ { balances'balance :: [Balance_] } $(AesTH.deriveFromJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"} ''Balances_) instance BitXAesRecordConvert [Balance] Balances_ where- aesToRec (Balances_ balances''balances) =- map aesToRec balances''balances+ aesToRec (Balances_ {..}) =+ map aesToRec balances'balance ----------------------------------------- FundingAddress type -------------------------------------- data FundingAddress_ = FundingAddress_ { fundingAdress'asset :: Asset , fundingAdress'address :: Text- , fundingAdress'total_received :: QuotedDecimal- , fundingAdress'total_unconfirmed :: QuotedDecimal+ , fundingAdress'total_received :: QuotedScientific+ , fundingAdress'total_unconfirmed :: QuotedScientific } $(AesTH.deriveFromJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"} ''FundingAddress_) instance BitXAesRecordConvert FundingAddress FundingAddress_ where- aesToRec (FundingAddress_ fundingAdress''asset fundingAdress''address- fundingAdress''total_received fundingAdress''total_unconfirmed) =- [record| {asset = fundingAdress''asset,- address = fundingAdress''address,- totalReceived = qdToDecimal fundingAdress''total_received,- totalUnconfirmed = qdToDecimal fundingAdress''total_unconfirmed} |]+ aesToRec (FundingAddress_ {..}) =+ [record| {asset = fundingAdress'asset,+ address = fundingAdress'address,+ totalReceived = qsToScientific fundingAdress'total_received,+ totalUnconfirmed = qsToScientific fundingAdress'total_unconfirmed} |] --------------------------------------------- Asset type ------------------------------------------- @@ -425,9 +423,9 @@ ''WithdrawalRequest_) instance BitXAesRecordConvert WithdrawalRequest WithdrawalRequest_ where- aesToRec (WithdrawalRequest_ withdrawalRequest''status withdrawalRequest''id) =- [record| {status = requestStatusParse withdrawalRequest''status,- id = withdrawalRequest''id} |]+ aesToRec (WithdrawalRequest_ {..}) =+ [record| {status = requestStatusParse withdrawalRequest'status,+ id = withdrawalRequest'id} |] -------------------------------------- WithdrawalRequests type ------------------------------------- @@ -439,8 +437,8 @@ ''WithdrawalRequests_) instance BitXAesRecordConvert [WithdrawalRequest] WithdrawalRequests_ where- aesToRec (WithdrawalRequests_ withdrawalRequests''withdrawals) =- map aesToRec withdrawalRequests''withdrawals+ aesToRec (WithdrawalRequests_ {..}) =+ map aesToRec withdrawalRequests'withdrawals ----------------------------------------- NewWithdrawal type --------------------------------------- @@ -476,8 +474,8 @@ { orderQuote'id :: Text , orderQuote'type :: QuoteType , orderQuote'pair :: CcyPair- , orderQuote'base_amount :: QuotedDecimal- , orderQuote'counter_amount :: QuotedDecimal+ , orderQuote'base_amount :: QuotedScientific+ , orderQuote'counter_amount :: QuotedScientific , orderQuote'created_at :: TimestampMS , orderQuote'expires_at :: TimestampMS , orderQuote'discarded :: Bool@@ -488,18 +486,16 @@ ''OrderQuote_) instance BitXAesRecordConvert OrderQuote OrderQuote_ where- aesToRec (OrderQuote_ orderQuote''id orderQuote''type orderQuote''pair orderQuote''base_amount- orderQuote''counter_amount orderQuote''created_at orderQuote''expires_at orderQuote''discarded- orderQuote''exercised) =- [record| {id = orderQuote''id,- type = orderQuote''type,- pair = orderQuote''pair,- baseAmount = qdToDecimal orderQuote''base_amount,- counterAmount = qdToDecimal orderQuote''counter_amount,- createdAt = tsmsToUTCTime orderQuote''created_at,- expiresAt = tsmsToUTCTime orderQuote''expires_at,- discarded = orderQuote''discarded,- exercised = orderQuote''exercised} |]+ aesToRec (OrderQuote_ {..}) =+ [record| {id = orderQuote'id,+ type = orderQuote'type,+ pair = orderQuote'pair,+ baseAmount = qsToScientific orderQuote'base_amount,+ counterAmount = qsToScientific orderQuote'counter_amount,+ createdAt = tsmsToUTCTime orderQuote'created_at,+ expiresAt = tsmsToUTCTime orderQuote'expires_at,+ discarded = orderQuote'discarded,+ exercised = orderQuote'exercised} |] -------------------------------------------- BitXAuth type ----------------------------------------- @@ -512,19 +508,19 @@ ''BitXAuth_) instance BitXAesRecordConvert BitXAuth BitXAuth_ where- aesToRec (BitXAuth_ bitXAuth''api_key_id bitXAuth''api_key_secret) =- [record| {id = bitXAuth''api_key_id,- secret = bitXAuth''api_key_secret} |]+ aesToRec (BitXAuth_ {..}) =+ [record| {id = bitXAuth'api_key_id,+ secret = bitXAuth'api_key_secret} |] ------------------------------------------ Transaction type ---------------------------------------- data Transaction_ = Transaction_ { transaction'row_index :: Int , transaction'timestamp :: TimestampMS- , transaction'balance :: QuotedDecimal- , transaction'available :: QuotedDecimal- , transaction'balance_delta :: QuotedDecimal- , transaction'available_delta :: QuotedDecimal+ , transaction'balance :: QuotedScientific+ , transaction'available :: QuotedScientific+ , transaction'balance_delta :: QuotedScientific+ , transaction'available_delta :: QuotedScientific , transaction'currency :: Asset , transaction'description :: Text }@@ -533,17 +529,15 @@ ''Transaction_) instance BitXAesRecordConvert Transaction Transaction_ where- aesToRec (Transaction_ transaction''row_index transaction''timestamp transaction''balance- transaction''available transaction''balance_delta transaction''available_delta- transaction''currency transaction''description) =- [record| {rowIndex = transaction''row_index,- timestamp = tsmsToUTCTime transaction''timestamp,- balance = qdToDecimal transaction''balance,- available = qdToDecimal transaction''available,- balanceDelta = qdToDecimal transaction''balance_delta,- availableDelta = qdToDecimal transaction''available_delta,- currency = transaction''currency,- description = transaction''description} |]+ aesToRec (Transaction_ {..}) =+ [record| {rowIndex = transaction'row_index,+ timestamp = tsmsToUTCTime transaction'timestamp,+ balance = qsToScientific transaction'balance,+ available = qsToScientific transaction'available,+ balanceDelta = qsToScientific transaction'balance_delta,+ availableDelta = qsToScientific transaction'available_delta,+ currency = transaction'currency,+ description = transaction'description} |] ---------------------------------------- Transactions type ----------------------------------------- @@ -555,8 +549,8 @@ ''Transactions_) instance BitXAesRecordConvert [Transaction] Transactions_ where- aesToRec (Transactions_ transactions''transactions) =- map aesToRec transactions''transactions+ aesToRec (Transactions_ {..}) =+ map aesToRec transactions'transactions data PendingTransactions_ = PendingTransactions_@@ -567,8 +561,8 @@ ''PendingTransactions_) instance BitXAesRecordConvert PendingTransactions__ PendingTransactions_ where- aesToRec (PendingTransactions_ transactions''pending) =- [record| {transactions = map aesToRec transactions''pending}|]+ aesToRec (PendingTransactions_ {..}) =+ [record| {transactions = map aesToRec transactions'pending}|] type PendingTransactions__ = [record|@@ -577,3 +571,24 @@ pendingTransactionsToTransactions :: PendingTransactions__ -> [Transaction] pendingTransactionsToTransactions pts = (view [lens| transactions |] pts) +-------------------------------------------- Account type ------------------------------------------++data Account_ = Account_+ { account'id :: Text+ , account'name :: Text+ , account'currency :: Asset+ }++$(AesTH.deriveFromJSON AesTH.defaultOptions{AesTH.fieldLabelModifier = last . splitOn "'"}+ ''Account_)++instance BitXAesRecordConvert Account Account_ where+ aesToRec (Account_ {..}) =+ [record| {id = account'id,+ name = account'name,+ currency = account'currency} |]++instance POSTEncodeable Account where+ postEncode acc =+ [("name", showableToBytestring_ (view [lens| name |] acc)),+ ("currency", showableToBytestring_ (view [lens| currency |] acc))]
+ test/Network/Bitcoin/BitX/Spec/Common.hs view
@@ -0,0 +1,12 @@+module Network.Bitcoin.BitX.Spec.Common+ (+ recordAesCheck+ ) where++import Test.Hspec+import Data.Aeson+import Network.Bitcoin.BitX (BitXAesRecordConvert(..))+import Data.ByteString.Lazy (ByteString)++recordAesCheck :: (BitXAesRecordConvert rec aes, Show rec, Eq rec) => ByteString -> rec -> Expectation+recordAesCheck aesTxt recd = (fmap aesToRec $ decode aesTxt) `shouldBe` Just recd
+ test/Network/Bitcoin/BitX/Spec/Specs/AesonRecordSpec.hs view
@@ -0,0 +1,225 @@+{-# LANGUAGE OverloadedStrings, QuasiQuotes, DataKinds #-}++module Network.Bitcoin.BitX.Spec.Specs.AesonRecordSpec+ (+ spec+ ) where++import Test.Hspec+import Network.Bitcoin.BitX.Types+import Record+import Data.Time.Clock.POSIX+import Network.Bitcoin.BitX.Spec.Common++spec :: Spec+spec = do+ describe "FromJSON to Record" $ do+ it "BitXError is parsed properly" $ do+ recordAesCheck+ "{\"error\" : \"oops\", \"error_code\" : \"ABadError\"}"+ ([record| {error = "oops", errorCode = "ABadError"} |] :: BitXError)+ it "Ticker is parsed properly" $ do+ recordAesCheck+ "{\"timestamp\":1431811395699,\"bid\":\"3083.00\",\"ask\":\"3115.00\",\+ \ \"last_trade\":\"3116.00\",\"rolling_24_hour_volume\":\"19.776608\",\"pair\":\"XBTZAR\"}"+ ([record|+ {ask = 3115.00,+ timestamp = (posixSecondsToUTCTime 1431811395.699),+ bid = 3083.0,+ rolling24HourVolume = 19.776608,+ lastTrade = 3116.00,+ pair = XBTZAR} |] :: Ticker)+ it "Balance is parsed properly" $ do+ recordAesCheck+ "{\"account_id\":\"314159\",\"asset\":\"ZAR\",\"balance\":\"2159.15\",\"reserved\":\"320\",\+ \ \"unconfirmed\":\"175\"}"+ ([record|+ {id = "314159",+ asset = ZAR,+ balance = 2159.15,+ reserved = 320,+ unconfirmed = 175} |] :: Balance)+ it "Order is parsed properly" $ do+ recordAesCheck+ "{\"volume\":\"314159\",\"price\":\"4321\"}"+ ([record|+ {volume = 314159,+ price = 4321} |] :: Order)+ it "WithdrawalRequest is parsed properly" $ do+ recordAesCheck+ "{\"status\":\"PENDING\",\"id\":\"271828\"}"+ ([record|+ {status = PENDING,+ id = "271828" } |] :: WithdrawalRequest)+ it "Tickers is parsed properly" $ do+ recordAesCheck+ "{\"tickers\":[{\"timestamp\":1431811395699,\"bid\":\"3083.00\",\"ask\":\"3115.00\",\+ \ \"last_trade\":\"3116.00\",\"rolling_24_hour_volume\":\"19.776608\",\+ \ \"pair\":\"XBTZAR\"}]}"+ [tickerInner]+ it "Orderbook is parsed properly" $ do+ recordAesCheck+ "{\"timestamp\":1431811395699,\"bids\":[{\"volume\":\"654.98\",\"price\":\"3789.66\"}],\+ \ \"asks\":[{\"volume\":\"654.98\",\"price\":\"3789.66\"}]}"+ ([record|+ {timestamp = (posixSecondsToUTCTime 1431811395.699),+ bids = [orderInner],+ asks = [orderInner] } |] :: Orderbook)+ it "Trade is parsed properly" $ do+ recordAesCheck+ "{\"timestamp\":1431811395699,\"volume\":\"6754.09\",\"price\":\"5327.765\"}"+ ([record|+ {timestamp = (posixSecondsToUTCTime 1431811395.699),+ volume = 6754.09,+ price = 5327.765 } |] :: Trade)+ it "PublicTrades is parsed properly" $ do+ recordAesCheck+ "{\"trades\":[{\"timestamp\":1431811395699,\"volume\":\"6754.09\",\+ \ \"price\":\"5327.765\"}],\"currency\":\"ZAR\"}"+ [tradeInner]+ it "PrivateOrder is parsed properly" $ do+ recordAesCheck+ "{\"base\":\"568.7\", \"counter\":3764.2,\"creation_timestamp\":478873467, \+ \ \"expiration_timestamp\":8768834222, \"fee_base\":\"3687.3\", \"fee_counter\":12.9,\+ \ \"limit_price\":765,\"limit_volume\":55.2,\"order_id\":\"83YG\",\"pair\":\"NADXBT\",\+ \ \"state\":\"COMPLETE\",\"type\":\"BID\"}"+ ([record|+ {base = 568.7,+ counter = 3764.2,+ creationTimestamp = (posixSecondsToUTCTime 478873.467),+ expirationTimestamp = (posixSecondsToUTCTime 8768834.222),+ feeBase = 3687.3,+ feeCounter = 12.9,+ limitPrice = 765,+ limitVolume = 55.2,+ id = "83YG",+ pair = NADXBT,+ state = COMPLETE,+ type = BID } |] :: PrivateOrder)+ it "PrivateOrders is parsed properly" $ do+ recordAesCheck+ "{\"orders\":[{\"base\":\"568.7\", \"counter\":3764.2,\"creation_timestamp\":478873467, \+ \ \"expiration_timestamp\":8768834222, \"fee_base\":\"3687.3\", \"fee_counter\":12.9,\+ \ \"limit_price\":765,\"limit_volume\":55.2,\"order_id\":\"83YG\",\"pair\":\"NADXBT\",\+ \ \"state\":\"COMPLETE\",\"type\":\"BID\"}]}"+ [privateOrderInner]+ it "OrderID is parsed properly" $ do+ recordAesCheck+ "{\"order_id\":\"57983\"}"+ ("57983" :: OrderID)+ it "PublicTrades is parsed properly" $ do+ recordAesCheck+ "{\"trades\":[{\"timestamp\":1431811395699,\"volume\":\"6754.09\",\"price\":\"5327.765\"}], \+ \ \"currency\":\"ZAR\"}"+ [tradeInner]+ it "RequestSuccess is parsed properly" $ do+ recordAesCheck+ "{\"success\":true}"+ (True :: RequestSuccess)+ it "PrivateOrderWithTrades is parsed properly" $ do+ recordAesCheck+ "{\"base\":\"568.7\", \"counter\":3764.2,\"creation_timestamp\":478873467, \+ \ \"expiration_timestamp\":8768834222, \"fee_base\":\"3687.3\", \"fee_counter\":12.9,\+ \ \"limit_price\":765,\"limit_volume\":55.2,\"order_id\":\"83YG\",\"pair\":\"NADXBT\",\+ \ \"state\":\"COMPLETE\",\"type\":\"BID\", \"trades\":[{\"timestamp\":1431811395699, \+ \ \"volume\":\"6754.09\",\"price\":\"5327.765\"}]}"+ ([record|+ {base = 568.7,+ counter = 3764.2,+ creationTimestamp = (posixSecondsToUTCTime 478873.467),+ expirationTimestamp = (posixSecondsToUTCTime 8768834.222),+ feeBase = 3687.3,+ feeCounter = 12.9,+ limitPrice = 765,+ limitVolume = 55.2,+ id = "83YG",+ pair = NADXBT,+ state = COMPLETE,+ type = BID,+ trades = [tradeInner]} |] :: PrivateOrderWithTrades)+ it "WithdrawalRequest is parsed properly" $ do+ recordAesCheck+ "{\"status\":\"PENDING\", \"id\":\"7yrfU4987\"}"+ ([record|+ {status = PENDING,+ id = "7yrfU4987" } |] :: WithdrawalRequest)+ it "FundingAddress is parsed properly" $ do+ recordAesCheck+ "{\"asset\":\"ZAR\", \"address\":\"093gu959t894G\", \"total_received\":\"432.5\", \+ \ \"total_unconfirmed\":\"0.023\"}"+ ([record|+ {asset = ZAR,+ address = "093gu959t894G",+ totalReceived = 432.5,+ totalUnconfirmed = 0.023} |] :: FundingAddress)+ it "Transaction is parsed properly" $ do+ recordAesCheck+ "{\"row_index\":1,\"timestamp\":1387527013000,\"balance\":0.0199, \"available\":0.0299, \+ \ \"account_id\":\"3485527347968330182\", \"balance_delta\":0.0399, \+ \ \"available_delta\":0.0099, \"currency\":\"XBT\",\"description\":\"Bought BTC 0.01 for R 79.00\"}"+ ([record|+ {rowIndex = 1,+ timestamp = (posixSecondsToUTCTime 1387527013),+ balance = 0.0199,+ available = 0.0299,+ balanceDelta = 0.0399,+ availableDelta = 0.0099,+ currency = XBT,+ description = "Bought BTC 0.01 for R 79.00"}|] :: Transaction)+ it "Transactions is parsed properly" $ do+ recordAesCheck+ "{\"transactions\":[{\"row_index\":1,\"timestamp\":1387527013000,\"balance\":0.0199, \"available\":0.0299, \+ \ \"account_id\":\"3485527347968330182\", \"balance_delta\":0.0399, \+ \ \"available_delta\":0.0099, \"currency\":\"XBT\",\"description\":\"Bought BTC 0.01 for R 79.00\"}]}"+ [transactionInner]++tickerInner :: Ticker+tickerInner =+ [record|+ {ask = 3115.00,+ timestamp = (posixSecondsToUTCTime 1431811395.699),+ bid = 3083.0,+ rolling24HourVolume = 19.776608,+ lastTrade = 3116.00,+ pair = XBTZAR} |]++orderInner :: Order+orderInner =+ [record|+ {volume = 654.98,+ price = 3789.66} |]++tradeInner :: Trade+tradeInner =+ [record|+ {timestamp = (posixSecondsToUTCTime 1431811395.699),+ volume = 6754.09,+ price = 5327.765 } |]++privateOrderInner :: PrivateOrder+privateOrderInner =+ [record|+ {base = 568.7,+ counter = 3764.2,+ creationTimestamp = (posixSecondsToUTCTime 478873.467),+ expirationTimestamp = (posixSecondsToUTCTime 8768834.222),+ feeBase = 3687.3,+ feeCounter = 12.9,+ limitPrice = 765,+ limitVolume = 55.2,+ id = "83YG",+ pair = NADXBT,+ state = COMPLETE,+ type = BID } |]++transactionInner :: Transaction+transactionInner =+ [record|+ {rowIndex = 1,+ timestamp = (posixSecondsToUTCTime 1387527013),+ balance = 0.0199,+ available = 0.0299,+ balanceDelta = 0.0399,+ availableDelta = 0.0099,+ currency = XBT,+ description = "Bought BTC 0.01 for R 79.00"}|]
+ test/Network/Bitcoin/BitX/Spec/Specs/JsonSpec.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE OverloadedStrings, QuasiQuotes, DataKinds #-}++module Network.Bitcoin.BitX.Spec.Specs.JsonSpec+ (+ spec+ ) where++import Test.Hspec+import Network.Bitcoin.BitX.Types+import Record+import Network.Bitcoin.BitX.Spec.Common+import Data.Time.Clock.POSIX++spec :: Spec+spec = do+ describe "FromJSON intances" $ do+ it "QuotedDecimal is parsed whether quoted or not, for floating point numbers" $ do+ recordAesCheck+ "{\"volume\":314159.23,\"price\":\"4321.56\"}"+ ([record|+ {volume = 314159.23,+ price = 4321.56} |] :: Order)+ it "QuotedDecimal is parsed whether quoted or not, for integral numbers" $ do+ recordAesCheck+ "{\"volume\":314159,\"price\":\"4321\"}"+ ([record|+ {volume = 314159,+ price = 4321} |] :: Order)+ it "OrderType BUY is parsed properly" $ do+ recordAesCheck+ "{\"base\":\"568.7\", \"counter\":3764.2,\"creation_timestamp\":478873467, \+ \\"expiration_timestamp\":8768834222, \"fee_base\":\"3687.3\", \"fee_counter\":12.9,\+ \\"limit_price\":765,\"limit_volume\":55.2,\"order_id\":\"83YG\",\"pair\":\"NADXBT\",\+ \\"state\":\"COMPLETE\",\"type\":\"BUY\"}"+ ([record|+ {base = 568.7,+ counter = 3764.2,+ creationTimestamp = (posixSecondsToUTCTime 478873.467),+ expirationTimestamp = (posixSecondsToUTCTime 8768834.222),+ feeBase = 3687.3,+ feeCounter = 12.9,+ limitPrice = 765,+ limitVolume = 55.2,+ id = "83YG",+ pair = NADXBT,+ state = COMPLETE,+ type = BID } |] :: PrivateOrder)+ it "OrderType BID is parsed properly" $ do+ recordAesCheck+ "{\"base\":\"568.7\", \"counter\":3764.2,\"creation_timestamp\":478873467, \+ \\"expiration_timestamp\":8768834222, \"fee_base\":\"3687.3\", \"fee_counter\":12.9,\+ \\"limit_price\":765,\"limit_volume\":55.2,\"order_id\":\"83YG\",\"pair\":\"NADXBT\",\+ \\"state\":\"COMPLETE\",\"type\":\"BID\"}"+ ([record|+ {base = 568.7,+ counter = 3764.2,+ creationTimestamp = (posixSecondsToUTCTime 478873.467),+ expirationTimestamp = (posixSecondsToUTCTime 8768834.222),+ feeBase = 3687.3,+ feeCounter = 12.9,+ limitPrice = 765,+ limitVolume = 55.2,+ id = "83YG",+ pair = NADXBT,+ state = COMPLETE,+ type = BID } |] :: PrivateOrder)+ it "OrderType ASK is parsed properly" $ do+ recordAesCheck+ "{\"base\":\"568.7\", \"counter\":3764.2,\"creation_timestamp\":478873467, \+ \\"expiration_timestamp\":8768834222, \"fee_base\":\"3687.3\", \"fee_counter\":12.9,\+ \\"limit_price\":765,\"limit_volume\":55.2,\"order_id\":\"83YG\",\"pair\":\"NADXBT\",\+ \\"state\":\"COMPLETE\",\"type\":\"ASK\"}"+ ([record|+ {base = 568.7,+ counter = 3764.2,+ creationTimestamp = (posixSecondsToUTCTime 478873.467),+ expirationTimestamp = (posixSecondsToUTCTime 8768834.222),+ feeBase = 3687.3,+ feeCounter = 12.9,+ limitPrice = 765,+ limitVolume = 55.2,+ id = "83YG",+ pair = NADXBT,+ state = COMPLETE,+ type = ASK } |] :: PrivateOrder)+ it "OrderType SELL is parsed properly" $ do+ recordAesCheck+ "{\"base\":\"568.7\", \"counter\":3764.2,\"creation_timestamp\":478873467, \+ \\"expiration_timestamp\":8768834222, \"fee_base\":\"3687.3\", \"fee_counter\":12.9,\+ \\"limit_price\":765,\"limit_volume\":55.2,\"order_id\":\"83YG\",\"pair\":\"NADXBT\",\+ \\"state\":\"COMPLETE\",\"type\":\"SELL\"}"+ ([record|+ {base = 568.7,+ counter = 3764.2,+ creationTimestamp = (posixSecondsToUTCTime 478873.467),+ expirationTimestamp = (posixSecondsToUTCTime 8768834.222),+ feeBase = 3687.3,+ feeCounter = 12.9,+ limitPrice = 765,+ limitVolume = 55.2,+ id = "83YG",+ pair = NADXBT,+ state = COMPLETE,+ type = ASK } |] :: PrivateOrder)+ it "RequestStatus COMPLETE is parsed properly" $ do+ recordAesCheck+ "{\"status\":\"COMPLETE\", \"id\":\"\"}"+ ([record|+ {status = COMPLETE,+ id = "" } |] :: WithdrawalRequest)+ it "RequestStatus COMPLETED is parsed properly" $ do+ recordAesCheck+ "{\"status\":\"COMPLETED\", \"id\":\"\"}"+ ([record|+ {status = COMPLETE,+ id = "" } |] :: WithdrawalRequest)
+ test/Network/Bitcoin/BitX/Spec/Specs/NetSpec.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings, QuasiQuotes, DataKinds, TemplateHaskell #-}++module Network.Bitcoin.BitX.Spec.Specs.NetSpec+ (+ spec+ ) where++import Test.Hspec+import Network.Bitcoin.BitX.Types+import Network.Bitcoin.BitX.Public+import System.IO.Unsafe (unsafePerformIO)+import Network.Bitcoin.BitX.Response++spec :: Spec+spec = do+ describe "Public BitX connectivity" $ do+ it "getTicker connects to BitX and works" $ do+ connectsAndParsesOkay $ getTicker XBTZAR+ it "getTickers connects to BitX and works" $ do+ connectsAndParsesOkay $ getTickers+ it "getOrderBook connects to BitX and works" $ do+ connectsAndParsesOkay $ getOrderBook XBTZAR+ it "getTrades connects to BitX and works" $ do+ connectsAndParsesOkay $ getTrades XBTKES++connectsAndParsesOkay :: IO (BitXAPIResponse rec) -> Bool+connectsAndParsesOkay = isValidResponse . unsafePerformIO++isValidResponse :: BitXAPIResponse rec -> Bool+isValidResponse (ValidResponse _) = True+isValidResponse _ = False
+ test/Network/Bitcoin/BitX/Spec/Specs/PostSpec.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE OverloadedStrings, QuasiQuotes, DataKinds #-}++module Network.Bitcoin.BitX.Spec.Specs.PostSpec+ (+ spec+ ) where++import Test.Hspec+import Network.Bitcoin.BitX+import Record++spec :: Spec+spec = do+ describe "PostEncode test" $ do+ it "OrderRequest is post-encoded properly" $ do+ postEncode+ ([record|+ {pair = XBTZAR,+ type = BID,+ volume = 83.02,+ price = 15.23 } |] :: OrderRequest)+ `shouldBe`+ [("pair", "XBTZAR"),+ ("type", "BID"),+ ("volume", "83.02"),+ ("price", "15.23")]+ it "Asset is post-encoded properly" $ do+ postEncode ZAR+ `shouldBe`+ [("asset", "ZAR")]+ it "NewWithdrawal is post-encoded properly" $ do+ postEncode+ ([record|+ {type = ZAR_EFT,+ amount = 83.02} |] :: NewWithdrawal)+ `shouldBe`+ [("type", "ZAR_EFT"),+ ("amount", "83.02")]+ it "BitcoinSendRequest is post-encoded properly" $ do+ postEncode+ ([record|+ {amount = 83.02,+ currency = XBT,+ address = "ahglk98aslfk",+ description = Just "Send some coinz to dis ere dude.",+ message = Just "Dude, ere'z your coinz."} |] :: BitcoinSendRequest)+ `shouldBe`+ [("amount", "83.02"),+ ("currency", "XBT"),+ ("address", "ahglk98aslfk"),+ ("description", "Send some coinz to dis ere dude."),+ ("message", "Dude, ere'z your coinz.")]+ it "QuoteRequest is post-encoded properly" $ do+ postEncode+ ([record|+ {type = BUY,+ pair = XBTKES,+ baseAmount = 566.76} |] :: QuoteRequest)+ `shouldBe`+ [("type", "BUY"),+ ("pair", "XBTKES"),+ ("base_amount", "566.76")]