rbpcp-api 0.1.0.0 → 0.2.0.0
raw patch · 5 files changed
+104/−56 lines, 5 filesdep +bitcoin-payment-protocoldep +http-api-datadep +servant-client
Dependencies added: bitcoin-payment-protocol, http-api-data, servant-client, time
Files
- rbpcp-api.cabal +17/−14
- src/RBPCP/Api.hs +1/−7
- src/RBPCP/Callback/Types.hs +1/−0
- src/RBPCP/Internal/Types.hs +30/−14
- src/RBPCP/Types.hs +55/−21
rbpcp-api.cabal view
@@ -1,13 +1,12 @@--- This file has been generated from package.yaml by hpack version 0.14.0.+-- This file has been generated from package.yaml by hpack version 0.15.0. -- -- see: https://github.com/sol/hpack name: rbpcp-api-version: 0.1.0.0+version: 0.2.0.0 synopsis: RESTful Bitcoin Payment Channel Protocol Servant API description-description: RESTful Bitcoin Payment Channel Protocol (RBPCP) allows a client to send Bitcoin payments to a server by establishing a Bitcoin payment channel with the server in a standardized manner.+description: RESTful Bitcoin Payment Channel Protocol (RBPCP) allows a client to send Bitcoin payments to a server, by establishing a Bitcoin payment channel with the server in a standardized manner. This library provides Servant API endpoints for RBPCP.- API spec: http://paychandoc.runeks.me/ category: Finance Network Bitcoin homepage: http://paychandoc.runeks.me/ bug-reports: https://github.com/runeksvendsen/haskell-rbpcp-api/issues@@ -25,17 +24,21 @@ library hs-source-dirs: src- default-extensions: MultiParamTypeClasses FlexibleInstances+ default-extensions: OverloadedStrings MultiParamTypeClasses FlexibleInstances RecordWildCards DataKinds FlexibleContexts LambdaCase TypeOperators build-depends:- base >=4.7 && <5- , haskoin-core >=0.4.0 && <0.5.0- , base16-bytestring >=0.1.0 && <0.2.0- , bytestring >=0.10.0 && <0.11.0- , cereal >=0.5.0 && <0.6.0- , text >=1.2.0 && <1.3.0- , aeson >=0.11.0 && <1.1- , string-conversions >=0.4 && <0.5- , servant >=0.8 && <0.10+ base >=4.7 && <5+ , haskoin-core >=0.4.0 && <0.5.0+ , bitcoin-payment-protocol >=0.1 && <0.2+ , base16-bytestring >=0.1.0 && <0.2.0+ , bytestring >=0.10.0 && <0.11.0+ , cereal >=0.5.0 && <0.6.0+ , text >=1.2.0 && <1.3.0+ , aeson >=0.11.0 && <1.1+ , string-conversions >=0.4 && <0.5+ , servant >=0.8 && <0.10+ , servant-client >=0.8 && <0.10+ , time+ , http-api-data exposed-modules: RBPCP RBPCP.Types
src/RBPCP/Api.hs view
@@ -11,12 +11,7 @@ -- Example: /funding/028adc96575e3ee23a69eb17723911e77c5c06320e4354b1518bb635f32793c910/1474949961/info type FundInfo = VER :> "funding" :> Capture "client_pubkey" (Client PubKey) :> Capture "exp_time" BLT :> "info"- :> Get '[JSON] FundingInfo---- Example: /funding/028adc96575e3ee23a69eb17723911e77c5c06320e4354b1518bb635f32793c910/033911e77c5c06320e48adc96575e3ee23a672354b1518bb635f32793c9109eb17/1474949961/begin_open-type BeginOpen = VER :> "funding" :> Capture "client_pubkey" (Client PubKey) :> Capture "server_pubkey" (Server PubKey)- :> Capture "exp_time" BLT :> "begin_open"- :> Header "Host" String :> Get '[JSON] ChannelLocation+ :> Get '[JSON, PAYREQ] FundInfoResponse -- Example: /channels/028adc96575e3ee23a69eb17723911e77c5c06320e4354b1518bb635f32793c910/1474949961/53ee3615ac0dd479ec1d3e144eb651f65764d3a5e400c04cf3c79425e8b22fb0/2 type ChanOpen = VER :> "channels" :> Capture "client_pubkey" (Client PubKey) :> Capture "exp_time" BLT@@ -35,7 +30,6 @@ -- | RESTful Bitcoin payment channel protocol type RBPCP = FundInfo- :<|> BeginOpen :<|> ChanOpen :<|> ChanPay :<|> ChanClose
src/RBPCP/Callback/Types.hs view
@@ -12,6 +12,7 @@ { value_received :: Word64 , chan_value_left :: Word64 , chan_total_capacity :: Word64+ , client_pubkey :: PubKeyC , full_payment :: Payment } deriving (Generic, FromJSON, ToJSON)
src/RBPCP/Internal/Types.hs view
@@ -1,11 +1,8 @@-{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} module RBPCP.Internal.Types ( module RBPCP.Internal.Types- , module Network.Haskoin.Transaction- , module Network.Haskoin.Crypto- , module Network.Haskoin.Script- , module Data.Word+ , module X , ByteString , Generic )@@ -13,29 +10,48 @@ import RBPCP.Internal.Util -import Network.Haskoin.Transaction-import Network.Haskoin.Crypto hiding (PubKey)-import Network.Haskoin.Script+import Network.Haskoin.Transaction as X+import Network.Haskoin.Crypto as X hiding (PubKey)+import Network.Haskoin.Script as X import Data.ByteString (ByteString)-import Data.Word (Word32, Word64)+import Data.Word as X (Word32, Word64) import GHC.Generics (Generic) import Data.Aeson import qualified Data.Serialize as Bin+import qualified Web.HttpApiData as Web type PubKey = PubKeyC -data JsonHex a = JsonHex { fromHex :: a } deriving (Eq, Show, Generic, Bin.Serialize)-instance (Eq a, Show a, Bin.Serialize a) => ToJSON (JsonHex a) where+newtype JsonHex a = JsonHex { fromHex :: a } deriving (Eq, Show, Generic, Bin.Serialize)+instance Bin.Serialize a => ToJSON (JsonHex a) where toJSON = String . cs . hexEncode . fromHex-instance (Eq a, Show a, Bin.Serialize a) => FromJSON (JsonHex a) where+instance Bin.Serialize a => FromJSON (JsonHex a) where parseJSON = withText "JsonHex a" $ either (fail . (++ "Hex decode fail: ")) return . fmap JsonHex . hexDecode . cs -- | Wraps any client-related datatype (eg. pubkey, signature)-data Client a = Client a deriving (Eq, Show, Generic, Bin.Serialize, ToJSON, FromJSON)+newtype Client a = Client a deriving+ ( Eq+ , Show+ , Generic+ , Bin.Serialize+ , ToJSON+ , FromJSON+ , Web.ToHttpApiData+ , Web.FromHttpApiData+ ) -- | Wraps any server-related datatype (eg. pubkey, signature)-data Server a = Server a deriving (Eq, Show, Generic, Bin.Serialize, ToJSON, FromJSON)+newtype Server a = Server a deriving+ ( Eq+ , Show+ , Generic+ , Bin.Serialize+ , ToJSON+ , FromJSON+ , Web.ToHttpApiData+ , Web.FromHttpApiData+ )
src/RBPCP/Types.hs view
@@ -1,34 +1,75 @@-{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric, DeriveAnyClass, OverloadedStrings #-} module RBPCP.Types ( module RBPCP.Types+, PAYREQ , JsonHex(..)+, BaseUrl(..)+, Client(..)+, Server(..)+ ) where import RBPCP.Internal.Types import RBPCP.Internal.Util import RBPCP.Internal.Orphans ()+import PayProto (PaymentRequest, PAYREQ, PayReqSpec(..), mkPayRequestT) import Data.Aeson import Data.Aeson.Types+import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Data.Word (Word8, Word32, Word64) import qualified Data.Serialize as Bin+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import Servant.API (MimeRender(..), JSON)+import Servant.Client (BaseUrl(..))+import Data.Time.Clock --- Generated+-- Generated code import Data.List (stripPrefix) import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Function ((&)) +{- +mkPayRequestT :: UTCTime -> PayReqSpec -> P.PaymentRequest +-}+ type Vout = Word32 type Hours = Word type BtcConf = Word +data FundInfoResponse = FundInfoResponse+ { firFundInfo :: FundingInfo+ , firUserMemo :: T.Text -- ^ Memo displayed to user+ , firNow :: UTCTime -- ^ Current time+ , firPaymentURL :: BaseUrl+ -- ^ BIP70 @Payment@ POST URL. Will receive message (containing tx paying to the funding address)+ , firExpSecs :: Word64+ } +instance MimeRender PAYREQ FundInfoResponse where+ mimeRender a FundInfoResponse{..} =+ mimeRender a $+ mkPayRequestT firNow prs+ where prs = PayReqSpec+ { prsOuts = [(fundingInfoFundingAddressCopy firFundInfo, 0)]+ , prsPayUrl = firPaymentURL+ , prsExpSecs = firExpSecs+ , prsMerchMemo = firUserMemo+ , prsMerchData = BL.toStrict $ encode firFundInfo+ }++instance MimeRender JSON FundInfoResponse where+ mimeRender a FundInfoResponse{..} =+ mimeRender a firFundInfo++ data ChannelStatus = ChannelOpen | ChannelClosed deriving (Show, Eq) instance FromJSON ChannelStatus where parseJSON = withText "ChannelStatus" $@@ -66,16 +107,19 @@ 0x02 -> return ApplicationError n -> fail $ "expected 0x01 or 0x02, not: " ++ show n +instance Bin.Serialize Text where+ put = Bin.put . encodeUtf8+ get = decodeUtf8 <$> Bin.get --- Generated code with types modified:+-- Generated code with some types modified: -- | data PaymentResult = PaymentResult { paymentResult_channel_status :: ChannelStatus -- ^ Equal to \"open\" if the channel is still open, otherwise \"closed\". The channel is automatically closed when there is no value left to send. If a payment sends all remaining channel value to the server, the server will close the channel and set this field to \"closed\". , paymentResult_channel_valueLeft :: Word64 -- ^ Remaining channel value. This is the amount that the client/sender would receive if the channel was closed now. , paymentResult_value_received :: Word64 -- ^ Value of the payment that was just received. This is the additional value assigned to the receiver/server with this payment. , paymentResult_settlement_txid :: Maybe TxHash -- ^ If channel_status equals \"closed\": the transaction ID of the Bitcoin transaction which settles the channel; otherwise null.- , paymentResult_application_data :: T.Text -- ^ Optional application data- } deriving (Show, Eq, Generic)+ , paymentResult_application_data :: Text -- ^ Optional application data+ } deriving (Show, Eq, Generic, Bin.Serialize) -- | data FundingInfo = FundingInfo@@ -87,15 +131,16 @@ , fundingInfoFunding_tx_min_conf :: BtcConf -- ^ Minimum confirmation count that the funding transaction must have before proceeding with opening a new channel. , fundingInfoSettlement_period_hours :: Hours -- ^ The server reserves the right to close the payment channel this many hours before the specified expiration date. The server hasn't received any actual value until it publishes a payment transaction to the Bitcoin network, so it needs a window of time in which the client can no longer send payments over the channel, and yet the channel refund transaction hasn't become valid. , fundingInfoMin_duration_hours :: Hours -- ^ Minimum duration of newly opened channels- } deriving (Show, Eq, Generic)+ } deriving (Show, Eq, Generic, Bin.Serialize) -- | Error response type data Error = Error { errorType :: ErrorType -- ^ Either 'payment_error', in case of an invalid payment, or 'application_error', in case of application-related errors (invalid 'application_data' in the supplied **Payment**) , errorMessage :: Text -- ^ Human-readable error message- } deriving (Show, Eq, Generic)+ } deriving (Show, Eq, Generic, Bin.Serialize) --- | A payment comprises a signature over a Bitcoin transaction with a decremented client change value. The Bitcoin transaction redeems the outpoint specified by 'funding_txid' and 'funding_vout' (a P2SH output governed by 'redeem_script'), and pays 'change_value' to 'change_address'.+-- | A payment comprises a signature over a Bitcoin transaction with a decremented client change value.+-- The Bitcoin transaction redeems the outpoint specified by 'funding_txid' and 'funding_vout' (a P2SH output governed by 'redeem_script'), and pays 'change_value' to 'change_address'. data PaymentData = PaymentData { paymentDataRedeemScript :: JsonHex Script -- ^ The funds sent to the funding address are bound by this contract (Bitcoin script). The data is needed to construct the payment signature. Hex-encoded data. , paymentDataFundingTxid :: TxHash -- ^ The transaction ID of the Bitcoin transaction paying to the channel funding address.@@ -104,7 +149,7 @@ , paymentDataChangeValue :: Word64 -- ^ The value sent back to the client in the payment transaction. The total amount transferred to the server is this amount subtracted from the value sent to the channel funding address. , paymentDataChangeAddress :: Address -- ^ The client change address as used in the only output of the payment transaction. , paymentDataSighashFlag :: JsonHex SigHash -- ^ Specifies which parts of the payment Bitcoin transaction are signed. Hex-encoded, single byte; in both v1 and v2 always equal to \"83\" (0x83), which is **SIGHASH_SINGLE|ANYONECANPAY**, meaning the client only signs its own output, and also allowing more to be added.- } deriving (Show, Eq, Generic)+ } deriving (Show, Eq, Generic, Bin.Serialize) instance FromJSON PaymentData where parseJSON = genericParseJSON (removeFieldLabelPrefix True "paymentData")@@ -116,18 +161,13 @@ data Payment = Payment { paymentPaymentData :: PaymentData -- ^ Actual payment , paymentApplicationData :: Text -- ^ Optional application data (may be an empty string). The client may wish to include data with the payment, for example an order reference, or any other data which will be used by the server to deliver the appropriate application data in the **PaymentResult** response.- } deriving (Show, Eq, Generic)+ } deriving (Show, Eq, Generic, Bin.Serialize) instance FromJSON Payment where parseJSON = genericParseJSON (removeFieldLabelPrefix True "payment") instance ToJSON Payment where toJSON = genericToJSON (removeFieldLabelPrefix False "payment") --- |-data ChannelLocation = ChannelLocation- { channelInfo_channel_uri :: Text -- ^ The URL of the resource which must the POSTed to in order to open a new payment channel, after which further payments can be PUT on this resource. Close the payment channel by issuing a DELETE request on the resource.- } deriving (Show, Eq, Generic)- instance FromJSON Error where parseJSON = genericParseJSON (removeFieldLabelPrefix True "error") instance ToJSON Error where@@ -153,9 +193,3 @@ specialChars = [("@", "'At"), ("!", "'Exclamation"), ("<=", "'Less_Than_Or_Equal_To"), ("#", "'Hash"), ("$", "'Dollar"), ("%", "'Percent"), ("&", "'Ampersand"), ("*", "'Star"), ("+", "'Plus"), ("-", "'Dash"), (".", "'Period"), (":", "'Colon"), ("|", "'Pipe"), ("<", "'LessThan"), ("!=", "'Not_Equal"), ("=", "'Equal"), ("^", "'Caret"), (">", "'GreaterThan"), ("_", "'Underscore"), (">=", "'Greater_Than_Or_Equal_To")] mkCharReplacement (replaceStr, searchStr) = T.unpack . replacer (T.pack searchStr) (T.pack replaceStr) . T.pack replacer = if forParsing then flip T.replace else T.replace--instance FromJSON ChannelLocation where- parseJSON = genericParseJSON (removeFieldLabelPrefix True "channelInfo_")-instance ToJSON ChannelLocation where- toJSON = genericToJSON (removeFieldLabelPrefix False "channelInfo_")-