packages feed

rbpcp-api (empty) → 0.1.0.0

raw patch · 12 files changed

+508/−0 lines, 12 filesdep +aesondep +basedep +base16-bytestringsetup-changed

Dependencies added: aeson, base, base16-bytestring, bytestring, cereal, haskoin-core, servant, string-conversions, text

Files

+ LICENSE view
@@ -0,0 +1,119 @@+     CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT+     PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT+     CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES+     THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO+     WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION+     OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES+     RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR+     WORKS PROVIDED HEREUNDER.++Statement of Purpose++The laws of most jurisdictions throughout the world automatically+confer exclusive Copyright and Related Rights (defined below) upon the+creator and subsequent owner(s) (each and all, an "owner") of an+original work of authorship and/or a database (each, a "Work").++Certain owners wish to permanently relinquish those rights to a Work+for the purpose of contributing to a commons of creative, cultural and+scientific works ("Commons") that the public can reliably and without+fear of later claims of infringement build upon, modify, incorporate+in other works, reuse and redistribute as freely as possible in any+form whatsoever and for any purposes, including without limitation+commercial purposes. These owners may contribute to the Commons to+promote the ideal of a free culture and the further production of+creative, cultural and scientific works, or to gain reputation or+greater distribution for their Work in part through the use and+efforts of others.++For these and/or other purposes and motivations, and without any+expectation of additional consideration or compensation, the person+associating CC0 with a Work (the "Affirmer"), to the extent that he or+she is an owner of Copyright and Related Rights in the Work,+voluntarily elects to apply CC0 to the Work and publicly distribute+the Work under its terms, with knowledge of his or her Copyright and+Related Rights in the Work and the meaning and intended legal effect+of CC0 on those rights.++1. Copyright and Related Rights. A Work made available under CC0 may+be protected by copyright and related or neighboring rights+("Copyright and Related Rights"). Copyright and Related Rights+include, but are not limited to, the following:++    the right to reproduce, adapt, distribute, perform, display,+    communicate, and translate a Work; moral rights retained by the+    original author(s) and/or performer(s); publicity and privacy+    rights pertaining to a person's image or likeness depicted in a+    Work; rights protecting against unfair competition in regards to a+    Work, subject to the limitations in paragraph 4(a), below; rights+    protecting the extraction, dissemination, use and reuse of data in+    a Work; database rights (such as those arising under Directive+    96/9/EC of the European Parliament and of the Council of 11 March+    1996 on the legal protection of databases, and under any national+    implementation thereof, including any amended or successor version+    of such directive); and other similar, equivalent or corresponding+    rights throughout the world based on applicable law or treaty, and+    any national implementations thereof.++2. Waiver. To the greatest extent permitted by, but not in+contravention of, applicable law, Affirmer hereby overtly, fully,+permanently, irrevocably and unconditionally waives, abandons, and+surrenders all of Affirmer's Copyright and Related Rights and+associated claims and causes of action, whether now known or unknown+(including existing as well as future claims and causes of action), in+the Work (i) in all territories worldwide, (ii) for the maximum+duration provided by applicable law or treaty (including future time+extensions), (iii) in any current or future medium and for any number+of copies, and (iv) for any purpose whatsoever, including without+limitation commercial, advertising or promotional purposes (the+"Waiver"). Affirmer makes the Waiver for the benefit of each member of+the public at large and to the detriment of Affirmer's heirs and+successors, fully intending that such Waiver shall not be subject to+revocation, rescission, cancellation, termination, or any other legal+or equitable action to disrupt the quiet enjoyment of the Work by the+public as contemplated by Affirmer's express Statement of Purpose.++3. Public License Fallback. Should any part of the Waiver for any+reason be judged legally invalid or ineffective under applicable law,+then the Waiver shall be preserved to the maximum extent permitted+taking into account Affirmer's express Statement of Purpose. In+addition, to the extent the Waiver is so judged Affirmer hereby grants+to each affected person a royalty-free, non transferable, non+sublicensable, non exclusive, irrevocable and unconditional license to+exercise Affirmer's Copyright and Related Rights in the Work (i) in+all territories worldwide, (ii) for the maximum duration provided by+applicable law or treaty (including future time extensions), (iii) in+any current or future medium and for any number of copies, and (iv)+for any purpose whatsoever, including without limitation commercial,+advertising or promotional purposes (the "License"). The License shall+be deemed effective as of the date CC0 was applied by Affirmer to the+Work. Should any part of the License for any reason be judged legally+invalid or ineffective under applicable law, such partial invalidity+or ineffectiveness shall not invalidate the remainder of the License,+and in such case Affirmer hereby affirms that he or she will not (i)+exercise any of his or her remaining Copyright and Related Rights in+the Work or (ii) assert any associated claims and causes of action+with respect to the Work, in either case contrary to Affirmer's+express Statement of Purpose.++4. Limitations and Disclaimers.++    No trademark or patent rights held by Affirmer are waived,+    abandoned, surrendered, licensed or otherwise affected by this+    document.  Affirmer offers the Work as-is and makes no+    representations or warranties of any kind concerning the Work,+    express, implied, statutory or otherwise, including without+    limitation warranties of title, merchantability, fitness for a+    particular purpose, non infringement, or the absence of latent or+    other defects, accuracy, or the present or absence of errors,+    whether or not discoverable, all to the greatest extent+    permissible under applicable law.  Affirmer disclaims+    responsibility for clearing rights of other persons that may apply+    to the Work or any use thereof, including without limitation any+    person's Copyright and Related Rights in the Work. Further,+    Affirmer disclaims responsibility for obtaining any necessary+    consents, permissions or other rights required for any use of the+    Work.  Affirmer understands and acknowledges that Creative Commons+    is not a party to this document and has no duty or obligation with+    respect to this CC0 or use of the Work.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ rbpcp-api.cabal view
@@ -0,0 +1,51 @@+-- This file has been generated from package.yaml by hpack version 0.14.0.+--+-- see: https://github.com/sol/hpack++name:           rbpcp-api+version:        0.1.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.+                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+author:         Rune K. Svendsen+maintainer:     runesvend@gmail.com+license:        OtherLicense+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10++source-repository head+  type: git+  location: https://github.com/runeksvendsen/haskell-rbpcp-api++library+  hs-source-dirs:+      src+  default-extensions: MultiParamTypeClasses FlexibleInstances+  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+  exposed-modules:+      RBPCP+      RBPCP.Types+      RBPCP.Api+      RBPCP.Callback+      RBPCP.Callback.Types+      RBPCP.Callback.Api+  other-modules:+      RBPCP.Internal.Orphans+      RBPCP.Internal.Types+      RBPCP.Internal.Util+      Paths_rbpcp_api+  default-language: Haskell2010
+ src/RBPCP.hs view
@@ -0,0 +1,10 @@+module RBPCP+(+    module RBPCP.Api+  , module RBPCP.Types+) where++import RBPCP.Api+import RBPCP.Types++
+ src/RBPCP/Api.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE DataKinds, LambdaCase, TypeOperators, OverloadedStrings, FlexibleInstances, MultiParamTypeClasses #-}++module RBPCP.Api where++import           RBPCP.Types+import           RBPCP.Internal.Types+import           Servant.API+++type VER = "v2"++-- 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++-- Example: /channels/028adc96575e3ee23a69eb17723911e77c5c06320e4354b1518bb635f32793c910/1474949961/53ee3615ac0dd479ec1d3e144eb651f65764d3a5e400c04cf3c79425e8b22fb0/2+type ChanOpen  = VER :> "channels" :> Capture "client_pubkey" (Client PubKey) :> Capture "exp_time"     BLT+                                   :> Capture "funding_txid"  TxHash     :> Capture "funding_vout" Vout+                                   :> ReqBody '[JSON] Payment            :> Verb 'POST 201 '[JSON] PaymentResult++type ChanPay   = VER :> "channels" :> Capture "client_pubkey" (Client PubKey) :> Capture "exp_time" BLT+                                   :> Capture "funding_txid"  TxHash     :> Capture "funding_vout" Vout+                                   :> ReqBody '[JSON]         Payment    :> Put '[JSON] PaymentResult++type ChanClose = VER :> "channels" :> Capture "client_pubkey" (Client PubKey) :> Capture "exp_time" BLT+                                   :> Capture "funding_txid" TxHash      :> Capture "funding_vout" Vout     :> "close"+                                   :> ReqBody '[JSON] Payment            :> Put '[JSON] PaymentResult+++-- | RESTful Bitcoin payment channel protocol+type RBPCP =+       FundInfo+  :<|> BeginOpen+  :<|> ChanOpen+  :<|> ChanPay+  :<|> ChanClose+++-- Short-hands+type BLT = Word32
+ src/RBPCP/Callback.hs view
@@ -0,0 +1,9 @@+module RBPCP.Callback+(+    module RBPCP.Callback.Api+  , module RBPCP.Callback.Types+)+where++import RBPCP.Callback.Api+import RBPCP.Callback.Types
+ src/RBPCP/Callback/Api.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE DataKinds, LambdaCase, TypeOperators, OverloadedStrings, FlexibleInstances, MultiParamTypeClasses #-}++module RBPCP.Callback.Api where++import           RBPCP.Callback.Types+import           Servant.API+++-- |A server implementing this API can be contacted by this server,+--  and receive information about all incoming payments.+--  The response delivered to this request can contain arbitrary application data,+--  which will be included in the PaymentResult response delivered to the paying client.+type PaymentCallback =+    "value_received" :> ReqBody '[JSON] CallbackInfo :> Post '[JSON] CallbackResponse++
+ src/RBPCP/Callback/Types.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}+module RBPCP.Callback.Types where++import           RBPCP.Types+import           RBPCP.Internal.Types+import qualified Data.Text as T+import           Data.Aeson (FromJSON, ToJSON)+import           GHC.Generics+++data CallbackInfo = CallbackInfo+  { value_received      :: Word64+  , chan_value_left     :: Word64+  , chan_total_capacity :: Word64+  , full_payment        :: Payment+  } deriving (Generic, FromJSON, ToJSON)++data CallbackResponse = CallbackResponse+  { resp_app_data       :: T.Text+  , resp_app_error      :: Maybe T.Text+  } deriving (Generic, FromJSON, ToJSON)
+ src/RBPCP/Internal/Orphans.hs view
@@ -0,0 +1,12 @@+module RBPCP.Internal.Orphans where++import RBPCP.Internal.Types+import RBPCP.Internal.Util+import Data.Aeson (Value(..), FromJSON(..), ToJSON(..), withText, genericToJSON, genericParseJSON)+++instance ToJSON Script where+    toJSON = String . cs . hexEncode++instance FromJSON Script where+    parseJSON = withText "BitcoinScript" (either fail return . hexDecode . cs)
+ src/RBPCP/Internal/Types.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}+module RBPCP.Internal.Types+(+    module RBPCP.Internal.Types+  , module Network.Haskoin.Transaction+  , module Network.Haskoin.Crypto+  , module Network.Haskoin.Script+  , module Data.Word+  , ByteString+  , Generic+)+where++import RBPCP.Internal.Util++import Network.Haskoin.Transaction+import Network.Haskoin.Crypto hiding (PubKey)+import Network.Haskoin.Script+import Data.ByteString      (ByteString)+import Data.Word            (Word32, Word64)+import           GHC.Generics (Generic)+import Data.Aeson+import qualified Data.Serialize as Bin+++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+    toJSON = String . cs . hexEncode . fromHex+instance (Eq a, Show a, 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)++-- | Wraps any server-related datatype (eg. pubkey, signature)+data Server a = Server a deriving (Eq, Show, Generic, Bin.Serialize, ToJSON, FromJSON)+
+ src/RBPCP/Internal/Util.hs view
@@ -0,0 +1,21 @@+module RBPCP.Internal.Util+(+    cs+  , (<>)+  , hexEncode, hexDecode+)+where++import           Data.String.Conversions (cs)+import           Data.Monoid ((<>))++import qualified Data.ByteString as BS+import qualified Data.Serialize as Bin+import qualified Data.ByteString.Base16 as B16+++hexEncode :: Bin.Serialize a => a -> BS.ByteString+hexEncode = B16.encode . Bin.encode++hexDecode :: Bin.Serialize a => BS.ByteString -> Either String a+hexDecode = Bin.decode . fst . B16.decode
+ src/RBPCP/Types.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}++module RBPCP.Types+(+  module RBPCP.Types+, JsonHex(..)+)+where++import           RBPCP.Internal.Types+import           RBPCP.Internal.Util+import           RBPCP.Internal.Orphans     ()+import           Data.Aeson+import           Data.Aeson.Types+import           Data.Word                  (Word8, Word32, Word64)+import qualified Data.Serialize             as Bin++-- Generated+import Data.List (stripPrefix)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import qualified Data.Text as T+import           Data.Function ((&))++++type Vout  = Word32+type Hours = Word+type BtcConf = Word+++data ChannelStatus = ChannelOpen | ChannelClosed deriving (Show, Eq)+instance FromJSON ChannelStatus where+    parseJSON = withText "ChannelStatus" $+            \s -> case s of+                "open"   -> return ChannelOpen+                "closed" -> return ChannelClosed+                e        -> fail $ "expected \"open\" or \"closed\", not: " ++ show (cs e :: String)+instance ToJSON ChannelStatus where+  toJSON ChannelOpen = String "open"+  toJSON ChannelClosed = String "closed"+instance Bin.Serialize ChannelStatus where+    put ChannelOpen = Bin.putWord8 0x01+    put ChannelClosed = Bin.putWord8 0x02+    get = Bin.getWord8 >>= \w -> case w of+            0x01 -> return ChannelOpen+            0x02 -> return ChannelClosed+            n    -> fail $ "expected 1 or 2, not: " ++ show n++data ErrorType = PaymentError | ApplicationError deriving (Show, Eq)+instance FromJSON ErrorType where+    parseJSON = withText "ErrorType" $+            \s -> case s of+                "payment_error"   -> return PaymentError+                "application_error" -> return ApplicationError+                e        -> fail $ "expected \"payment_error\"" +++                                   " or \"application_error\", not: " ++ show (cs e :: String)+instance ToJSON ErrorType where+  toJSON PaymentError = String "payment_error"+  toJSON ApplicationError = String "application_error"+instance Bin.Serialize ErrorType where+    put PaymentError = Bin.putWord8 0x01+    put ApplicationError = Bin.putWord8 0x02+    get = Bin.getWord8 >>= \w -> case w of+            0x01 -> return PaymentError+            0x02 -> return ApplicationError+            n    -> fail $ "expected 0x01 or 0x02, not: " ++ show n+++-- Generated code with 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)++-- |+data FundingInfo = FundingInfo+    { fundingInfoServerPubkey               :: Server PubKey    -- ^ Server/value receiver public key. Hex-encoded, compressed Secp256k1 pubkey, 33 bytes.+    , fundingInfoDustLimit                  :: Word64  -- ^ (Satoshis) The server will not accept payments where the client change amount is less than this amount. This \"dust limit\" is necessary in order to avoid producing a settlement transaction that will not circulate in the Bitcoin P2P network because it contains an output of minuscule value. Consequently, the maximum amount, that can be sent over the payment channel, is the amount sent to the funding address minus this \"dust limit\".+    , fundingInfoFundingAddressCopy         :: Address    -- ^ Server derived channel funding address. The client will confirm that its own derived funding address matches this one, before paying to it.+    , fundingInfoRedeem_scriptCopy          :: JsonHex Script     -- ^ Server derived channel redeem script. Defines sender, receiver and channel expiration date. Used to construct the input in the payment transaction. The client will also verify that this matches what it expects. Hex-encoded.+    , fundingInfoOpenPrice                  :: Word64 -- ^ Price (in satoshis) for opening a channel with the given {exp_time}. This amount is paid in the initial channel payment when creating a new channel. May be zero, in which case a payment of zero value is transferred, ensuring that the channel can be closed at any time.+    , 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)++-- | 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)++-- | A payment comprises a signature over a Bitcoin transaction with a decremented client change value. The Bitcoin transaction redeems the outpoint specified by &#39;funding_txid&#39; and &#39;funding_vout&#39; (a P2SH output governed by &#39;redeem_script&#39;), and pays &#39;change_value&#39; to &#39;change_address&#39;.+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.+    , paymentDataFundingVout    :: Vout                 -- ^ The output index/\"vout\" of the output (in the transaction) payingto the channel funding address.+    , paymentDataSignatureData  :: JsonHex Signature    -- ^ DER-encoded ECDSA signature (in hex). This is a SIGHASH_SINGLE|ANYONECANPAY signature over the the \"payment transaction\", which is a Bitcoin transaction that: redeems the outpoint specified by 'funding_txid' and 'funding_vout' using the redeem script defined in 'redeem_script', with an output which sends 'change_value' to 'change_address'.+    , 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)++instance FromJSON PaymentData where+  parseJSON  = genericParseJSON  (removeFieldLabelPrefix True "paymentData")+instance ToJSON PaymentData where+  toJSON     = genericToJSON     (removeFieldLabelPrefix False "paymentData")+++-- | A wrapper that contains both payment data and application data+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)++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+  toJSON     = genericToJSON     (removeFieldLabelPrefix False "error")++instance FromJSON FundingInfo where+  parseJSON  = genericParseJSON  (removeFieldLabelPrefix True "fundingInfo")+instance ToJSON FundingInfo where+  toJSON     = genericToJSON     (removeFieldLabelPrefix False "fundingInfo")++instance FromJSON PaymentResult where+  parseJSON  = genericParseJSON  (removeFieldLabelPrefix True "paymentResult_")+instance ToJSON PaymentResult where+  toJSON     = genericToJSON     (removeFieldLabelPrefix False "paymentResult_")++removeFieldLabelPrefix :: Bool -> String -> Options+removeFieldLabelPrefix forParsing prefix =+  defaultOptions+    { fieldLabelModifier = camelTo2 '_' . fromMaybe (error ("did not find prefix " ++ prefix)) . stripPrefix prefix . replaceSpecialChars+    }+  where+    replaceSpecialChars field = foldl (&) field (map mkCharReplacement specialChars)+    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_")+