packages feed

haskoin-wallet 0.4.2 → 0.9.4

raw patch · 49 files changed

+8179/−8052 lines, 49 filesdep +Decimaldep +ansi-terminaldep +base16-bytestringdep −daemonsdep −deepseqdep −exceptionsdep ~HUnitdep ~QuickCheckdep ~aesonnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: Decimal, ansi-terminal, base16-bytestring, base64-bytestring, entropy, haskoin-store-data, hspec, http-types, lens, lens-aeson, optparse-applicative, pretty, random, raw-strings-qq, secp256k1-haskell, transformers, wreq

Dependencies removed: daemons, deepseq, exceptions, file-embed, filepath, haskoin-node, lifted-async, lifted-base, monad-control, persistent-template, resourcet, semigroups, stm, stm-chans, stm-conduit, test-framework, test-framework-hunit, test-framework-quickcheck2, transformers-base, unix, yaml, zeromq4-haskell

Dependency ranges changed: HUnit, QuickCheck, aeson, aeson-pretty, base, bytestring, cereal, conduit, containers, data-default, directory, esqueleto, haskeline, haskoin-core, monad-logger, mtl, persistent, persistent-sqlite, split, string-conversions, text, time, unordered-containers

API changes (from Hackage documentation)

- Network.Haskoin.Wallet: AccountMultisig :: !Int -> !Int -> AccountType
- Network.Haskoin.Wallet: AccountRegular :: AccountType
- Network.Haskoin.Wallet: AddressExternal :: AddressType
- Network.Haskoin.Wallet: AddressInfo :: !Address -> !(Maybe Word64) -> !Bool -> AddressInfo
- Network.Haskoin.Wallet: AddressInternal :: AddressType
- Network.Haskoin.Wallet: AddressLabel :: !Text -> AddressLabel
- Network.Haskoin.Wallet: BalanceInfo :: !Word64 -> !Word64 -> !Int -> !Int -> BalanceInfo
- Network.Haskoin.Wallet: CoinSignData :: !OutPoint -> !ScriptOutput -> !SoftPath -> CoinSignData
- Network.Haskoin.Wallet: Config :: !Word32 -> !Word32 -> !Bool -> !Word64 -> !Bool -> !AddressType -> !Bool -> !Bool -> !(Maybe HardPath) -> !OutputFormat -> !String -> !String -> !Bool -> !FilePath -> !Bool -> !FilePath -> !String -> !String -> !(HashMap Text [BTCNode]) -> !SPVMode -> !Double -> !(HashMap Text DatabaseConfType) -> !FilePath -> !FilePath -> !LogLevel -> !Bool -> !(Maybe (Restricted Div5 ByteString)) -> !(Maybe (Restricted Div5 ByteString)) -> !(Maybe (Restricted Div5 ByteString)) -> !(Maybe (Restricted Div5 ByteString)) -> Config
- Network.Haskoin.Wallet: CreateTx :: ![(Address, Word64)] -> !Word64 -> !Word32 -> !Bool -> !Bool -> TxAction
- Network.Haskoin.Wallet: DeleteTxIdR :: !TxHash -> WalletRequest
- Network.Haskoin.Wallet: GetAccountR :: !AccountName -> WalletRequest
- Network.Haskoin.Wallet: GetAccountsR :: !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetAddrTxsR :: !AccountName -> !KeyIndex -> !AddressType -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetAddressR :: !AccountName -> !KeyIndex -> !AddressType -> !Word32 -> !Bool -> WalletRequest
- Network.Haskoin.Wallet: GetAddressesR :: !AccountName -> !AddressType -> !Word32 -> !Bool -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetAddressesUnusedR :: !AccountName -> !AddressType -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetBalanceR :: !AccountName -> !Word32 -> !Bool -> WalletRequest
- Network.Haskoin.Wallet: GetBlockInfoR :: ![BlockHash] -> WalletRequest
- Network.Haskoin.Wallet: GetDeadR :: !AccountName -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetIndexR :: !AccountName -> !PubKeyC -> !AddressType -> WalletRequest
- Network.Haskoin.Wallet: GetOfflineTxR :: !AccountName -> !TxHash -> WalletRequest
- Network.Haskoin.Wallet: GetPendingR :: !AccountName -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetSyncHeightR :: !AccountName -> !BlockHeight -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetSyncR :: !AccountName -> !BlockHash -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: GetTxR :: !AccountName -> !TxHash -> WalletRequest
- Network.Haskoin.Wallet: GetTxsR :: !AccountName -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet: ImportTx :: !Tx -> TxAction
- Network.Haskoin.Wallet: JsonAccount :: !Text -> !AccountType -> !(Maybe XPrvKey) -> !(Maybe Text) -> !(Maybe HardPath) -> ![XPubKey] -> !Word32 -> !UTCTime -> JsonAccount
- Network.Haskoin.Wallet: JsonAddr :: !Address -> !KeyIndex -> !AddressType -> !Text -> !(Maybe ScriptOutput) -> !(Maybe PubKeyC) -> !UTCTime -> !(Maybe BalanceInfo) -> JsonAddr
- Network.Haskoin.Wallet: JsonCoin :: !TxHash -> !Word32 -> !Word64 -> !ScriptOutput -> !UTCTime -> !(Maybe JsonTx) -> !(Maybe JsonAddr) -> !(Maybe JsonTx) -> JsonCoin
- Network.Haskoin.Wallet: JsonTx :: !TxHash -> !TxHash -> !TxType -> !Word64 -> !Word64 -> !Int64 -> ![AddressInfo] -> ![AddressInfo] -> ![AddressInfo] -> !Tx -> !Bool -> !TxConfidence -> !(Maybe BlockHash) -> !(Maybe Word32) -> !(Maybe Word32) -> !UTCTime -> !AccountName -> !(Maybe Word32) -> !(Maybe BlockHash) -> !(Maybe BlockHeight) -> JsonTx
- Network.Haskoin.Wallet: ListRequest :: !Word32 -> !Word32 -> !Bool -> ListRequest
- Network.Haskoin.Wallet: ListResult :: ![a] -> !Word32 -> ListResult a
- Network.Haskoin.Wallet: NewAccount :: !AccountName -> !AccountType -> !(Maybe Text) -> !(Maybe XPrvKey) -> !(Maybe HardPath) -> ![XPubKey] -> !Bool -> NewAccount
- Network.Haskoin.Wallet: NodeActionRescan :: !(Maybe Word32) -> NodeAction
- Network.Haskoin.Wallet: NodeActionStatus :: NodeAction
- Network.Haskoin.Wallet: OfflineTxData :: !Tx -> ![CoinSignData] -> OfflineTxData
- Network.Haskoin.Wallet: OutputJSON :: OutputFormat
- Network.Haskoin.Wallet: OutputNormal :: OutputFormat
- Network.Haskoin.Wallet: OutputYAML :: OutputFormat
- Network.Haskoin.Wallet: PostAccountGapR :: !AccountName -> !SetAccountGap -> WalletRequest
- Network.Haskoin.Wallet: PostAccountKeysR :: !AccountName -> ![XPubKey] -> WalletRequest
- Network.Haskoin.Wallet: PostAccountRenameR :: !AccountName -> !AccountName -> WalletRequest
- Network.Haskoin.Wallet: PostAccountsR :: !NewAccount -> WalletRequest
- Network.Haskoin.Wallet: PostAddressesR :: !AccountName -> !KeyIndex -> !AddressType -> WalletRequest
- Network.Haskoin.Wallet: PostNodeR :: !NodeAction -> WalletRequest
- Network.Haskoin.Wallet: PostOfflineTxR :: !AccountName -> !(Maybe XPrvKey) -> !Tx -> ![CoinSignData] -> WalletRequest
- Network.Haskoin.Wallet: PostTxsR :: !AccountName -> !(Maybe XPrvKey) -> !TxAction -> WalletRequest
- Network.Haskoin.Wallet: PutAddressR :: !AccountName -> !KeyIndex -> !AddressType -> !AddressLabel -> WalletRequest
- Network.Haskoin.Wallet: RescanRes :: !Word32 -> RescanRes
- Network.Haskoin.Wallet: ResponseError :: !Text -> WalletResponse a
- Network.Haskoin.Wallet: ResponseValid :: !(Maybe a) -> WalletResponse a
- Network.Haskoin.Wallet: SPVOffline :: SPVMode
- Network.Haskoin.Wallet: SPVOnline :: SPVMode
- Network.Haskoin.Wallet: SetAccountGap :: !Word32 -> SetAccountGap
- Network.Haskoin.Wallet: SignTx :: !TxHash -> TxAction
- Network.Haskoin.Wallet: StopServerR :: WalletRequest
- Network.Haskoin.Wallet: TxBuilding :: TxConfidence
- Network.Haskoin.Wallet: TxCompleteRes :: !Tx -> !Bool -> TxCompleteRes
- Network.Haskoin.Wallet: TxDead :: TxConfidence
- Network.Haskoin.Wallet: TxIncoming :: TxType
- Network.Haskoin.Wallet: TxOffline :: TxConfidence
- Network.Haskoin.Wallet: TxOutgoing :: TxType
- Network.Haskoin.Wallet: TxPending :: TxConfidence
- Network.Haskoin.Wallet: TxSelf :: TxType
- Network.Haskoin.Wallet: [accTxActionFee] :: TxAction -> !Word64
- Network.Haskoin.Wallet: [accTxActionHash] :: TxAction -> !TxHash
- Network.Haskoin.Wallet: [accTxActionMinConf] :: TxAction -> !Word32
- Network.Haskoin.Wallet: [accTxActionRcptFee] :: TxAction -> !Bool
- Network.Haskoin.Wallet: [accTxActionRecipients] :: TxAction -> ![(Address, Word64)]
- Network.Haskoin.Wallet: [accTxActionSign] :: TxAction -> !Bool
- Network.Haskoin.Wallet: [accTxActionTx] :: TxAction -> !Tx
- Network.Haskoin.Wallet: [accountTypeRequiredSigs] :: AccountType -> !Int
- Network.Haskoin.Wallet: [accountTypeTotalKeys] :: AccountType -> !Int
- Network.Haskoin.Wallet: [addressInfoAddress] :: AddressInfo -> !Address
- Network.Haskoin.Wallet: [addressInfoIsLocal] :: AddressInfo -> !Bool
- Network.Haskoin.Wallet: [addressInfoValue] :: AddressInfo -> !(Maybe Word64)
- Network.Haskoin.Wallet: [addressLabelLabel] :: AddressLabel -> !Text
- Network.Haskoin.Wallet: [balanceInfoCoins] :: BalanceInfo -> !Int
- Network.Haskoin.Wallet: [balanceInfoInBalance] :: BalanceInfo -> !Word64
- Network.Haskoin.Wallet: [balanceInfoOutBalance] :: BalanceInfo -> !Word64
- Network.Haskoin.Wallet: [balanceInfoSpentCoins] :: BalanceInfo -> !Int
- Network.Haskoin.Wallet: [coinSignDeriv] :: CoinSignData -> !SoftPath
- Network.Haskoin.Wallet: [coinSignOutPoint] :: CoinSignData -> !OutPoint
- Network.Haskoin.Wallet: [coinSignScriptOutput] :: CoinSignData -> !ScriptOutput
- Network.Haskoin.Wallet: [configAddrType] :: Config -> !AddressType
- Network.Haskoin.Wallet: [configBTCNodes] :: Config -> !(HashMap Text [BTCNode])
- Network.Haskoin.Wallet: [configBindNotif] :: Config -> !String
- Network.Haskoin.Wallet: [configBind] :: Config -> !String
- Network.Haskoin.Wallet: [configBloomFP] :: Config -> !Double
- Network.Haskoin.Wallet: [configClientKeyPub] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet: [configClientKey] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet: [configConnectNotif] :: Config -> !String
- Network.Haskoin.Wallet: [configConnect] :: Config -> !String
- Network.Haskoin.Wallet: [configCount] :: Config -> !Word32
- Network.Haskoin.Wallet: [configDatabase] :: Config -> !(HashMap Text DatabaseConfType)
- Network.Haskoin.Wallet: [configDetach] :: Config -> !Bool
- Network.Haskoin.Wallet: [configDir] :: Config -> !FilePath
- Network.Haskoin.Wallet: [configFee] :: Config -> !Word64
- Network.Haskoin.Wallet: [configFile] :: Config -> !FilePath
- Network.Haskoin.Wallet: [configFormat] :: Config -> !OutputFormat
- Network.Haskoin.Wallet: [configLogFile] :: Config -> !FilePath
- Network.Haskoin.Wallet: [configLogLevel] :: Config -> !LogLevel
- Network.Haskoin.Wallet: [configMinConf] :: Config -> !Word32
- Network.Haskoin.Wallet: [configMode] :: Config -> !SPVMode
- Network.Haskoin.Wallet: [configOffline] :: Config -> !Bool
- Network.Haskoin.Wallet: [configPath] :: Config -> !(Maybe HardPath)
- Network.Haskoin.Wallet: [configPidFile] :: Config -> !FilePath
- Network.Haskoin.Wallet: [configRcptFee] :: Config -> !Bool
- Network.Haskoin.Wallet: [configReversePaging] :: Config -> !Bool
- Network.Haskoin.Wallet: [configServerKeyPub] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet: [configServerKey] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet: [configSignTx] :: Config -> !Bool
- Network.Haskoin.Wallet: [configTestnet] :: Config -> !Bool
- Network.Haskoin.Wallet: [configVerbose] :: Config -> !Bool
- Network.Haskoin.Wallet: [getAccountGap] :: SetAccountGap -> !Word32
- Network.Haskoin.Wallet: [jsonAccountCreated] :: JsonAccount -> !UTCTime
- Network.Haskoin.Wallet: [jsonAccountDerivation] :: JsonAccount -> !(Maybe HardPath)
- Network.Haskoin.Wallet: [jsonAccountGap] :: JsonAccount -> !Word32
- Network.Haskoin.Wallet: [jsonAccountKeys] :: JsonAccount -> ![XPubKey]
- Network.Haskoin.Wallet: [jsonAccountMaster] :: JsonAccount -> !(Maybe XPrvKey)
- Network.Haskoin.Wallet: [jsonAccountMnemonic] :: JsonAccount -> !(Maybe Text)
- Network.Haskoin.Wallet: [jsonAccountName] :: JsonAccount -> !Text
- Network.Haskoin.Wallet: [jsonAccountType] :: JsonAccount -> !AccountType
- Network.Haskoin.Wallet: [jsonAddrAddress] :: JsonAddr -> !Address
- Network.Haskoin.Wallet: [jsonAddrBalance] :: JsonAddr -> !(Maybe BalanceInfo)
- Network.Haskoin.Wallet: [jsonAddrCreated] :: JsonAddr -> !UTCTime
- Network.Haskoin.Wallet: [jsonAddrIndex] :: JsonAddr -> !KeyIndex
- Network.Haskoin.Wallet: [jsonAddrKey] :: JsonAddr -> !(Maybe PubKeyC)
- Network.Haskoin.Wallet: [jsonAddrLabel] :: JsonAddr -> !Text
- Network.Haskoin.Wallet: [jsonAddrRedeem] :: JsonAddr -> !(Maybe ScriptOutput)
- Network.Haskoin.Wallet: [jsonAddrType] :: JsonAddr -> !AddressType
- Network.Haskoin.Wallet: [jsonCoinAddress] :: JsonCoin -> !(Maybe JsonAddr)
- Network.Haskoin.Wallet: [jsonCoinCreated] :: JsonCoin -> !UTCTime
- Network.Haskoin.Wallet: [jsonCoinHash] :: JsonCoin -> !TxHash
- Network.Haskoin.Wallet: [jsonCoinPos] :: JsonCoin -> !Word32
- Network.Haskoin.Wallet: [jsonCoinScript] :: JsonCoin -> !ScriptOutput
- Network.Haskoin.Wallet: [jsonCoinSpendingTx] :: JsonCoin -> !(Maybe JsonTx)
- Network.Haskoin.Wallet: [jsonCoinTx] :: JsonCoin -> !(Maybe JsonTx)
- Network.Haskoin.Wallet: [jsonCoinValue] :: JsonCoin -> !Word64
- Network.Haskoin.Wallet: [jsonTxAccount] :: JsonTx -> !AccountName
- Network.Haskoin.Wallet: [jsonTxBestBlockHeight] :: JsonTx -> !(Maybe BlockHeight)
- Network.Haskoin.Wallet: [jsonTxBestBlock] :: JsonTx -> !(Maybe BlockHash)
- Network.Haskoin.Wallet: [jsonTxChange] :: JsonTx -> ![AddressInfo]
- Network.Haskoin.Wallet: [jsonTxConfidence] :: JsonTx -> !TxConfidence
- Network.Haskoin.Wallet: [jsonTxConfirmations] :: JsonTx -> !(Maybe Word32)
- Network.Haskoin.Wallet: [jsonTxConfirmedBy] :: JsonTx -> !(Maybe BlockHash)
- Network.Haskoin.Wallet: [jsonTxConfirmedDate] :: JsonTx -> !(Maybe Word32)
- Network.Haskoin.Wallet: [jsonTxConfirmedHeight] :: JsonTx -> !(Maybe Word32)
- Network.Haskoin.Wallet: [jsonTxCreated] :: JsonTx -> !UTCTime
- Network.Haskoin.Wallet: [jsonTxHash] :: JsonTx -> !TxHash
- Network.Haskoin.Wallet: [jsonTxInValue] :: JsonTx -> !Word64
- Network.Haskoin.Wallet: [jsonTxInputs] :: JsonTx -> ![AddressInfo]
- Network.Haskoin.Wallet: [jsonTxIsCoinbase] :: JsonTx -> !Bool
- Network.Haskoin.Wallet: [jsonTxNosigHash] :: JsonTx -> !TxHash
- Network.Haskoin.Wallet: [jsonTxOutValue] :: JsonTx -> !Word64
- Network.Haskoin.Wallet: [jsonTxOutputs] :: JsonTx -> ![AddressInfo]
- Network.Haskoin.Wallet: [jsonTxTx] :: JsonTx -> !Tx
- Network.Haskoin.Wallet: [jsonTxType] :: JsonTx -> !TxType
- Network.Haskoin.Wallet: [jsonTxValue] :: JsonTx -> !Int64
- Network.Haskoin.Wallet: [listLimit] :: ListRequest -> !Word32
- Network.Haskoin.Wallet: [listOffset] :: ListRequest -> !Word32
- Network.Haskoin.Wallet: [listResultItems] :: ListResult a -> ![a]
- Network.Haskoin.Wallet: [listResultTotal] :: ListResult a -> !Word32
- Network.Haskoin.Wallet: [listReverse] :: ListRequest -> !Bool
- Network.Haskoin.Wallet: [newAccountDeriv] :: NewAccount -> !(Maybe HardPath)
- Network.Haskoin.Wallet: [newAccountKeys] :: NewAccount -> ![XPubKey]
- Network.Haskoin.Wallet: [newAccountMaster] :: NewAccount -> !(Maybe XPrvKey)
- Network.Haskoin.Wallet: [newAccountMnemonic] :: NewAccount -> !(Maybe Text)
- Network.Haskoin.Wallet: [newAccountName] :: NewAccount -> !AccountName
- Network.Haskoin.Wallet: [newAccountReadOnly] :: NewAccount -> !Bool
- Network.Haskoin.Wallet: [newAccountType] :: NewAccount -> !AccountType
- Network.Haskoin.Wallet: [nodeActionTimestamp] :: NodeAction -> !(Maybe Word32)
- Network.Haskoin.Wallet: [offlineTxDataCoinData] :: OfflineTxData -> ![CoinSignData]
- Network.Haskoin.Wallet: [offlineTxDataTx] :: OfflineTxData -> !Tx
- Network.Haskoin.Wallet: [rescanTimestamp] :: RescanRes -> !Word32
- Network.Haskoin.Wallet: [responseError] :: WalletResponse a -> !Text
- Network.Haskoin.Wallet: [responseResult] :: WalletResponse a -> !(Maybe a)
- Network.Haskoin.Wallet: [txCompleteComplete] :: TxCompleteRes -> !Bool
- Network.Haskoin.Wallet: [txCompleteTx] :: TxCompleteRes -> !Tx
- Network.Haskoin.Wallet: accountBalance :: MonadIO m => AccountId -> Word32 -> Bool -> SqlPersistT m Word64
- Network.Haskoin.Wallet: accounts :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => ListRequest -> SqlPersistT m ([Account], Word32)
- Network.Haskoin.Wallet: addAccountKeys :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> [XPubKey] -> SqlPersistT m Account
- Network.Haskoin.Wallet: addrTxs :: MonadIO m => Entity Account -> Entity WalletAddr -> ListRequest -> SqlPersistT m ([WalletTx], Word32)
- Network.Haskoin.Wallet: addrTypeIndex :: AddressType -> KeyIndex
- Network.Haskoin.Wallet: addressBalances :: MonadIO m => Entity Account -> KeyIndex -> KeyIndex -> AddressType -> Word32 -> Bool -> SqlPersistT m [(KeyIndex, BalanceInfo)]
- Network.Haskoin.Wallet: addressCount :: MonadIO m => Entity Account -> AddressType -> SqlPersistT m Word32
- Network.Haskoin.Wallet: addressList :: MonadIO m => Entity Account -> AddressType -> ListRequest -> SqlPersistT m ([WalletAddr], Word32)
- Network.Haskoin.Wallet: addressPrvKey :: (MonadIO m, MonadThrow m) => Entity Account -> Maybe XPrvKey -> KeyIndex -> AddressType -> SqlPersistT m PrvKeyC
- Network.Haskoin.Wallet: addresses :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> AddressType -> SqlPersistT m [WalletAddr]
- Network.Haskoin.Wallet: addressesAll :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => SqlPersistT m [WalletAddr]
- Network.Haskoin.Wallet: clientMain :: IO ()
- Network.Haskoin.Wallet: createWalletTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> Maybe (TBMChan Notif) -> Maybe XPrvKey -> [(Address, Word64)] -> Word64 -> Word32 -> Bool -> Bool -> SqlPersistT m (WalletTx, [WalletAddr])
- Network.Haskoin.Wallet: data AccountType
- Network.Haskoin.Wallet: data AddressInfo
- Network.Haskoin.Wallet: data AddressLabel
- Network.Haskoin.Wallet: data AddressType
- Network.Haskoin.Wallet: data BalanceInfo
- Network.Haskoin.Wallet: data CoinSignData
- Network.Haskoin.Wallet: data Config
- Network.Haskoin.Wallet: data JsonAccount
- Network.Haskoin.Wallet: data JsonAddr
- Network.Haskoin.Wallet: data JsonCoin
- Network.Haskoin.Wallet: data JsonTx
- Network.Haskoin.Wallet: data ListRequest
- Network.Haskoin.Wallet: data ListResult a
- Network.Haskoin.Wallet: data NewAccount
- Network.Haskoin.Wallet: data NodeAction
- Network.Haskoin.Wallet: data OfflineTxData
- Network.Haskoin.Wallet: data OutputFormat
- Network.Haskoin.Wallet: data RescanRes
- Network.Haskoin.Wallet: data SPVMode
- Network.Haskoin.Wallet: data SetAccountGap
- Network.Haskoin.Wallet: data TxAction
- Network.Haskoin.Wallet: data TxCompleteRes
- Network.Haskoin.Wallet: data TxConfidence
- Network.Haskoin.Wallet: data TxType
- Network.Haskoin.Wallet: data WalletRequest
- Network.Haskoin.Wallet: data WalletResponse a
- Network.Haskoin.Wallet: firstAddrTime :: MonadIO m => SqlPersistT m (Maybe Timestamp)
- Network.Haskoin.Wallet: getAccount :: (MonadIO m, MonadThrow m) => AccountName -> SqlPersistT m (Entity Account)
- Network.Haskoin.Wallet: getAccountTx :: MonadIO m => AccountId -> TxHash -> SqlPersistT m WalletTx
- Network.Haskoin.Wallet: getAddress :: (MonadIO m, MonadThrow m) => Entity Account -> AddressType -> KeyIndex -> SqlPersistT m (Entity WalletAddr)
- Network.Haskoin.Wallet: getBloomFilter :: (MonadIO m, MonadThrow m) => SqlPersistT m (BloomFilter, Int, Double)
- Network.Haskoin.Wallet: getOfflineTxData :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => AccountId -> TxHash -> SqlPersistT m (OfflineTxData, [InCoinData])
- Network.Haskoin.Wallet: getPathPubKey :: Account -> SoftPath -> PubKeyC
- Network.Haskoin.Wallet: getPathRedeem :: Account -> SoftPath -> RedeemScript
- Network.Haskoin.Wallet: getTx :: MonadIO m => TxHash -> SqlPersistT m (Maybe Tx)
- Network.Haskoin.Wallet: importMerkles :: MonadIO m => BlockChainAction -> [MerkleTxs] -> Maybe (TBMChan Notif) -> SqlPersistT m ()
- Network.Haskoin.Wallet: importNetTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Tx -> Maybe (TBMChan Notif) -> SqlPersistT m ([WalletTx], [WalletAddr])
- Network.Haskoin.Wallet: importTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Tx -> Maybe (TBMChan Notif) -> AccountId -> SqlPersistT m ([WalletTx], [WalletAddr])
- Network.Haskoin.Wallet: initWallet :: MonadIO m => Double -> SqlPersistT m ()
- Network.Haskoin.Wallet: isCompleteAccount :: Account -> Bool
- Network.Haskoin.Wallet: isMultisigAccount :: Account -> Bool
- Network.Haskoin.Wallet: isReadAccount :: Account -> Bool
- Network.Haskoin.Wallet: newAccount :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => NewAccount -> SqlPersistT m (Entity Account, Maybe Mnemonic)
- Network.Haskoin.Wallet: resetRescan :: MonadIO m => SqlPersistT m ()
- Network.Haskoin.Wallet: runSPVServer :: Config -> IO ()
- Network.Haskoin.Wallet: setAccountGap :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> Word32 -> SqlPersistT m (Entity Account)
- Network.Haskoin.Wallet: setAddrLabel :: (MonadIO m, MonadThrow m) => Entity Account -> KeyIndex -> AddressType -> Text -> SqlPersistT m WalletAddr
- Network.Haskoin.Wallet: signAccountTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> Maybe (TBMChan Notif) -> Maybe XPrvKey -> TxHash -> SqlPersistT m ([WalletTx], [WalletAddr])
- Network.Haskoin.Wallet: signOfflineTx :: Account -> Maybe XPrvKey -> Tx -> [CoinSignData] -> Tx
- Network.Haskoin.Wallet: spendableCoins :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => AccountId -> Word32 -> (SqlExpr (Entity WalletCoin) -> SqlExpr (Entity WalletTx) -> [SqlExpr OrderBy]) -> SqlPersistT m [InCoinData]
- Network.Haskoin.Wallet: stopSPVServer :: Config -> IO ()
- Network.Haskoin.Wallet: txs :: MonadIO m => Maybe TxConfidence -> AccountId -> ListRequest -> SqlPersistT m ([WalletTx], Word32)
- Network.Haskoin.Wallet: unusedAddresses :: MonadIO m => Entity Account -> AddressType -> ListRequest -> SqlPersistT m ([WalletAddr], Word32)
- Network.Haskoin.Wallet: useAddress :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => WalletAddr -> SqlPersistT m [WalletAddr]
- Network.Haskoin.Wallet: walletBestBlock :: MonadIO m => SqlPersistT m (BlockHash, Word32)
- Network.Haskoin.Wallet.Client: clientMain :: IO ()
- Network.Haskoin.Wallet.Internals: AccountMultisig :: !Int -> !Int -> AccountType
- Network.Haskoin.Wallet.Internals: AccountRegular :: AccountType
- Network.Haskoin.Wallet.Internals: AddressExternal :: AddressType
- Network.Haskoin.Wallet.Internals: AddressInfo :: !Address -> !(Maybe Word64) -> !Bool -> AddressInfo
- Network.Haskoin.Wallet.Internals: AddressInternal :: AddressType
- Network.Haskoin.Wallet.Internals: AddressLabel :: !Text -> AddressLabel
- Network.Haskoin.Wallet.Internals: BTCNode :: !String -> !Int -> BTCNode
- Network.Haskoin.Wallet.Internals: BalanceInfo :: !Word64 -> !Word64 -> !Int -> !Int -> BalanceInfo
- Network.Haskoin.Wallet.Internals: BlockInfo :: !BlockHash -> !BlockHeight -> !Word32 -> !UTCTime -> !BlockHash -> !JsonHash256 -> !Word32 -> !Word32 -> !Word32 -> !Double -> BlockInfo
- Network.Haskoin.Wallet.Internals: CoinSignData :: !OutPoint -> !ScriptOutput -> !SoftPath -> CoinSignData
- Network.Haskoin.Wallet.Internals: CreateTx :: ![(Address, Word64)] -> !Word64 -> !Word32 -> !Bool -> !Bool -> TxAction
- Network.Haskoin.Wallet.Internals: DeleteTxIdR :: !TxHash -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetAccountR :: !AccountName -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetAccountsR :: !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetAddrTxsR :: !AccountName -> !KeyIndex -> !AddressType -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetAddressR :: !AccountName -> !KeyIndex -> !AddressType -> !Word32 -> !Bool -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetAddressesR :: !AccountName -> !AddressType -> !Word32 -> !Bool -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetAddressesUnusedR :: !AccountName -> !AddressType -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetBalanceR :: !AccountName -> !Word32 -> !Bool -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetBlockInfoR :: ![BlockHash] -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetDeadR :: !AccountName -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetIndexR :: !AccountName -> !PubKeyC -> !AddressType -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetOfflineTxR :: !AccountName -> !TxHash -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetPendingR :: !AccountName -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetSyncHeightR :: !AccountName -> !BlockHeight -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetSyncR :: !AccountName -> !BlockHash -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetTxR :: !AccountName -> !TxHash -> WalletRequest
- Network.Haskoin.Wallet.Internals: GetTxsR :: !AccountName -> !ListRequest -> WalletRequest
- Network.Haskoin.Wallet.Internals: HandlerSession :: !Config -> !ConnectionPool -> !(Maybe SharedNodeState) -> !(TBMChan Notif) -> HandlerSession
- Network.Haskoin.Wallet.Internals: ImportTx :: !Tx -> TxAction
- Network.Haskoin.Wallet.Internals: InCoinData :: !(Entity WalletCoin) -> !WalletTx -> !WalletAddr -> InCoinData
- Network.Haskoin.Wallet.Internals: JsonAccount :: !Text -> !AccountType -> !(Maybe XPrvKey) -> !(Maybe Text) -> !(Maybe HardPath) -> ![XPubKey] -> !Word32 -> !UTCTime -> JsonAccount
- Network.Haskoin.Wallet.Internals: JsonAddr :: !Address -> !KeyIndex -> !AddressType -> !Text -> !(Maybe ScriptOutput) -> !(Maybe PubKeyC) -> !UTCTime -> !(Maybe BalanceInfo) -> JsonAddr
- Network.Haskoin.Wallet.Internals: JsonBlock :: !BlockHash -> !BlockHeight -> !BlockHash -> JsonBlock
- Network.Haskoin.Wallet.Internals: JsonCoin :: !TxHash -> !Word32 -> !Word64 -> !ScriptOutput -> !UTCTime -> !(Maybe JsonTx) -> !(Maybe JsonAddr) -> !(Maybe JsonTx) -> JsonCoin
- Network.Haskoin.Wallet.Internals: JsonSyncBlock :: !BlockHash -> !BlockHeight -> !BlockHash -> ![JsonTx] -> JsonSyncBlock
- Network.Haskoin.Wallet.Internals: JsonTx :: !TxHash -> !TxHash -> !TxType -> !Word64 -> !Word64 -> !Int64 -> ![AddressInfo] -> ![AddressInfo] -> ![AddressInfo] -> !Tx -> !Bool -> !TxConfidence -> !(Maybe BlockHash) -> !(Maybe Word32) -> !(Maybe Word32) -> !UTCTime -> !AccountName -> !(Maybe Word32) -> !(Maybe BlockHash) -> !(Maybe BlockHeight) -> JsonTx
- Network.Haskoin.Wallet.Internals: ListRequest :: !Word32 -> !Word32 -> !Bool -> ListRequest
- Network.Haskoin.Wallet.Internals: ListResult :: ![a] -> !Word32 -> ListResult a
- Network.Haskoin.Wallet.Internals: NewAccount :: !AccountName -> !AccountType -> !(Maybe Text) -> !(Maybe XPrvKey) -> !(Maybe HardPath) -> ![XPubKey] -> !Bool -> NewAccount
- Network.Haskoin.Wallet.Internals: NodeActionRescan :: !(Maybe Word32) -> NodeAction
- Network.Haskoin.Wallet.Internals: NodeActionStatus :: NodeAction
- Network.Haskoin.Wallet.Internals: NotifBlock :: !JsonBlock -> Notif
- Network.Haskoin.Wallet.Internals: NotifTx :: !JsonTx -> Notif
- Network.Haskoin.Wallet.Internals: OfflineTxData :: !Tx -> ![CoinSignData] -> OfflineTxData
- Network.Haskoin.Wallet.Internals: PostAccountGapR :: !AccountName -> !SetAccountGap -> WalletRequest
- Network.Haskoin.Wallet.Internals: PostAccountKeysR :: !AccountName -> ![XPubKey] -> WalletRequest
- Network.Haskoin.Wallet.Internals: PostAccountRenameR :: !AccountName -> !AccountName -> WalletRequest
- Network.Haskoin.Wallet.Internals: PostAccountsR :: !NewAccount -> WalletRequest
- Network.Haskoin.Wallet.Internals: PostAddressesR :: !AccountName -> !KeyIndex -> !AddressType -> WalletRequest
- Network.Haskoin.Wallet.Internals: PostNodeR :: !NodeAction -> WalletRequest
- Network.Haskoin.Wallet.Internals: PostOfflineTxR :: !AccountName -> !(Maybe XPrvKey) -> !Tx -> ![CoinSignData] -> WalletRequest
- Network.Haskoin.Wallet.Internals: PostTxsR :: !AccountName -> !(Maybe XPrvKey) -> !TxAction -> WalletRequest
- Network.Haskoin.Wallet.Internals: PutAddressR :: !AccountName -> !KeyIndex -> !AddressType -> !AddressLabel -> WalletRequest
- Network.Haskoin.Wallet.Internals: RescanRes :: !Word32 -> RescanRes
- Network.Haskoin.Wallet.Internals: ResponseError :: !Text -> WalletResponse a
- Network.Haskoin.Wallet.Internals: ResponseValid :: !(Maybe a) -> WalletResponse a
- Network.Haskoin.Wallet.Internals: SetAccountGap :: !Word32 -> SetAccountGap
- Network.Haskoin.Wallet.Internals: SignTx :: !TxHash -> TxAction
- Network.Haskoin.Wallet.Internals: StopServerR :: WalletRequest
- Network.Haskoin.Wallet.Internals: TxBuilding :: TxConfidence
- Network.Haskoin.Wallet.Internals: TxCompleteRes :: !Tx -> !Bool -> TxCompleteRes
- Network.Haskoin.Wallet.Internals: TxDead :: TxConfidence
- Network.Haskoin.Wallet.Internals: TxIncoming :: TxType
- Network.Haskoin.Wallet.Internals: TxOffline :: TxConfidence
- Network.Haskoin.Wallet.Internals: TxOutgoing :: TxType
- Network.Haskoin.Wallet.Internals: TxPending :: TxConfidence
- Network.Haskoin.Wallet.Internals: TxSelf :: TxType
- Network.Haskoin.Wallet.Internals: WalletException :: !String -> WalletException
- Network.Haskoin.Wallet.Internals: [accTxActionFee] :: TxAction -> !Word64
- Network.Haskoin.Wallet.Internals: [accTxActionHash] :: TxAction -> !TxHash
- Network.Haskoin.Wallet.Internals: [accTxActionMinConf] :: TxAction -> !Word32
- Network.Haskoin.Wallet.Internals: [accTxActionRcptFee] :: TxAction -> !Bool
- Network.Haskoin.Wallet.Internals: [accTxActionRecipients] :: TxAction -> ![(Address, Word64)]
- Network.Haskoin.Wallet.Internals: [accTxActionSign] :: TxAction -> !Bool
- Network.Haskoin.Wallet.Internals: [accTxActionTx] :: TxAction -> !Tx
- Network.Haskoin.Wallet.Internals: [accountTypeRequiredSigs] :: AccountType -> !Int
- Network.Haskoin.Wallet.Internals: [accountTypeTotalKeys] :: AccountType -> !Int
- Network.Haskoin.Wallet.Internals: [addressInfoAddress] :: AddressInfo -> !Address
- Network.Haskoin.Wallet.Internals: [addressInfoIsLocal] :: AddressInfo -> !Bool
- Network.Haskoin.Wallet.Internals: [addressInfoValue] :: AddressInfo -> !(Maybe Word64)
- Network.Haskoin.Wallet.Internals: [addressLabelLabel] :: AddressLabel -> !Text
- Network.Haskoin.Wallet.Internals: [balanceInfoCoins] :: BalanceInfo -> !Int
- Network.Haskoin.Wallet.Internals: [balanceInfoInBalance] :: BalanceInfo -> !Word64
- Network.Haskoin.Wallet.Internals: [balanceInfoOutBalance] :: BalanceInfo -> !Word64
- Network.Haskoin.Wallet.Internals: [balanceInfoSpentCoins] :: BalanceInfo -> !Int
- Network.Haskoin.Wallet.Internals: [blockInfoBits] :: BlockInfo -> !Word32
- Network.Haskoin.Wallet.Internals: [blockInfoChainWork] :: BlockInfo -> !Double
- Network.Haskoin.Wallet.Internals: [blockInfoChain] :: BlockInfo -> !Word32
- Network.Haskoin.Wallet.Internals: [blockInfoHash] :: BlockInfo -> !BlockHash
- Network.Haskoin.Wallet.Internals: [blockInfoHeight] :: BlockInfo -> !BlockHeight
- Network.Haskoin.Wallet.Internals: [blockInfoMerkleRoot] :: BlockInfo -> !JsonHash256
- Network.Haskoin.Wallet.Internals: [blockInfoNonce] :: BlockInfo -> !Word32
- Network.Haskoin.Wallet.Internals: [blockInfoPrevBlock] :: BlockInfo -> !BlockHash
- Network.Haskoin.Wallet.Internals: [blockInfoTimestamp] :: BlockInfo -> !UTCTime
- Network.Haskoin.Wallet.Internals: [blockInfoVersion] :: BlockInfo -> !Word32
- Network.Haskoin.Wallet.Internals: [btcNodeHost] :: BTCNode -> !String
- Network.Haskoin.Wallet.Internals: [btcNodePort] :: BTCNode -> !Int
- Network.Haskoin.Wallet.Internals: [coinSignDeriv] :: CoinSignData -> !SoftPath
- Network.Haskoin.Wallet.Internals: [coinSignOutPoint] :: CoinSignData -> !OutPoint
- Network.Haskoin.Wallet.Internals: [coinSignScriptOutput] :: CoinSignData -> !ScriptOutput
- Network.Haskoin.Wallet.Internals: [getAccountGap] :: SetAccountGap -> !Word32
- Network.Haskoin.Wallet.Internals: [handlerConfig] :: HandlerSession -> !Config
- Network.Haskoin.Wallet.Internals: [handlerNodeState] :: HandlerSession -> !(Maybe SharedNodeState)
- Network.Haskoin.Wallet.Internals: [handlerNotifChan] :: HandlerSession -> !(TBMChan Notif)
- Network.Haskoin.Wallet.Internals: [handlerPool] :: HandlerSession -> !ConnectionPool
- Network.Haskoin.Wallet.Internals: [inCoinDataAddr] :: InCoinData -> !WalletAddr
- Network.Haskoin.Wallet.Internals: [inCoinDataCoin] :: InCoinData -> !(Entity WalletCoin)
- Network.Haskoin.Wallet.Internals: [inCoinDataTx] :: InCoinData -> !WalletTx
- Network.Haskoin.Wallet.Internals: [jsonAccountCreated] :: JsonAccount -> !UTCTime
- Network.Haskoin.Wallet.Internals: [jsonAccountDerivation] :: JsonAccount -> !(Maybe HardPath)
- Network.Haskoin.Wallet.Internals: [jsonAccountGap] :: JsonAccount -> !Word32
- Network.Haskoin.Wallet.Internals: [jsonAccountKeys] :: JsonAccount -> ![XPubKey]
- Network.Haskoin.Wallet.Internals: [jsonAccountMaster] :: JsonAccount -> !(Maybe XPrvKey)
- Network.Haskoin.Wallet.Internals: [jsonAccountMnemonic] :: JsonAccount -> !(Maybe Text)
- Network.Haskoin.Wallet.Internals: [jsonAccountName] :: JsonAccount -> !Text
- Network.Haskoin.Wallet.Internals: [jsonAccountType] :: JsonAccount -> !AccountType
- Network.Haskoin.Wallet.Internals: [jsonAddrAddress] :: JsonAddr -> !Address
- Network.Haskoin.Wallet.Internals: [jsonAddrBalance] :: JsonAddr -> !(Maybe BalanceInfo)
- Network.Haskoin.Wallet.Internals: [jsonAddrCreated] :: JsonAddr -> !UTCTime
- Network.Haskoin.Wallet.Internals: [jsonAddrIndex] :: JsonAddr -> !KeyIndex
- Network.Haskoin.Wallet.Internals: [jsonAddrKey] :: JsonAddr -> !(Maybe PubKeyC)
- Network.Haskoin.Wallet.Internals: [jsonAddrLabel] :: JsonAddr -> !Text
- Network.Haskoin.Wallet.Internals: [jsonAddrRedeem] :: JsonAddr -> !(Maybe ScriptOutput)
- Network.Haskoin.Wallet.Internals: [jsonAddrType] :: JsonAddr -> !AddressType
- Network.Haskoin.Wallet.Internals: [jsonBlockHash] :: JsonBlock -> !BlockHash
- Network.Haskoin.Wallet.Internals: [jsonBlockHeight] :: JsonBlock -> !BlockHeight
- Network.Haskoin.Wallet.Internals: [jsonBlockPrev] :: JsonBlock -> !BlockHash
- Network.Haskoin.Wallet.Internals: [jsonCoinAddress] :: JsonCoin -> !(Maybe JsonAddr)
- Network.Haskoin.Wallet.Internals: [jsonCoinCreated] :: JsonCoin -> !UTCTime
- Network.Haskoin.Wallet.Internals: [jsonCoinHash] :: JsonCoin -> !TxHash
- Network.Haskoin.Wallet.Internals: [jsonCoinPos] :: JsonCoin -> !Word32
- Network.Haskoin.Wallet.Internals: [jsonCoinScript] :: JsonCoin -> !ScriptOutput
- Network.Haskoin.Wallet.Internals: [jsonCoinSpendingTx] :: JsonCoin -> !(Maybe JsonTx)
- Network.Haskoin.Wallet.Internals: [jsonCoinTx] :: JsonCoin -> !(Maybe JsonTx)
- Network.Haskoin.Wallet.Internals: [jsonCoinValue] :: JsonCoin -> !Word64
- Network.Haskoin.Wallet.Internals: [jsonSyncBlockHash] :: JsonSyncBlock -> !BlockHash
- Network.Haskoin.Wallet.Internals: [jsonSyncBlockHeight] :: JsonSyncBlock -> !BlockHeight
- Network.Haskoin.Wallet.Internals: [jsonSyncBlockPrev] :: JsonSyncBlock -> !BlockHash
- Network.Haskoin.Wallet.Internals: [jsonSyncBlockTxs] :: JsonSyncBlock -> ![JsonTx]
- Network.Haskoin.Wallet.Internals: [jsonTxAccount] :: JsonTx -> !AccountName
- Network.Haskoin.Wallet.Internals: [jsonTxBestBlockHeight] :: JsonTx -> !(Maybe BlockHeight)
- Network.Haskoin.Wallet.Internals: [jsonTxBestBlock] :: JsonTx -> !(Maybe BlockHash)
- Network.Haskoin.Wallet.Internals: [jsonTxChange] :: JsonTx -> ![AddressInfo]
- Network.Haskoin.Wallet.Internals: [jsonTxConfidence] :: JsonTx -> !TxConfidence
- Network.Haskoin.Wallet.Internals: [jsonTxConfirmations] :: JsonTx -> !(Maybe Word32)
- Network.Haskoin.Wallet.Internals: [jsonTxConfirmedBy] :: JsonTx -> !(Maybe BlockHash)
- Network.Haskoin.Wallet.Internals: [jsonTxConfirmedDate] :: JsonTx -> !(Maybe Word32)
- Network.Haskoin.Wallet.Internals: [jsonTxConfirmedHeight] :: JsonTx -> !(Maybe Word32)
- Network.Haskoin.Wallet.Internals: [jsonTxCreated] :: JsonTx -> !UTCTime
- Network.Haskoin.Wallet.Internals: [jsonTxHash] :: JsonTx -> !TxHash
- Network.Haskoin.Wallet.Internals: [jsonTxInValue] :: JsonTx -> !Word64
- Network.Haskoin.Wallet.Internals: [jsonTxInputs] :: JsonTx -> ![AddressInfo]
- Network.Haskoin.Wallet.Internals: [jsonTxIsCoinbase] :: JsonTx -> !Bool
- Network.Haskoin.Wallet.Internals: [jsonTxNosigHash] :: JsonTx -> !TxHash
- Network.Haskoin.Wallet.Internals: [jsonTxOutValue] :: JsonTx -> !Word64
- Network.Haskoin.Wallet.Internals: [jsonTxOutputs] :: JsonTx -> ![AddressInfo]
- Network.Haskoin.Wallet.Internals: [jsonTxTx] :: JsonTx -> !Tx
- Network.Haskoin.Wallet.Internals: [jsonTxType] :: JsonTx -> !TxType
- Network.Haskoin.Wallet.Internals: [jsonTxValue] :: JsonTx -> !Int64
- Network.Haskoin.Wallet.Internals: [listLimit] :: ListRequest -> !Word32
- Network.Haskoin.Wallet.Internals: [listOffset] :: ListRequest -> !Word32
- Network.Haskoin.Wallet.Internals: [listResultItems] :: ListResult a -> ![a]
- Network.Haskoin.Wallet.Internals: [listResultTotal] :: ListResult a -> !Word32
- Network.Haskoin.Wallet.Internals: [listReverse] :: ListRequest -> !Bool
- Network.Haskoin.Wallet.Internals: [newAccountDeriv] :: NewAccount -> !(Maybe HardPath)
- Network.Haskoin.Wallet.Internals: [newAccountKeys] :: NewAccount -> ![XPubKey]
- Network.Haskoin.Wallet.Internals: [newAccountMaster] :: NewAccount -> !(Maybe XPrvKey)
- Network.Haskoin.Wallet.Internals: [newAccountMnemonic] :: NewAccount -> !(Maybe Text)
- Network.Haskoin.Wallet.Internals: [newAccountName] :: NewAccount -> !AccountName
- Network.Haskoin.Wallet.Internals: [newAccountReadOnly] :: NewAccount -> !Bool
- Network.Haskoin.Wallet.Internals: [newAccountType] :: NewAccount -> !AccountType
- Network.Haskoin.Wallet.Internals: [nodeActionTimestamp] :: NodeAction -> !(Maybe Word32)
- Network.Haskoin.Wallet.Internals: [offlineTxDataCoinData] :: OfflineTxData -> ![CoinSignData]
- Network.Haskoin.Wallet.Internals: [offlineTxDataTx] :: OfflineTxData -> !Tx
- Network.Haskoin.Wallet.Internals: [rescanTimestamp] :: RescanRes -> !Word32
- Network.Haskoin.Wallet.Internals: [responseError] :: WalletResponse a -> !Text
- Network.Haskoin.Wallet.Internals: [responseResult] :: WalletResponse a -> !(Maybe a)
- Network.Haskoin.Wallet.Internals: [txCompleteComplete] :: TxCompleteRes -> !Bool
- Network.Haskoin.Wallet.Internals: [txCompleteTx] :: TxCompleteRes -> !Tx
- Network.Haskoin.Wallet.Internals: accTxsFromBlock :: (MonadIO m, MonadThrow m) => AccountId -> BlockHeight -> Word32 -> SqlPersistT m [WalletTx]
- Network.Haskoin.Wallet.Internals: accountBalance :: MonadIO m => AccountId -> Word32 -> Bool -> SqlPersistT m Word64
- Network.Haskoin.Wallet.Internals: accounts :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => ListRequest -> SqlPersistT m ([Account], Word32)
- Network.Haskoin.Wallet.Internals: addAccountKeys :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> [XPubKey] -> SqlPersistT m Account
- Network.Haskoin.Wallet.Internals: addrTxs :: MonadIO m => Entity Account -> Entity WalletAddr -> ListRequest -> SqlPersistT m ([WalletTx], Word32)
- Network.Haskoin.Wallet.Internals: addrTypeIndex :: AddressType -> KeyIndex
- Network.Haskoin.Wallet.Internals: addressBalances :: MonadIO m => Entity Account -> KeyIndex -> KeyIndex -> AddressType -> Word32 -> Bool -> SqlPersistT m [(KeyIndex, BalanceInfo)]
- Network.Haskoin.Wallet.Internals: addressCount :: MonadIO m => Entity Account -> AddressType -> SqlPersistT m Word32
- Network.Haskoin.Wallet.Internals: addressList :: MonadIO m => Entity Account -> AddressType -> ListRequest -> SqlPersistT m ([WalletAddr], Word32)
- Network.Haskoin.Wallet.Internals: addressPrvKey :: (MonadIO m, MonadThrow m) => Entity Account -> Maybe XPrvKey -> KeyIndex -> AddressType -> SqlPersistT m PrvKeyC
- Network.Haskoin.Wallet.Internals: addresses :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> AddressType -> SqlPersistT m [WalletAddr]
- Network.Haskoin.Wallet.Internals: addressesAll :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => SqlPersistT m [WalletAddr]
- Network.Haskoin.Wallet.Internals: adjustFCTime :: Timestamp -> Timestamp
- Network.Haskoin.Wallet.Internals: cmdAccount :: String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdAccounts :: [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdAddKey :: String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdAddrTxs :: String -> String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdBalance :: String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdBlockInfo :: [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdDead :: String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdDecodeTx :: Handler ()
- Network.Haskoin.Wallet.Internals: cmdDeleteTx :: String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdGenAddrs :: String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdGetOffline :: String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdGetTx :: String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdImport :: String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdKeyIndex :: String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdKeyPair :: Handler ()
- Network.Haskoin.Wallet.Internals: cmdLabel :: String -> String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdList :: String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdMonitor :: [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdNewAcc :: Bool -> String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdPending :: String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdPubKeys :: String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdRenameAcc :: String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdRescan :: [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdSend :: String -> String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdSendMany :: String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdSetGap :: String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdSign :: String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdSignOffline :: String -> String -> String -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdStart :: Handler ()
- Network.Haskoin.Wallet.Internals: cmdStatus :: Handler ()
- Network.Haskoin.Wallet.Internals: cmdStop :: Handler ()
- Network.Haskoin.Wallet.Internals: cmdSync :: String -> String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdTxs :: String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdUnused :: String -> [String] -> Handler ()
- Network.Haskoin.Wallet.Internals: cmdVersion :: Handler ()
- Network.Haskoin.Wallet.Internals: createWalletTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> Maybe (TBMChan Notif) -> Maybe XPrvKey -> [(Address, Word64)] -> Word64 -> Word32 -> Bool -> Bool -> SqlPersistT m (WalletTx, [WalletAddr])
- Network.Haskoin.Wallet.Internals: data AccountType
- Network.Haskoin.Wallet.Internals: data AddressInfo
- Network.Haskoin.Wallet.Internals: data AddressLabel
- Network.Haskoin.Wallet.Internals: data AddressType
- Network.Haskoin.Wallet.Internals: data BTCNode
- Network.Haskoin.Wallet.Internals: data BalanceInfo
- Network.Haskoin.Wallet.Internals: data BlockInfo
- Network.Haskoin.Wallet.Internals: data CoinSignData
- Network.Haskoin.Wallet.Internals: data HandlerSession
- Network.Haskoin.Wallet.Internals: data InCoinData
- Network.Haskoin.Wallet.Internals: data JsonAccount
- Network.Haskoin.Wallet.Internals: data JsonAddr
- Network.Haskoin.Wallet.Internals: data JsonBlock
- Network.Haskoin.Wallet.Internals: data JsonCoin
- Network.Haskoin.Wallet.Internals: data JsonSyncBlock
- Network.Haskoin.Wallet.Internals: data JsonTx
- Network.Haskoin.Wallet.Internals: data ListRequest
- Network.Haskoin.Wallet.Internals: data ListResult a
- Network.Haskoin.Wallet.Internals: data NewAccount
- Network.Haskoin.Wallet.Internals: data NodeAction
- Network.Haskoin.Wallet.Internals: data Notif
- Network.Haskoin.Wallet.Internals: data OfflineTxData
- Network.Haskoin.Wallet.Internals: data RescanRes
- Network.Haskoin.Wallet.Internals: data SetAccountGap
- Network.Haskoin.Wallet.Internals: data TxAction
- Network.Haskoin.Wallet.Internals: data TxCompleteRes
- Network.Haskoin.Wallet.Internals: data TxConfidence
- Network.Haskoin.Wallet.Internals: data TxType
- Network.Haskoin.Wallet.Internals: data WalletException
- Network.Haskoin.Wallet.Internals: data WalletRequest
- Network.Haskoin.Wallet.Internals: data WalletResponse a
- Network.Haskoin.Wallet.Internals: deleteTx :: (MonadIO m, MonadThrow m) => TxHash -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: deleteTxIdR :: (MonadLoggerIO m, MonadThrow m, MonadBaseControl IO m) => TxHash -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: firstAddrTime :: MonadIO m => SqlPersistT m (Maybe Timestamp)
- Network.Haskoin.Wallet.Internals: format :: String -> Text
- Network.Haskoin.Wallet.Internals: generateAddrs :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> AddressType -> KeyIndex -> SqlPersistT m Int
- Network.Haskoin.Wallet.Internals: getAccount :: (MonadIO m, MonadThrow m) => AccountName -> SqlPersistT m (Entity Account)
- Network.Haskoin.Wallet.Internals: getAccountR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getAccountTx :: MonadIO m => AccountId -> TxHash -> SqlPersistT m WalletTx
- Network.Haskoin.Wallet.Internals: getAccountsR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadBase IO m, MonadThrow m, MonadResource m) => ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getAddrTxsR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> KeyIndex -> AddressType -> ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getAddress :: (MonadIO m, MonadThrow m) => Entity Account -> AddressType -> KeyIndex -> SqlPersistT m (Entity WalletAddr)
- Network.Haskoin.Wallet.Internals: getAddressR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> KeyIndex -> AddressType -> Word32 -> Bool -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getAddressesR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> AddressType -> Word32 -> Bool -> ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getAddressesUnusedR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> AddressType -> ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getBalanceR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> Word32 -> Bool -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getBlockInfoR :: (MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m) => [BlockHash] -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getBloomFilter :: (MonadIO m, MonadThrow m) => SqlPersistT m (BloomFilter, Int, Double)
- Network.Haskoin.Wallet.Internals: getDatabasePool :: (MonadLoggerIO m, MonadBaseControl IO m) => DatabaseConfType -> m ConnectionPool
- Network.Haskoin.Wallet.Internals: getDeadR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getIndexR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> PubKeyC -> AddressType -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getOfflineTxData :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => AccountId -> TxHash -> SqlPersistT m (OfflineTxData, [InCoinData])
- Network.Haskoin.Wallet.Internals: getOfflineTxR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadBase IO m, MonadThrow m, MonadResource m) => AccountName -> TxHash -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getPathPubKey :: Account -> SoftPath -> PubKeyC
- Network.Haskoin.Wallet.Internals: getPathRedeem :: Account -> SoftPath -> RedeemScript
- Network.Haskoin.Wallet.Internals: getPendingR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getPendingTxs :: MonadIO m => Int -> SqlPersistT m [TxHash]
- Network.Haskoin.Wallet.Internals: getSyncR :: (MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m) => AccountName -> Either BlockHeight BlockHash -> ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getTx :: MonadIO m => TxHash -> SqlPersistT m (Maybe Tx)
- Network.Haskoin.Wallet.Internals: getTxR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> TxHash -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getTxs :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> ListRequest -> String -> (AccountId -> ListRequest -> SqlPersistT m ([WalletTx], Word32)) -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: getTxsR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> ListRequest -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: importMerkles :: MonadIO m => BlockChainAction -> [MerkleTxs] -> Maybe (TBMChan Notif) -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: importNetTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Tx -> Maybe (TBMChan Notif) -> SqlPersistT m ([WalletTx], [WalletAddr])
- Network.Haskoin.Wallet.Internals: importTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Tx -> Maybe (TBMChan Notif) -> AccountId -> SqlPersistT m ([WalletTx], [WalletAddr])
- Network.Haskoin.Wallet.Internals: initWallet :: MonadIO m => Double -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: isCompleteAccount :: Account -> Bool
- Network.Haskoin.Wallet.Internals: isMultisigAccount :: Account -> Bool
- Network.Haskoin.Wallet.Internals: isReadAccount :: Account -> Bool
- Network.Haskoin.Wallet.Internals: join2 :: [SqlExpr (Value Bool)] -> SqlExpr (Value Bool)
- Network.Haskoin.Wallet.Internals: killTxs :: MonadIO m => Maybe (TBMChan Notif) -> [TxHash] -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: limitOffset :: Word32 -> Word32 -> SqlQuery ()
- Network.Haskoin.Wallet.Internals: lookupByPubKey :: (MonadIO m, MonadThrow m) => Entity Account -> PubKeyC -> AddressType -> SqlPersistT m [WalletAddr]
- Network.Haskoin.Wallet.Internals: newAccount :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => NewAccount -> SqlPersistT m (Entity Account, Maybe Mnemonic)
- Network.Haskoin.Wallet.Internals: paramLimit :: Int
- Network.Haskoin.Wallet.Internals: postAccountGapR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadBase IO m, MonadThrow m, MonadResource m) => AccountName -> SetAccountGap -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: postAccountKeysR :: (MonadResource m, MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m) => AccountName -> [XPubKey] -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: postAccountRenameR :: (MonadResource m, MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m) => AccountName -> AccountName -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: postAccountsR :: (MonadResource m, MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m) => NewAccount -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: postAddressesR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m, MonadBase IO m, MonadResource m) => AccountName -> KeyIndex -> AddressType -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: postNodeR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => NodeAction -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: postOfflineTxR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadBase IO m, MonadThrow m, MonadResource m) => AccountName -> Maybe XPrvKey -> Tx -> [CoinSignData] -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: postTxsR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadBase IO m, MonadThrow m, MonadResource m) => AccountName -> Maybe XPrvKey -> TxAction -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: putAddressR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m) => AccountName -> KeyIndex -> AddressType -> AddressLabel -> Handler m (Maybe Value)
- Network.Haskoin.Wallet.Internals: renameAccount :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> AccountName -> SqlPersistT m Account
- Network.Haskoin.Wallet.Internals: resetRescan :: MonadIO m => SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: reviveTx :: MonadIO m => Maybe (TBMChan Notif) -> Tx -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: runDB :: MonadBaseControl IO m => SqlPersistT m a -> Handler m a
- Network.Haskoin.Wallet.Internals: runDBPool :: MonadBaseControl IO m => SqlPersistT m a -> ConnectionPool -> m a
- Network.Haskoin.Wallet.Internals: runHandler :: Monad m => Handler m a -> HandlerSession -> m a
- Network.Haskoin.Wallet.Internals: runNode :: MonadIO m => NodeT m a -> Handler m a
- Network.Haskoin.Wallet.Internals: setAccountGap :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> Word32 -> SqlPersistT m (Entity Account)
- Network.Haskoin.Wallet.Internals: setAddrLabel :: (MonadIO m, MonadThrow m) => Entity Account -> KeyIndex -> AddressType -> Text -> SqlPersistT m WalletAddr
- Network.Haskoin.Wallet.Internals: signAccountTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => Entity Account -> Maybe (TBMChan Notif) -> Maybe XPrvKey -> TxHash -> SqlPersistT m ([WalletTx], [WalletAddr])
- Network.Haskoin.Wallet.Internals: signOfflineTx :: Account -> Maybe XPrvKey -> Tx -> [CoinSignData] -> Tx
- Network.Haskoin.Wallet.Internals: spendableCoins :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => AccountId -> Word32 -> (SqlExpr (Entity WalletCoin) -> SqlExpr (Entity WalletTx) -> [SqlExpr OrderBy]) -> SqlPersistT m [InCoinData]
- Network.Haskoin.Wallet.Internals: splitDelete :: MonadIO m => [t] -> ([t] -> SqlQuery ()) -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: splitInsertMany_ :: (MonadIO m, PersistEntity val, PersistEntityBackend val ~ SqlBackend) => [val] -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: splitSelect :: (SqlSelect a r, MonadIO m) => [t] -> ([t] -> SqlQuery a) -> SqlPersistT m [r]
- Network.Haskoin.Wallet.Internals: splitUpdate :: (MonadIO m, PersistEntity val, PersistEntityBackend val ~ SqlBackend) => [t] -> ([t] -> SqlExpr (Entity val) -> SqlQuery ()) -> SqlPersistT m ()
- Network.Haskoin.Wallet.Internals: subSelectAddrCount :: Entity Account -> AddressType -> SqlExpr (Value KeyIndex)
- Network.Haskoin.Wallet.Internals: tryDBPool :: MonadLoggerIO m => ConnectionPool -> SqlPersistM a -> m (Maybe a)
- Network.Haskoin.Wallet.Internals: txs :: MonadIO m => Maybe TxConfidence -> AccountId -> ListRequest -> SqlPersistT m ([WalletTx], Word32)
- Network.Haskoin.Wallet.Internals: type AccountName = Text
- Network.Haskoin.Wallet.Internals: type DatabaseConfType = SqliteConf
- Network.Haskoin.Wallet.Internals: type Handler m = ReaderT HandlerSession m
- Network.Haskoin.Wallet.Internals: unusedAddresses :: MonadIO m => Entity Account -> AddressType -> ListRequest -> SqlPersistT m ([WalletAddr], Word32)
- Network.Haskoin.Wallet.Internals: updateNodeFilter :: (MonadBaseControl IO m, MonadLoggerIO m, MonadThrow m) => Handler m ()
- Network.Haskoin.Wallet.Internals: useAddress :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m) => WalletAddr -> SqlPersistT m [WalletAddr]
- Network.Haskoin.Wallet.Internals: walletBestBlock :: MonadIO m => SqlPersistT m (BlockHash, Word32)
- Network.Haskoin.Wallet.Internals: whenOnline :: Monad m => Handler m () -> Handler m ()
- Network.Haskoin.Wallet.Model: Account :: !Text -> !AccountType -> !(Maybe HardPath) -> !(Maybe XPrvKey) -> ![XPubKey] -> !Word32 -> !UTCTime -> Account
- Network.Haskoin.Wallet.Model: SpentCoin :: !(Key Account) -> !TxHash -> !Word32 -> !(Key WalletTx) -> !UTCTime -> SpentCoin
- Network.Haskoin.Wallet.Model: WalletAddr :: !(Key Account) -> !Address -> !KeyIndex -> !AddressType -> !Text -> !(Maybe ScriptOutput) -> !(Maybe PubKeyC) -> !UTCTime -> WalletAddr
- Network.Haskoin.Wallet.Model: WalletCoin :: !(Key Account) -> !TxHash -> !Word32 -> !(Key WalletTx) -> !(Key WalletAddr) -> !Word64 -> !ScriptOutput -> !UTCTime -> WalletCoin
- Network.Haskoin.Wallet.Model: WalletState :: !BlockHeight -> !BlockHash -> !BloomFilter -> !Int -> !Double -> !Int -> !UTCTime -> WalletState
- Network.Haskoin.Wallet.Model: WalletTx :: !(Key Account) -> !TxHash -> !TxHash -> !TxType -> !Word64 -> !Word64 -> ![AddressInfo] -> ![AddressInfo] -> ![AddressInfo] -> !Tx -> !Bool -> !TxConfidence -> !(Maybe BlockHash) -> !(Maybe BlockHeight) -> !(Maybe Timestamp) -> !UTCTime -> WalletTx
- Network.Haskoin.Wallet.Model: [accountCreated] :: Account -> !UTCTime
- Network.Haskoin.Wallet.Model: [accountDerivation] :: Account -> !(Maybe HardPath)
- Network.Haskoin.Wallet.Model: [accountGap] :: Account -> !Word32
- Network.Haskoin.Wallet.Model: [accountKeys] :: Account -> ![XPubKey]
- Network.Haskoin.Wallet.Model: [accountMaster] :: Account -> !(Maybe XPrvKey)
- Network.Haskoin.Wallet.Model: [accountName] :: Account -> !Text
- Network.Haskoin.Wallet.Model: [accountType] :: Account -> !AccountType
- Network.Haskoin.Wallet.Model: [spentCoinAccount] :: SpentCoin -> !(Key Account)
- Network.Haskoin.Wallet.Model: [spentCoinCreated] :: SpentCoin -> !UTCTime
- Network.Haskoin.Wallet.Model: [spentCoinHash] :: SpentCoin -> !TxHash
- Network.Haskoin.Wallet.Model: [spentCoinPos] :: SpentCoin -> !Word32
- Network.Haskoin.Wallet.Model: [spentCoinSpendingTx] :: SpentCoin -> !(Key WalletTx)
- Network.Haskoin.Wallet.Model: [walletAddrAccount] :: WalletAddr -> !(Key Account)
- Network.Haskoin.Wallet.Model: [walletAddrAddress] :: WalletAddr -> !Address
- Network.Haskoin.Wallet.Model: [walletAddrCreated] :: WalletAddr -> !UTCTime
- Network.Haskoin.Wallet.Model: [walletAddrIndex] :: WalletAddr -> !KeyIndex
- Network.Haskoin.Wallet.Model: [walletAddrKey] :: WalletAddr -> !(Maybe PubKeyC)
- Network.Haskoin.Wallet.Model: [walletAddrLabel] :: WalletAddr -> !Text
- Network.Haskoin.Wallet.Model: [walletAddrRedeem] :: WalletAddr -> !(Maybe ScriptOutput)
- Network.Haskoin.Wallet.Model: [walletAddrType] :: WalletAddr -> !AddressType
- Network.Haskoin.Wallet.Model: [walletCoinAccount] :: WalletCoin -> !(Key Account)
- Network.Haskoin.Wallet.Model: [walletCoinAddr] :: WalletCoin -> !(Key WalletAddr)
- Network.Haskoin.Wallet.Model: [walletCoinCreated] :: WalletCoin -> !UTCTime
- Network.Haskoin.Wallet.Model: [walletCoinHash] :: WalletCoin -> !TxHash
- Network.Haskoin.Wallet.Model: [walletCoinPos] :: WalletCoin -> !Word32
- Network.Haskoin.Wallet.Model: [walletCoinScript] :: WalletCoin -> !ScriptOutput
- Network.Haskoin.Wallet.Model: [walletCoinTx] :: WalletCoin -> !(Key WalletTx)
- Network.Haskoin.Wallet.Model: [walletCoinValue] :: WalletCoin -> !Word64
- Network.Haskoin.Wallet.Model: [walletStateBlock] :: WalletState -> !BlockHash
- Network.Haskoin.Wallet.Model: [walletStateBloomElems] :: WalletState -> !Int
- Network.Haskoin.Wallet.Model: [walletStateBloomFilter] :: WalletState -> !BloomFilter
- Network.Haskoin.Wallet.Model: [walletStateBloomFp] :: WalletState -> !Double
- Network.Haskoin.Wallet.Model: [walletStateCreated] :: WalletState -> !UTCTime
- Network.Haskoin.Wallet.Model: [walletStateHeight] :: WalletState -> !BlockHeight
- Network.Haskoin.Wallet.Model: [walletStateVersion] :: WalletState -> !Int
- Network.Haskoin.Wallet.Model: [walletTxAccount] :: WalletTx -> !(Key Account)
- Network.Haskoin.Wallet.Model: [walletTxChange] :: WalletTx -> ![AddressInfo]
- Network.Haskoin.Wallet.Model: [walletTxConfidence] :: WalletTx -> !TxConfidence
- Network.Haskoin.Wallet.Model: [walletTxConfirmedBy] :: WalletTx -> !(Maybe BlockHash)
- Network.Haskoin.Wallet.Model: [walletTxConfirmedDate] :: WalletTx -> !(Maybe Timestamp)
- Network.Haskoin.Wallet.Model: [walletTxConfirmedHeight] :: WalletTx -> !(Maybe BlockHeight)
- Network.Haskoin.Wallet.Model: [walletTxCreated] :: WalletTx -> !UTCTime
- Network.Haskoin.Wallet.Model: [walletTxHash] :: WalletTx -> !TxHash
- Network.Haskoin.Wallet.Model: [walletTxInValue] :: WalletTx -> !Word64
- Network.Haskoin.Wallet.Model: [walletTxInputs] :: WalletTx -> ![AddressInfo]
- Network.Haskoin.Wallet.Model: [walletTxIsCoinbase] :: WalletTx -> !Bool
- Network.Haskoin.Wallet.Model: [walletTxNosigHash] :: WalletTx -> !TxHash
- Network.Haskoin.Wallet.Model: [walletTxOutValue] :: WalletTx -> !Word64
- Network.Haskoin.Wallet.Model: [walletTxOutputs] :: WalletTx -> ![AddressInfo]
- Network.Haskoin.Wallet.Model: [walletTxTx] :: WalletTx -> !Tx
- Network.Haskoin.Wallet.Model: [walletTxType] :: WalletTx -> !TxType
- Network.Haskoin.Wallet.Model: data Account
- Network.Haskoin.Wallet.Model: data SpentCoin
- Network.Haskoin.Wallet.Model: data WalletAddr
- Network.Haskoin.Wallet.Model: data WalletCoin
- Network.Haskoin.Wallet.Model: data WalletState
- Network.Haskoin.Wallet.Model: data WalletTx
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistEntity.PersistEntity Network.Haskoin.Wallet.Model.Account
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistEntity.PersistEntity Network.Haskoin.Wallet.Model.SpentCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistEntity.PersistEntity Network.Haskoin.Wallet.Model.WalletAddr
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistEntity.PersistEntity Network.Haskoin.Wallet.Model.WalletCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistEntity.PersistEntity Network.Haskoin.Wallet.Model.WalletState
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistEntity.PersistEntity Network.Haskoin.Wallet.Model.WalletTx
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField Network.Haskoin.Wallet.Model.Account
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField Network.Haskoin.Wallet.Model.SpentCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField Network.Haskoin.Wallet.Model.WalletAddr
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField Network.Haskoin.Wallet.Model.WalletCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField Network.Haskoin.Wallet.Model.WalletState
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistField.PersistField Network.Haskoin.Wallet.Model.WalletTx
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistStore.ToBackendKey Database.Persist.Sql.Types.Internal.SqlBackend Network.Haskoin.Wallet.Model.Account
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistStore.ToBackendKey Database.Persist.Sql.Types.Internal.SqlBackend Network.Haskoin.Wallet.Model.SpentCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistStore.ToBackendKey Database.Persist.Sql.Types.Internal.SqlBackend Network.Haskoin.Wallet.Model.WalletAddr
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistStore.ToBackendKey Database.Persist.Sql.Types.Internal.SqlBackend Network.Haskoin.Wallet.Model.WalletCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistStore.ToBackendKey Database.Persist.Sql.Types.Internal.SqlBackend Network.Haskoin.Wallet.Model.WalletState
- Network.Haskoin.Wallet.Model: instance Database.Persist.Class.PersistStore.ToBackendKey Database.Persist.Sql.Types.Internal.SqlBackend Network.Haskoin.Wallet.Model.WalletTx
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql Network.Haskoin.Wallet.Model.Account
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql Network.Haskoin.Wallet.Model.SpentCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql Network.Haskoin.Wallet.Model.WalletAddr
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql Network.Haskoin.Wallet.Model.WalletCoin
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql Network.Haskoin.Wallet.Model.WalletState
- Network.Haskoin.Wallet.Model: instance Database.Persist.Sql.Class.PersistFieldSql Network.Haskoin.Wallet.Model.WalletTx
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show Network.Haskoin.Wallet.Model.Account
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show Network.Haskoin.Wallet.Model.SpentCoin
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show Network.Haskoin.Wallet.Model.WalletAddr
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show Network.Haskoin.Wallet.Model.WalletCoin
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show Network.Haskoin.Wallet.Model.WalletState
- Network.Haskoin.Wallet.Model: instance GHC.Show.Show Network.Haskoin.Wallet.Model.WalletTx
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.Account)
- Network.Haskoin.Wallet.Model: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.SpentCoin)
- Network.Haskoin.Wallet.Model: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletAddr)
- Network.Haskoin.Wallet.Model: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletCoin)
- Network.Haskoin.Wallet.Model: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletState)
- Network.Haskoin.Wallet.Model: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Network.Haskoin.Wallet.Model.WalletTx)
- Network.Haskoin.Wallet.Model: migrateWallet :: Migration
- Network.Haskoin.Wallet.Model: toJsonAccount :: Maybe Mnemonic -> Account -> JsonAccount
- Network.Haskoin.Wallet.Model: toJsonAddr :: WalletAddr -> Maybe BalanceInfo -> JsonAddr
- Network.Haskoin.Wallet.Model: toJsonCoin :: WalletCoin -> Maybe JsonTx -> Maybe JsonAddr -> Maybe JsonTx -> JsonCoin
- Network.Haskoin.Wallet.Model: toJsonTx :: AccountName -> Maybe (BlockHash, BlockHeight) -> WalletTx -> JsonTx
- Network.Haskoin.Wallet.Model: type AccountId = Key Account
- Network.Haskoin.Wallet.Model: type SpentCoinId = Key SpentCoin
- Network.Haskoin.Wallet.Model: type WalletAddrId = Key WalletAddr
- Network.Haskoin.Wallet.Model: type WalletCoinId = Key WalletCoin
- Network.Haskoin.Wallet.Model: type WalletStateId = Key WalletState
- Network.Haskoin.Wallet.Model: type WalletTxId = Key WalletTx
- Network.Haskoin.Wallet.Server: instance Control.DeepSeq.NFData Network.Haskoin.Wallet.Server.EventSession
- Network.Haskoin.Wallet.Server: instance GHC.Classes.Eq Network.Haskoin.Wallet.Server.EventSession
- Network.Haskoin.Wallet.Server: instance GHC.Read.Read Network.Haskoin.Wallet.Server.EventSession
- Network.Haskoin.Wallet.Server: instance GHC.Show.Show Network.Haskoin.Wallet.Server.EventSession
- Network.Haskoin.Wallet.Server: runSPVServer :: Config -> IO ()
- Network.Haskoin.Wallet.Server: runSPVServerWithContext :: (MonadLoggerIO m, MonadBaseControl IO m, MonadBase IO m, MonadThrow m, MonadResource m) => Config -> Context -> m ()
- Network.Haskoin.Wallet.Server: stopSPVServer :: Config -> IO ()
- Network.Haskoin.Wallet.Settings: Config :: !Word32 -> !Word32 -> !Bool -> !Word64 -> !Bool -> !AddressType -> !Bool -> !Bool -> !(Maybe HardPath) -> !OutputFormat -> !String -> !String -> !Bool -> !FilePath -> !Bool -> !FilePath -> !String -> !String -> !(HashMap Text [BTCNode]) -> !SPVMode -> !Double -> !(HashMap Text DatabaseConfType) -> !FilePath -> !FilePath -> !LogLevel -> !Bool -> !(Maybe (Restricted Div5 ByteString)) -> !(Maybe (Restricted Div5 ByteString)) -> !(Maybe (Restricted Div5 ByteString)) -> !(Maybe (Restricted Div5 ByteString)) -> Config
- Network.Haskoin.Wallet.Settings: OutputJSON :: OutputFormat
- Network.Haskoin.Wallet.Settings: OutputNormal :: OutputFormat
- Network.Haskoin.Wallet.Settings: OutputYAML :: OutputFormat
- Network.Haskoin.Wallet.Settings: SPVOffline :: SPVMode
- Network.Haskoin.Wallet.Settings: SPVOnline :: SPVMode
- Network.Haskoin.Wallet.Settings: [configAddrType] :: Config -> !AddressType
- Network.Haskoin.Wallet.Settings: [configBTCNodes] :: Config -> !(HashMap Text [BTCNode])
- Network.Haskoin.Wallet.Settings: [configBindNotif] :: Config -> !String
- Network.Haskoin.Wallet.Settings: [configBind] :: Config -> !String
- Network.Haskoin.Wallet.Settings: [configBloomFP] :: Config -> !Double
- Network.Haskoin.Wallet.Settings: [configClientKeyPub] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet.Settings: [configClientKey] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet.Settings: [configConnectNotif] :: Config -> !String
- Network.Haskoin.Wallet.Settings: [configConnect] :: Config -> !String
- Network.Haskoin.Wallet.Settings: [configCount] :: Config -> !Word32
- Network.Haskoin.Wallet.Settings: [configDatabase] :: Config -> !(HashMap Text DatabaseConfType)
- Network.Haskoin.Wallet.Settings: [configDetach] :: Config -> !Bool
- Network.Haskoin.Wallet.Settings: [configDir] :: Config -> !FilePath
- Network.Haskoin.Wallet.Settings: [configFee] :: Config -> !Word64
- Network.Haskoin.Wallet.Settings: [configFile] :: Config -> !FilePath
- Network.Haskoin.Wallet.Settings: [configFormat] :: Config -> !OutputFormat
- Network.Haskoin.Wallet.Settings: [configLogFile] :: Config -> !FilePath
- Network.Haskoin.Wallet.Settings: [configLogLevel] :: Config -> !LogLevel
- Network.Haskoin.Wallet.Settings: [configMinConf] :: Config -> !Word32
- Network.Haskoin.Wallet.Settings: [configMode] :: Config -> !SPVMode
- Network.Haskoin.Wallet.Settings: [configOffline] :: Config -> !Bool
- Network.Haskoin.Wallet.Settings: [configPath] :: Config -> !(Maybe HardPath)
- Network.Haskoin.Wallet.Settings: [configPidFile] :: Config -> !FilePath
- Network.Haskoin.Wallet.Settings: [configRcptFee] :: Config -> !Bool
- Network.Haskoin.Wallet.Settings: [configReversePaging] :: Config -> !Bool
- Network.Haskoin.Wallet.Settings: [configServerKeyPub] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet.Settings: [configServerKey] :: Config -> !(Maybe (Restricted Div5 ByteString))
- Network.Haskoin.Wallet.Settings: [configSignTx] :: Config -> !Bool
- Network.Haskoin.Wallet.Settings: [configTestnet] :: Config -> !Bool
- Network.Haskoin.Wallet.Settings: [configVerbose] :: Config -> !Bool
- Network.Haskoin.Wallet.Settings: data Config
- Network.Haskoin.Wallet.Settings: data OutputFormat
- Network.Haskoin.Wallet.Settings: data SPVMode
- Network.Haskoin.Wallet.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Network.Haskoin.Wallet.Settings.Config
- Network.Haskoin.Wallet.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Network.Haskoin.Wallet.Settings.LogLevelJSON
- Network.Haskoin.Wallet.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Network.Haskoin.Wallet.Settings.OutputFormat
- Network.Haskoin.Wallet.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Network.Haskoin.Wallet.Settings.SPVMode
- Network.Haskoin.Wallet.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Network.Haskoin.Wallet.Settings.LogLevelJSON
- Network.Haskoin.Wallet.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Network.Haskoin.Wallet.Settings.OutputFormat
- Network.Haskoin.Wallet.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Network.Haskoin.Wallet.Settings.SPVMode
- Network.Haskoin.Wallet.Settings: instance Data.Default.Class.Default Network.Haskoin.Wallet.Settings.Config
- Network.Haskoin.Wallet.Settings: instance GHC.Classes.Eq Network.Haskoin.Wallet.Settings.LogLevelJSON
- Network.Haskoin.Wallet.Settings: instance GHC.Classes.Eq Network.Haskoin.Wallet.Settings.SPVMode
- Network.Haskoin.Wallet.Settings: instance GHC.Read.Read Network.Haskoin.Wallet.Settings.LogLevelJSON
- Network.Haskoin.Wallet.Settings: instance GHC.Read.Read Network.Haskoin.Wallet.Settings.SPVMode
- Network.Haskoin.Wallet.Settings: instance GHC.Show.Show Network.Haskoin.Wallet.Settings.LogLevelJSON
- Network.Haskoin.Wallet.Settings: instance GHC.Show.Show Network.Haskoin.Wallet.Settings.SPVMode
+ Haskoin.Wallet.Amounts: UnitBit :: AmountUnit
+ Haskoin.Wallet.Amounts: UnitBitcoin :: AmountUnit
+ Haskoin.Wallet.Amounts: UnitSatoshi :: AmountUnit
+ Haskoin.Wallet.Amounts: data AmountUnit
+ Haskoin.Wallet.Amounts: dropAmountSep :: Text -> Text
+ Haskoin.Wallet.Amounts: instance GHC.Classes.Eq Haskoin.Wallet.Amounts.AmountUnit
+ Haskoin.Wallet.Amounts: instance GHC.Show.Show Haskoin.Wallet.Amounts.AmountUnit
+ Haskoin.Wallet.Amounts: readAmount :: AmountUnit -> Text -> Maybe Natural
+ Haskoin.Wallet.Amounts: readIntegerAmount :: AmountUnit -> Text -> Maybe Integer
+ Haskoin.Wallet.Amounts: readNatural :: Text -> Maybe Natural
+ Haskoin.Wallet.Amounts: showAmount :: AmountUnit -> Natural -> Text
+ Haskoin.Wallet.Amounts: showIntegerAmount :: AmountUnit -> Integer -> Text
+ Haskoin.Wallet.Amounts: showUnit :: AmountUnit -> Integer -> Text
+ Haskoin.Wallet.Backup: AccountBackup :: !Text -> !Fingerprint -> !XPubKey -> !Network -> !Natural -> !Natural -> Map Address Text -> [Address] -> !UTCTime -> AccountBackup
+ Haskoin.Wallet.Backup: SyncRes :: !DBAccount -> !BlockHash -> !BlockHeight -> !Natural -> !Natural -> SyncRes
+ Haskoin.Wallet.Backup: WalletBackup :: [AccountBackup] -> WalletBackup
+ Haskoin.Wallet.Backup: [accountBackupCreated] :: AccountBackup -> !UTCTime
+ Haskoin.Wallet.Backup: [accountBackupExternal] :: AccountBackup -> !Natural
+ Haskoin.Wallet.Backup: [accountBackupFree] :: AccountBackup -> [Address]
+ Haskoin.Wallet.Backup: [accountBackupInternal] :: AccountBackup -> !Natural
+ Haskoin.Wallet.Backup: [accountBackupLabels] :: AccountBackup -> Map Address Text
+ Haskoin.Wallet.Backup: [accountBackupName] :: AccountBackup -> !Text
+ Haskoin.Wallet.Backup: [accountBackupNetwork] :: AccountBackup -> !Network
+ Haskoin.Wallet.Backup: [accountBackupPubKey] :: AccountBackup -> !XPubKey
+ Haskoin.Wallet.Backup: [accountBackupWallet] :: AccountBackup -> !Fingerprint
+ Haskoin.Wallet.Backup: [syncResAccount] :: SyncRes -> !DBAccount
+ Haskoin.Wallet.Backup: [syncResBlockHash] :: SyncRes -> !BlockHash
+ Haskoin.Wallet.Backup: [syncResBlockHeight] :: SyncRes -> !BlockHeight
+ Haskoin.Wallet.Backup: [syncResCoinUpdates] :: SyncRes -> !Natural
+ Haskoin.Wallet.Backup: [syncResTxUpdates] :: SyncRes -> !Natural
+ Haskoin.Wallet.Backup: coinToTxHash :: DBCoin -> Either String TxHash
+ Haskoin.Wallet.Backup: createBackup :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> ExceptT String (DB m) WalletBackup
+ Haskoin.Wallet.Backup: data AccountBackup
+ Haskoin.Wallet.Backup: data SyncRes
+ Haskoin.Wallet.Backup: discoverAddrs :: forall (m :: Type -> Type). MonadIO m => Network -> Ctx -> Config -> XPubKey -> ExceptT String m (Natural, Natural)
+ Haskoin.Wallet.Backup: filterAddresses :: [Balance] -> Map Address AddressBalance -> Either String [Balance]
+ Haskoin.Wallet.Backup: instance GHC.Classes.Eq Haskoin.Wallet.Backup.AccountBackup
+ Haskoin.Wallet.Backup: instance GHC.Classes.Eq Haskoin.Wallet.Backup.SyncRes
+ Haskoin.Wallet.Backup: instance GHC.Classes.Eq Haskoin.Wallet.Backup.WalletBackup
+ Haskoin.Wallet.Backup: instance GHC.Show.Show Haskoin.Wallet.Backup.AccountBackup
+ Haskoin.Wallet.Backup: instance GHC.Show.Show Haskoin.Wallet.Backup.SyncRes
+ Haskoin.Wallet.Backup: instance GHC.Show.Show Haskoin.Wallet.Backup.WalletBackup
+ Haskoin.Wallet.Backup: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.Backup.AccountBackup
+ Haskoin.Wallet.Backup: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.Backup.WalletBackup
+ Haskoin.Wallet.Backup: newtype WalletBackup
+ Haskoin.Wallet.Backup: restoreAccount :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> AccountBackup -> ExceptT String (DB m) SyncRes
+ Haskoin.Wallet.Backup: restoreBackup :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> WalletBackup -> ExceptT String (DB m) [SyncRes]
+ Haskoin.Wallet.Backup: searchAddrTxs :: forall (m :: Type -> Type). MonadIO m => Network -> Ctx -> Config -> [TxHash] -> [Address] -> ExceptT String m [TxHash]
+ Haskoin.Wallet.Backup: sync :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> Network -> DBAccountId -> Bool -> ExceptT String (DB m) SyncRes
+ Haskoin.Wallet.Commands: ResponseAccResult :: !DBAccount -> !Bool -> !Text -> Response
+ Haskoin.Wallet.Commands: ResponseAccount :: !DBAccount -> Response
+ Haskoin.Wallet.Commands: ResponseAccounts :: ![DBAccount] -> Response
+ Haskoin.Wallet.Commands: ResponseAddress :: !DBAccount -> !DBAddress -> Response
+ Haskoin.Wallet.Commands: ResponseAddresses :: !DBAccount -> ![DBAddress] -> Response
+ Haskoin.Wallet.Commands: ResponseCoins :: !DBAccount -> ![JsonCoin] -> Response
+ Haskoin.Wallet.Commands: ResponseDeleteTx :: !TxHash -> !Natural -> !Natural -> Response
+ Haskoin.Wallet.Commands: ResponseError :: !Text -> Response
+ Haskoin.Wallet.Commands: ResponseFile :: !FilePath -> Response
+ Haskoin.Wallet.Commands: ResponseMnemonic :: !Text -> ![Text] -> ![[Text]] -> Response
+ Haskoin.Wallet.Commands: ResponseRestore :: ![(DBAccount, Natural, Natural)] -> Response
+ Haskoin.Wallet.Commands: ResponseRollDice :: ![Natural] -> !Text -> Response
+ Haskoin.Wallet.Commands: ResponseSync :: [SyncRes] -> Response
+ Haskoin.Wallet.Commands: ResponseTx :: !DBAccount -> !TxInfo -> Response
+ Haskoin.Wallet.Commands: ResponseTxs :: !DBAccount -> ![TxInfo] -> Response
+ Haskoin.Wallet.Commands: ResponseVersion :: !Text -> !Text -> Response
+ Haskoin.Wallet.Commands: [responseAccount] :: Response -> !DBAccount
+ Haskoin.Wallet.Commands: [responseAccounts] :: Response -> ![DBAccount]
+ Haskoin.Wallet.Commands: [responseAddress] :: Response -> !DBAddress
+ Haskoin.Wallet.Commands: [responseAddresses] :: Response -> ![DBAddress]
+ Haskoin.Wallet.Commands: [responseCoins] :: Response -> ![JsonCoin]
+ Haskoin.Wallet.Commands: [responseDBVersion] :: Response -> !Text
+ Haskoin.Wallet.Commands: [responseEntropySource] :: Response -> !Text
+ Haskoin.Wallet.Commands: [responseError] :: Response -> !Text
+ Haskoin.Wallet.Commands: [responseFreedAddrs] :: Response -> !Natural
+ Haskoin.Wallet.Commands: [responseFreedCoins] :: Response -> !Natural
+ Haskoin.Wallet.Commands: [responseMnemonic] :: Response -> ![Text]
+ Haskoin.Wallet.Commands: [responseNoSigHash] :: Response -> !TxHash
+ Haskoin.Wallet.Commands: [responsePendingTx] :: Response -> !TxInfo
+ Haskoin.Wallet.Commands: [responseRestore] :: Response -> ![(DBAccount, Natural, Natural)]
+ Haskoin.Wallet.Commands: [responseResult] :: Response -> !Bool
+ Haskoin.Wallet.Commands: [responseRollDice] :: Response -> ![Natural]
+ Haskoin.Wallet.Commands: [responseSplitMnemonic] :: Response -> ![[Text]]
+ Haskoin.Wallet.Commands: [responseSync] :: Response -> [SyncRes]
+ Haskoin.Wallet.Commands: [responseText] :: Response -> !Text
+ Haskoin.Wallet.Commands: [responseTxFile] :: Response -> !FilePath
+ Haskoin.Wallet.Commands: [responseTxs] :: Response -> ![TxInfo]
+ Haskoin.Wallet.Commands: [responseVersion] :: Response -> !Text
+ Haskoin.Wallet.Commands: askInputLine :: String -> IO String
+ Haskoin.Wallet.Commands: askInputLineHidden :: String -> IO String
+ Haskoin.Wallet.Commands: askMnemonicPass :: (MonadError String m, MonadIO m) => Natural -> m MnemonicPass
+ Haskoin.Wallet.Commands: askMnemonicWords :: String -> IO Mnemonic
+ Haskoin.Wallet.Commands: askPassword :: IO String
+ Haskoin.Wallet.Commands: catchResponseError :: Monad m => ExceptT String m Response -> m Response
+ Haskoin.Wallet.Commands: cmdAccounts :: Config -> Maybe Text -> IO Response
+ Haskoin.Wallet.Commands: cmdAddrs :: Config -> Maybe Text -> Page -> IO Response
+ Haskoin.Wallet.Commands: cmdBackup :: Ctx -> Config -> FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdCoins :: Config -> Maybe Text -> Page -> IO Response
+ Haskoin.Wallet.Commands: cmdCreateAcc :: Ctx -> Config -> Text -> Network -> Maybe Natural -> Natural -> IO Response
+ Haskoin.Wallet.Commands: cmdDeleteAcc :: Config -> Text -> Network -> HardPath -> IO Response
+ Haskoin.Wallet.Commands: cmdDeleteTx :: Ctx -> Config -> TxHash -> IO Response
+ Haskoin.Wallet.Commands: cmdDiscoverAccount :: Ctx -> Config -> Maybe Text -> IO Response
+ Haskoin.Wallet.Commands: cmdExportAcc :: Ctx -> Config -> Maybe Text -> FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdExportTx :: Config -> TxHash -> FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdImportAcc :: Ctx -> Config -> FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdImportTx :: Ctx -> Config -> Maybe Text -> FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdLabel :: Config -> Maybe Text -> Natural -> Text -> IO Response
+ Haskoin.Wallet.Commands: cmdMnemonic :: Natural -> Bool -> Natural -> IO Response
+ Haskoin.Wallet.Commands: cmdPendingTxs :: Ctx -> Config -> Maybe Text -> Page -> IO Response
+ Haskoin.Wallet.Commands: cmdPrepareTx :: Ctx -> Config -> [(Text, Text)] -> Maybe Text -> AmountUnit -> Natural -> Natural -> Bool -> Natural -> Maybe FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdReceive :: Ctx -> Config -> Maybe Text -> Maybe Text -> IO Response
+ Haskoin.Wallet.Commands: cmdRenameAcc :: Config -> Text -> Text -> IO Response
+ Haskoin.Wallet.Commands: cmdRestore :: Ctx -> Config -> FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdReviewTx :: Ctx -> Config -> Maybe Text -> FilePath -> IO Response
+ Haskoin.Wallet.Commands: cmdSendTx :: Ctx -> Config -> TxHash -> IO Response
+ Haskoin.Wallet.Commands: cmdSignTx :: Ctx -> Config -> Maybe Text -> Maybe TxHash -> Maybe FilePath -> Maybe FilePath -> Natural -> IO Response
+ Haskoin.Wallet.Commands: cmdSyncAcc :: Ctx -> Config -> Maybe Text -> Bool -> IO Response
+ Haskoin.Wallet.Commands: cmdTestAcc :: Ctx -> Config -> Maybe Text -> Natural -> IO Response
+ Haskoin.Wallet.Commands: cmdTxs :: Ctx -> Config -> Maybe Text -> Page -> IO Response
+ Haskoin.Wallet.Commands: cmdVersion :: Config -> IO Response
+ Haskoin.Wallet.Commands: commandResponse :: Ctx -> Config -> AmountUnit -> Command -> IO Response
+ Haskoin.Wallet.Commands: data Response
+ Haskoin.Wallet.Commands: instance GHC.Classes.Eq Haskoin.Wallet.Commands.Response
+ Haskoin.Wallet.Commands: instance GHC.Show.Show Haskoin.Wallet.Commands.Response
+ Haskoin.Wallet.Commands: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.Commands.Response
+ Haskoin.Wallet.Commands: jsonError :: String -> Value
+ Haskoin.Wallet.Commands: liftEitherIO :: forall (m :: Type -> Type) a. MonadIO m => IO (Either String a) -> ExceptT String m a
+ Haskoin.Wallet.Commands: parseSignInput :: forall (m :: Type -> Type). MonadUnliftIO m => Maybe TxHash -> Maybe FilePath -> Maybe FilePath -> ExceptT String (DB m) (TxSignData, Bool)
+ Haskoin.Wallet.Commands: prepareSweep :: Ctx -> Config -> Maybe Text -> FilePath -> [Text] -> Maybe FilePath -> Natural -> Natural -> IO Response
+ Haskoin.Wallet.Commands: rollDice :: Natural -> IO Response
+ Haskoin.Wallet.Commands: runDBResponse :: MonadUnliftIO m => Config -> ExceptT String (DB m) Response -> m Response
+ Haskoin.Wallet.Commands: setTxInfoOnline :: TxInfo -> TxInfo
+ Haskoin.Wallet.Commands: signSweep :: Ctx -> Config -> Maybe Text -> Maybe TxHash -> Maybe FilePath -> Maybe FilePath -> FilePath -> IO Response
+ Haskoin.Wallet.Config: Config :: Text -> Natural -> Natural -> Natural -> Natural -> Natural -> Natural -> Maybe FilePath -> Config
+ Haskoin.Wallet.Config: [configAddrBatch] :: Config -> Natural
+ Haskoin.Wallet.Config: [configCoinBatch] :: Config -> Natural
+ Haskoin.Wallet.Config: [configDatabaseFile] :: Config -> Maybe FilePath
+ Haskoin.Wallet.Config: [configGap] :: Config -> Natural
+ Haskoin.Wallet.Config: [configHost] :: Config -> Text
+ Haskoin.Wallet.Config: [configRecoveryGap] :: Config -> Natural
+ Haskoin.Wallet.Config: [configTxBatch] :: Config -> Natural
+ Haskoin.Wallet.Config: [configTxFullBatch] :: Config -> Natural
+ Haskoin.Wallet.Config: apiHost :: Network -> Config -> ApiConfig
+ Haskoin.Wallet.Config: checkHealth :: forall (m :: Type -> Type). MonadIO m => Ctx -> Network -> Config -> ExceptT String m ()
+ Haskoin.Wallet.Config: data Config
+ Haskoin.Wallet.Config: databaseFile :: Config -> IO FilePath
+ Haskoin.Wallet.Config: hwDataDirectory :: IO FilePath
+ Haskoin.Wallet.Config: initConfig :: IO Config
+ Haskoin.Wallet.Config: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.Config.Config
+ Haskoin.Wallet.Config: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.Config.Config
+ Haskoin.Wallet.Config: instance Data.Default.Class.Default Haskoin.Wallet.Config.Config
+ Haskoin.Wallet.Config: instance GHC.Classes.Eq Haskoin.Wallet.Config.Config
+ Haskoin.Wallet.Config: instance GHC.Show.Show Haskoin.Wallet.Config.Config
+ Haskoin.Wallet.Database: AccountBalance :: !Word64 -> !Word64 -> !Word64 -> AccountBalance
+ Haskoin.Wallet.Database: AddrBusy :: AddrFree
+ Haskoin.Wallet.Database: AddrExternal :: AddrType
+ Haskoin.Wallet.Database: AddrFree :: AddrFree
+ Haskoin.Wallet.Database: AddrInternal :: AddrType
+ Haskoin.Wallet.Database: AddressBalance :: !Word64 -> !Word64 -> !Word64 -> !Word64 -> !Word64 -> AddressBalance
+ Haskoin.Wallet.Database: DBAccount :: !Text -> !DBWalletId -> !Int -> !Text -> !Text -> !Int -> !Int -> !Text -> !Word64 -> !Word64 -> !Word64 -> !UTCTime -> DBAccount
+ Haskoin.Wallet.Database: DBAddress :: !Int -> !DBWalletId -> !Text -> !Text -> !Text -> !Text -> !Word64 -> !Word64 -> !Word64 -> !Word64 -> !Word64 -> !Bool -> !Bool -> !UTCTime -> DBAddress
+ Haskoin.Wallet.Database: DBBest :: !Text -> !Text -> !Int -> DBBest
+ Haskoin.Wallet.Database: DBCoin :: !DBWalletId -> !Text -> !Text -> !Text -> !Word64 -> !ByteString -> !ByteString -> !Bool -> !Bool -> !UTCTime -> DBCoin
+ Haskoin.Wallet.Database: DBPendingTx :: !DBWalletId -> !Text -> !Text -> !ByteString -> !Bool -> !UTCTime -> DBPendingTx
+ Haskoin.Wallet.Database: DBRawTx :: !Text -> !ByteString -> DBRawTx
+ Haskoin.Wallet.Database: DBTxInfo :: !DBWalletId -> !Text -> !Text -> !ByteString -> !Bool -> !ByteString -> !UTCTime -> DBTxInfo
+ Haskoin.Wallet.Database: DBVersion :: !Text -> DBVersion
+ Haskoin.Wallet.Database: DBWallet :: !Text -> DBWallet
+ Haskoin.Wallet.Database: JsonCoin :: !OutPoint -> !Address -> !Word64 -> !BlockRef -> !Natural -> !Bool -> JsonCoin
+ Haskoin.Wallet.Database: TxOffline :: TxOnline
+ Haskoin.Wallet.Database: TxOnline :: TxOnline
+ Haskoin.Wallet.Database: [accBalanceCoins] :: AccountBalance -> !Word64
+ Haskoin.Wallet.Database: [accBalanceConfirmed] :: AccountBalance -> !Word64
+ Haskoin.Wallet.Database: [accBalanceUnconfirmed] :: AccountBalance -> !Word64
+ Haskoin.Wallet.Database: [addrBalanceCoins] :: AddressBalance -> !Word64
+ Haskoin.Wallet.Database: [addrBalanceConfirmed] :: AddressBalance -> !Word64
+ Haskoin.Wallet.Database: [addrBalanceReceived] :: AddressBalance -> !Word64
+ Haskoin.Wallet.Database: [addrBalanceTxs] :: AddressBalance -> !Word64
+ Haskoin.Wallet.Database: [addrBalanceUnconfirmed] :: AddressBalance -> !Word64
+ Haskoin.Wallet.Database: [dBAccountBalanceCoins] :: DBAccount -> !Word64
+ Haskoin.Wallet.Database: [dBAccountBalanceConfirmed] :: DBAccount -> !Word64
+ Haskoin.Wallet.Database: [dBAccountBalanceUnconfirmed] :: DBAccount -> !Word64
+ Haskoin.Wallet.Database: [dBAccountCreated] :: DBAccount -> !UTCTime
+ Haskoin.Wallet.Database: [dBAccountDerivation] :: DBAccount -> !Text
+ Haskoin.Wallet.Database: [dBAccountExternal] :: DBAccount -> !Int
+ Haskoin.Wallet.Database: [dBAccountIndex] :: DBAccount -> !Int
+ Haskoin.Wallet.Database: [dBAccountInternal] :: DBAccount -> !Int
+ Haskoin.Wallet.Database: [dBAccountName] :: DBAccount -> !Text
+ Haskoin.Wallet.Database: [dBAccountNetwork] :: DBAccount -> !Text
+ Haskoin.Wallet.Database: [dBAccountWallet] :: DBAccount -> !DBWalletId
+ Haskoin.Wallet.Database: [dBAccountXPubKey] :: DBAccount -> !Text
+ Haskoin.Wallet.Database: [dBAddressAccountDerivation] :: DBAddress -> !Text
+ Haskoin.Wallet.Database: [dBAddressAccountWallet] :: DBAddress -> !DBWalletId
+ Haskoin.Wallet.Database: [dBAddressAddress] :: DBAddress -> !Text
+ Haskoin.Wallet.Database: [dBAddressBalanceCoins] :: DBAddress -> !Word64
+ Haskoin.Wallet.Database: [dBAddressBalanceConfirmed] :: DBAddress -> !Word64
+ Haskoin.Wallet.Database: [dBAddressBalanceReceived] :: DBAddress -> !Word64
+ Haskoin.Wallet.Database: [dBAddressBalanceTxs] :: DBAddress -> !Word64
+ Haskoin.Wallet.Database: [dBAddressBalanceUnconfirmed] :: DBAddress -> !Word64
+ Haskoin.Wallet.Database: [dBAddressCreated] :: DBAddress -> !UTCTime
+ Haskoin.Wallet.Database: [dBAddressDerivation] :: DBAddress -> !Text
+ Haskoin.Wallet.Database: [dBAddressFree] :: DBAddress -> !Bool
+ Haskoin.Wallet.Database: [dBAddressIndex] :: DBAddress -> !Int
+ Haskoin.Wallet.Database: [dBAddressInternal] :: DBAddress -> !Bool
+ Haskoin.Wallet.Database: [dBAddressLabel] :: DBAddress -> !Text
+ Haskoin.Wallet.Database: [dBBestBestBlock] :: DBBest -> !Text
+ Haskoin.Wallet.Database: [dBBestBestHeight] :: DBBest -> !Int
+ Haskoin.Wallet.Database: [dBBestNetwork] :: DBBest -> !Text
+ Haskoin.Wallet.Database: [dBCoinAccountDerivation] :: DBCoin -> !Text
+ Haskoin.Wallet.Database: [dBCoinAccountWallet] :: DBCoin -> !DBWalletId
+ Haskoin.Wallet.Database: [dBCoinAddress] :: DBCoin -> !Text
+ Haskoin.Wallet.Database: [dBCoinBlob] :: DBCoin -> !ByteString
+ Haskoin.Wallet.Database: [dBCoinBlockRef] :: DBCoin -> !ByteString
+ Haskoin.Wallet.Database: [dBCoinConfirmed] :: DBCoin -> !Bool
+ Haskoin.Wallet.Database: [dBCoinCreated] :: DBCoin -> !UTCTime
+ Haskoin.Wallet.Database: [dBCoinLocked] :: DBCoin -> !Bool
+ Haskoin.Wallet.Database: [dBCoinOutpoint] :: DBCoin -> !Text
+ Haskoin.Wallet.Database: [dBCoinValue] :: DBCoin -> !Word64
+ Haskoin.Wallet.Database: [dBPendingTxAccountDerivation] :: DBPendingTx -> !Text
+ Haskoin.Wallet.Database: [dBPendingTxAccountWallet] :: DBPendingTx -> !DBWalletId
+ Haskoin.Wallet.Database: [dBPendingTxBlob] :: DBPendingTx -> !ByteString
+ Haskoin.Wallet.Database: [dBPendingTxCreated] :: DBPendingTx -> !UTCTime
+ Haskoin.Wallet.Database: [dBPendingTxNosigHash] :: DBPendingTx -> !Text
+ Haskoin.Wallet.Database: [dBPendingTxOnline] :: DBPendingTx -> !Bool
+ Haskoin.Wallet.Database: [dBRawTxBlob] :: DBRawTx -> !ByteString
+ Haskoin.Wallet.Database: [dBRawTxHash] :: DBRawTx -> !Text
+ Haskoin.Wallet.Database: [dBTxInfoAccountDerivation] :: DBTxInfo -> !Text
+ Haskoin.Wallet.Database: [dBTxInfoAccountWallet] :: DBTxInfo -> !DBWalletId
+ Haskoin.Wallet.Database: [dBTxInfoBlob] :: DBTxInfo -> !ByteString
+ Haskoin.Wallet.Database: [dBTxInfoBlockRef] :: DBTxInfo -> !ByteString
+ Haskoin.Wallet.Database: [dBTxInfoConfirmed] :: DBTxInfo -> !Bool
+ Haskoin.Wallet.Database: [dBTxInfoCreated] :: DBTxInfo -> !UTCTime
+ Haskoin.Wallet.Database: [dBTxInfoTxid] :: DBTxInfo -> !Text
+ Haskoin.Wallet.Database: [dBVersionVersion] :: DBVersion -> !Text
+ Haskoin.Wallet.Database: [dBWalletFingerprint] :: DBWallet -> !Text
+ Haskoin.Wallet.Database: [jsonCoinAddress] :: JsonCoin -> !Address
+ Haskoin.Wallet.Database: [jsonCoinBlock] :: JsonCoin -> !BlockRef
+ Haskoin.Wallet.Database: [jsonCoinConfirmations] :: JsonCoin -> !Natural
+ Haskoin.Wallet.Database: [jsonCoinLocked] :: JsonCoin -> !Bool
+ Haskoin.Wallet.Database: [jsonCoinOutpoint] :: JsonCoin -> !OutPoint
+ Haskoin.Wallet.Database: [jsonCoinValue] :: JsonCoin -> !Word64
+ Haskoin.Wallet.Database: accountIndex :: DBAccount -> Natural
+ Haskoin.Wallet.Database: accountNetwork :: DBAccount -> Network
+ Haskoin.Wallet.Database: accountWallet :: DBAccount -> Fingerprint
+ Haskoin.Wallet.Database: accountXPubKey :: Ctx -> DBAccount -> XPubKey
+ Haskoin.Wallet.Database: addrDeriv :: AddrType -> SoftPath
+ Haskoin.Wallet.Database: addressPage :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> Page -> DB m [DBAddress]
+ Haskoin.Wallet.Database: addrsDerivPage :: Ctx -> SoftPath -> Page -> XPubKey -> [(Address, SoftPath)]
+ Haskoin.Wallet.Database: allAddressesMap :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DBAccountId -> ExceptT String (DB m) (Map Address SoftPath, Map Address AddressBalance)
+ Haskoin.Wallet.Database: bestAddrWithFunds :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> AddrType -> DB m (Maybe Int)
+ Haskoin.Wallet.Database: bip44Deriv :: Network -> Natural -> HardPath
+ Haskoin.Wallet.Database: checkGap :: forall (m :: Type -> Type). MonadUnliftIO m => Config -> DBAccountId -> Int -> AddrType -> ExceptT String (DB m) ()
+ Haskoin.Wallet.Database: coinPage :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DBAccountId -> Page -> ExceptT String (DB m) [JsonCoin]
+ Haskoin.Wallet.Database: dBAccountCount :: AddrType -> DBAccount -> Int
+ Haskoin.Wallet.Database: dBAccountField :: AddrType -> EntityField DBAccount Int
+ Haskoin.Wallet.Database: dBAddressFk_wallet_derivation :: DBAddress -> Key DBAccount
+ Haskoin.Wallet.Database: dBCoinFk_wallet_derivation :: DBCoin -> Key DBAccount
+ Haskoin.Wallet.Database: dBPendingTxFk_wallet_derivation :: DBPendingTx -> Key DBAccount
+ Haskoin.Wallet.Database: dBTxInfoFk_wallet_derivation :: DBTxInfo -> Key DBAccount
+ Haskoin.Wallet.Database: data AccountBalance
+ Haskoin.Wallet.Database: data AddrFree
+ Haskoin.Wallet.Database: data AddrType
+ Haskoin.Wallet.Database: data AddressBalance
+ Haskoin.Wallet.Database: data DBAccount
+ Haskoin.Wallet.Database: data DBAddress
+ Haskoin.Wallet.Database: data DBBest
+ Haskoin.Wallet.Database: data DBCoin
+ Haskoin.Wallet.Database: data DBPendingTx
+ Haskoin.Wallet.Database: data DBRawTx
+ Haskoin.Wallet.Database: data DBTxInfo
+ Haskoin.Wallet.Database: data DBVersion
+ Haskoin.Wallet.Database: data DBWallet
+ Haskoin.Wallet.Database: data JsonCoin
+ Haskoin.Wallet.Database: data TxOnline
+ Haskoin.Wallet.Database: deleteAccount :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> DB m ()
+ Haskoin.Wallet.Database: deleteCoin :: forall (m :: Type -> Type). MonadUnliftIO m => DBCoin -> DB m ()
+ Haskoin.Wallet.Database: deletePendingTx :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> TxHash -> ExceptT String (DB m) (Natural, Natural)
+ Haskoin.Wallet.Database: deletePendingTxOnline :: forall (m :: Type -> Type). MonadUnliftIO m => DBPendingTxId -> DB m ()
+ Haskoin.Wallet.Database: discoverAccGenAddrs :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> DBAccountId -> AddrType -> Int -> ExceptT String (DB m) ()
+ Haskoin.Wallet.Database: entityDefListFormigrateAll :: [EntityDef]
+ Haskoin.Wallet.Database: existsAccount :: forall (m :: Type -> Type). MonadUnliftIO m => Text -> DB m Bool
+ Haskoin.Wallet.Database: existsXPubKey :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> Ctx -> XPubKey -> DB m Bool
+ Haskoin.Wallet.Database: extDeriv :: SoftPath
+ Haskoin.Wallet.Database: fillTxInfoLabels :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> TxInfo -> DB m TxInfo
+ Haskoin.Wallet.Database: flatMaybe :: Maybe (Value (Maybe a)) -> Maybe a
+ Haskoin.Wallet.Database: fromDBAddr :: Network -> DBAddress -> Either String (Address, SoftPath)
+ Haskoin.Wallet.Database: genExtAddress :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> DBAccountId -> Text -> ExceptT String (DB m) DBAddress
+ Haskoin.Wallet.Database: genNextAddress :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> DBAccountId -> AddrType -> AddrFree -> Bool -> ExceptT String (DB m) DBAddress
+ Haskoin.Wallet.Database: getAccountById :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> ExceptT String (DB m) DBAccount
+ Haskoin.Wallet.Database: getAccountByName :: forall (m :: Type -> Type). MonadUnliftIO m => Maybe Text -> ExceptT String (DB m) (DBAccountId, DBAccount)
+ Haskoin.Wallet.Database: getAccountNames :: forall (m :: Type -> Type). MonadUnliftIO m => DB m [Text]
+ Haskoin.Wallet.Database: getAccounts :: forall (m :: Type -> Type). MonadUnliftIO m => DB m [(DBAccountId, DBAccount)]
+ Haskoin.Wallet.Database: getAddrDeriv :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DBAccountId -> Address -> DB m (Either String SoftPath)
+ Haskoin.Wallet.Database: getBest :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DB m (Maybe (BlockHash, BlockHeight))
+ Haskoin.Wallet.Database: getCoinDeriv :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DBAccountId -> Unspent -> DB m (Either String SoftPath)
+ Haskoin.Wallet.Database: getCoinsByAddr :: forall (m :: Type -> Type). MonadUnliftIO m => Text -> DB m [DBCoin]
+ Haskoin.Wallet.Database: getConfirmations :: Maybe BlockHeight -> BlockRef -> Natural
+ Haskoin.Wallet.Database: getConfirmedTxs :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> Bool -> ExceptT String (DB m) [TxHash]
+ Haskoin.Wallet.Database: getPendingTx :: forall (m :: Type -> Type). MonadUnliftIO m => TxHash -> DB m (Maybe (DBAccountId, TxSignData, TxInfoPending))
+ Haskoin.Wallet.Database: getRawTx :: forall (m :: Type -> Type). MonadUnliftIO m => TxHash -> ExceptT String (DB m) Tx
+ Haskoin.Wallet.Database: getSpendableCoins :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DBAccountId -> Natural -> ExceptT String (DB m) [Unspent]
+ Haskoin.Wallet.Database: getVersion :: forall (m :: Type -> Type). MonadUnliftIO m => DB m SemVersion
+ Haskoin.Wallet.Database: getWalletOrCreate :: forall (m :: Type -> Type). MonadUnliftIO m => Fingerprint -> DB m DBWalletId
+ Haskoin.Wallet.Database: globalMigration :: forall (m :: Type -> Type). MonadUnliftIO m => DB m ()
+ Haskoin.Wallet.Database: groupCoins :: Network -> [Unspent] -> Map Text [Unspent]
+ Haskoin.Wallet.Database: importPendingTx :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> Ctx -> DBAccountId -> TxSignData -> ExceptT String (DB m) TxHash
+ Haskoin.Wallet.Database: insertAccount :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> Ctx -> Fingerprint -> Text -> XPubKey -> ExceptT String (DB m) (DBAccountId, DBAccount)
+ Haskoin.Wallet.Database: insertAddress :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DBAccountId -> SoftPath -> Address -> AddrFree -> ExceptT String (DB m) DBAddress
+ Haskoin.Wallet.Database: insertCoin :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> Text -> Unspent -> DB m DBCoin
+ Haskoin.Wallet.Database: insertRawTx :: forall (m :: Type -> Type). MonadUnliftIO m => Tx -> DB m ()
+ Haskoin.Wallet.Database: instance (TypeError ...) => Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance (TypeError ...) => Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.Database.AccountBalance
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.Database.AddressBalance
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.Database.AccountBalance
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.Database.AddressBalance
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBBest
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBVersion
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.PersistEntity Haskoin.Wallet.Database.DBWallet
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBBest
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBVersion
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SafeToInsert Haskoin.Wallet.Database.DBWallet
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountDerivation" Haskoin.Wallet.Database.DBAddress Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountDerivation" Haskoin.Wallet.Database.DBCoin Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountDerivation" Haskoin.Wallet.Database.DBPendingTx Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountDerivation" Haskoin.Wallet.Database.DBTxInfo Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountWallet" Haskoin.Wallet.Database.DBAddress Haskoin.Wallet.Database.DBWalletId
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountWallet" Haskoin.Wallet.Database.DBCoin Haskoin.Wallet.Database.DBWalletId
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountWallet" Haskoin.Wallet.Database.DBPendingTx Haskoin.Wallet.Database.DBWalletId
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "accountWallet" Haskoin.Wallet.Database.DBTxInfo Haskoin.Wallet.Database.DBWalletId
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "address" Haskoin.Wallet.Database.DBAddress Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "address" Haskoin.Wallet.Database.DBCoin Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceCoins" Haskoin.Wallet.Database.DBAccount GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceCoins" Haskoin.Wallet.Database.DBAddress GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceConfirmed" Haskoin.Wallet.Database.DBAccount GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceConfirmed" Haskoin.Wallet.Database.DBAddress GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceReceived" Haskoin.Wallet.Database.DBAddress GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceTxs" Haskoin.Wallet.Database.DBAddress GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceUnconfirmed" Haskoin.Wallet.Database.DBAccount GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "balanceUnconfirmed" Haskoin.Wallet.Database.DBAddress GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "bestBlock" Haskoin.Wallet.Database.DBBest Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "bestHeight" Haskoin.Wallet.Database.DBBest GHC.Types.Int
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "blob" Haskoin.Wallet.Database.DBCoin Data.ByteString.Internal.Type.ByteString
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "blob" Haskoin.Wallet.Database.DBPendingTx Data.ByteString.Internal.Type.ByteString
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "blob" Haskoin.Wallet.Database.DBRawTx Data.ByteString.Internal.Type.ByteString
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "blob" Haskoin.Wallet.Database.DBTxInfo Data.ByteString.Internal.Type.ByteString
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "blockRef" Haskoin.Wallet.Database.DBCoin Data.ByteString.Internal.Type.ByteString
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "blockRef" Haskoin.Wallet.Database.DBTxInfo Data.ByteString.Internal.Type.ByteString
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "confirmed" Haskoin.Wallet.Database.DBCoin GHC.Types.Bool
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "confirmed" Haskoin.Wallet.Database.DBTxInfo GHC.Types.Bool
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "created" Haskoin.Wallet.Database.DBAccount Data.Time.Clock.Internal.UTCTime.UTCTime
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "created" Haskoin.Wallet.Database.DBAddress Data.Time.Clock.Internal.UTCTime.UTCTime
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "created" Haskoin.Wallet.Database.DBCoin Data.Time.Clock.Internal.UTCTime.UTCTime
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "created" Haskoin.Wallet.Database.DBPendingTx Data.Time.Clock.Internal.UTCTime.UTCTime
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "created" Haskoin.Wallet.Database.DBTxInfo Data.Time.Clock.Internal.UTCTime.UTCTime
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "derivation" Haskoin.Wallet.Database.DBAccount Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "derivation" Haskoin.Wallet.Database.DBAddress Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "external" Haskoin.Wallet.Database.DBAccount GHC.Types.Int
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "fingerprint" Haskoin.Wallet.Database.DBWallet Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "free" Haskoin.Wallet.Database.DBAddress GHC.Types.Bool
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "hash" Haskoin.Wallet.Database.DBRawTx Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBAccount (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBAddress (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBBest (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBCoin (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBPendingTx (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBRawTx (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBTxInfo (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBVersion (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "id" Haskoin.Wallet.Database.DBWallet (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "index" Haskoin.Wallet.Database.DBAccount GHC.Types.Int
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "index" Haskoin.Wallet.Database.DBAddress GHC.Types.Int
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "internal" Haskoin.Wallet.Database.DBAccount GHC.Types.Int
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "internal" Haskoin.Wallet.Database.DBAddress GHC.Types.Bool
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "label" Haskoin.Wallet.Database.DBAddress Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "locked" Haskoin.Wallet.Database.DBCoin GHC.Types.Bool
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "name" Haskoin.Wallet.Database.DBAccount Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "network" Haskoin.Wallet.Database.DBAccount Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "network" Haskoin.Wallet.Database.DBBest Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "nosigHash" Haskoin.Wallet.Database.DBPendingTx Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "online" Haskoin.Wallet.Database.DBPendingTx GHC.Types.Bool
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "outpoint" Haskoin.Wallet.Database.DBCoin Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "txid" Haskoin.Wallet.Database.DBTxInfo Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "value" Haskoin.Wallet.Database.DBCoin GHC.Word.Word64
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "version" Haskoin.Wallet.Database.DBVersion Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "wallet" Haskoin.Wallet.Database.DBAccount Haskoin.Wallet.Database.DBWalletId
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistEntity.SymbolToField "xPubKey" Haskoin.Wallet.Database.DBAccount Data.Text.Internal.Text
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBBest
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBVersion
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistField.PersistField Haskoin.Wallet.Database.DBWallet
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBBest
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBVersion
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.AtLeastOneUniqueKey Haskoin.Wallet.Database.DBWallet
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBBest
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBVersion
+ Haskoin.Wallet.Database: instance Database.Persist.Class.PersistUnique.OnlyOneUniqueKey Haskoin.Wallet.Database.DBWallet
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBBest
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBVersion
+ Haskoin.Wallet.Database: instance Database.Persist.Sql.Class.PersistFieldSql Haskoin.Wallet.Database.DBWallet
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.AccountBalance
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.AddrFree
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.AddrType
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.AddressBalance
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.JsonCoin
+ Haskoin.Wallet.Database: instance GHC.Classes.Eq Haskoin.Wallet.Database.TxOnline
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord Haskoin.Wallet.Database.AccountBalance
+ Haskoin.Wallet.Database: instance GHC.Classes.Ord Haskoin.Wallet.Database.AddressBalance
+ Haskoin.Wallet.Database: instance GHC.Generics.Generic (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance GHC.Generics.Generic (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance GHC.Generics.Generic (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance GHC.Read.Read (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance GHC.Read.Read Haskoin.Wallet.Database.AccountBalance
+ Haskoin.Wallet.Database: instance GHC.Read.Read Haskoin.Wallet.Database.AddressBalance
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAccount)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBAddress)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBTxInfo)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance GHC.Show.Show (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.AccountBalance
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.AddrFree
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.AddrType
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.AddressBalance
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBAccount
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBAddress
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBBest
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBCoin
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBPendingTx
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBRawTx
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBTxInfo
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBVersion
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.DBWallet
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.JsonCoin
+ Haskoin.Wallet.Database: instance GHC.Show.Show Haskoin.Wallet.Database.TxOnline
+ Haskoin.Wallet.Database: instance Haskoin.Util.Marshal.MarshalJSON Haskoin.Network.Data.Network Haskoin.Wallet.Database.JsonCoin
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.FromHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Web.Internal.HttpApiData.ToHttpApiData (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBBest)
+ Haskoin.Wallet.Database: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBCoin)
+ Haskoin.Wallet.Database: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBPendingTx)
+ Haskoin.Wallet.Database: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBRawTx)
+ Haskoin.Wallet.Database: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBVersion)
+ Haskoin.Wallet.Database: instance Web.PathPieces.PathPiece (Database.Persist.Class.PersistEntity.Key Haskoin.Wallet.Database.DBWallet)
+ Haskoin.Wallet.Database: intDeriv :: SoftPath
+ Haskoin.Wallet.Database: isAddrFree :: AddrFree -> Bool
+ Haskoin.Wallet.Database: isExtPath :: SoftPath -> Bool
+ Haskoin.Wallet.Database: isIntPath :: SoftPath -> Bool
+ Haskoin.Wallet.Database: isInternal :: AddrType -> Bool
+ Haskoin.Wallet.Database: joinMaybe :: b -> (a -> b) -> Maybe (Value (Maybe a)) -> b
+ Haskoin.Wallet.Database: migrateAll :: Migration
+ Haskoin.Wallet.Database: nextAccountDeriv :: forall (m :: Type -> Type). MonadUnliftIO m => Fingerprint -> Network -> DB m Natural
+ Haskoin.Wallet.Database: nextFreeIntAddr :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> DBAccountId -> ExceptT String (DB m) DBAddress
+ Haskoin.Wallet.Database: outpointText :: OutPoint -> Text
+ Haskoin.Wallet.Database: parseTxInfoU :: TxInfo -> ([OutPoint], [(Address, KeyIndex)], [Address])
+ Haskoin.Wallet.Database: pathIndex :: SoftPath -> KeyIndex
+ Haskoin.Wallet.Database: pendingTxHashes :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> ExceptT String (DB m) [(TxHash, DBPendingTxId)]
+ Haskoin.Wallet.Database: pendingTxPage :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> DBAccountId -> Page -> ExceptT String (DB m) [TxInfo]
+ Haskoin.Wallet.Database: refreshCoins :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> DBAccountId -> [Address] -> [Unspent] -> ExceptT String (DB m) (Int, [DBCoin])
+ Haskoin.Wallet.Database: renameAccount :: forall (m :: Type -> Type). MonadUnliftIO m => Text -> Text -> ExceptT String (DB m) DBAccount
+ Haskoin.Wallet.Database: repsertTxInfo :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> Ctx -> DBAccountId -> TxInfo -> ExceptT String (DB m) (DBTxInfo, Bool)
+ Haskoin.Wallet.Database: runDB :: MonadUnliftIO m => Config -> DB m a -> m a
+ Haskoin.Wallet.Database: setAddrLabel :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> Int -> Text -> ExceptT String (DB m) DBAddress
+ Haskoin.Wallet.Database: setAddrsFree :: forall (m :: Type -> Type). MonadUnliftIO m => AddrFree -> [Text] -> DB m Natural
+ Haskoin.Wallet.Database: setLockCoin :: forall (m :: Type -> Type). MonadUnliftIO m => OutPoint -> Bool -> DB m Natural
+ Haskoin.Wallet.Database: setPendingTxOnline :: forall (m :: Type -> Type). MonadUnliftIO m => TxHash -> DB m Natural
+ Haskoin.Wallet.Database: setVersion :: forall (m :: Type -> Type). MonadUnliftIO m => SemVersion -> DB m ()
+ Haskoin.Wallet.Database: smallestUnused :: [Natural] -> Natural
+ Haskoin.Wallet.Database: textToOutpoint :: Text -> Either String OutPoint
+ Haskoin.Wallet.Database: toJsonCoin :: Network -> Maybe BlockHeight -> DBCoin -> Either String JsonCoin
+ Haskoin.Wallet.Database: txsPage :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> DBAccountId -> Page -> ExceptT String (DB m) [TxInfo]
+ Haskoin.Wallet.Database: type DB (m :: Type -> Type) = ReaderT SqlBackend NoLoggingT ResourceT m
+ Haskoin.Wallet.Database: type DBAccountId = Key DBAccount
+ Haskoin.Wallet.Database: type DBAddressId = Key DBAddress
+ Haskoin.Wallet.Database: type DBBestId = Key DBBest
+ Haskoin.Wallet.Database: type DBCoinId = Key DBCoin
+ Haskoin.Wallet.Database: type DBPendingTxId = Key DBPendingTx
+ Haskoin.Wallet.Database: type DBRawTxId = Key DBRawTx
+ Haskoin.Wallet.Database: type DBTxInfoId = Key DBTxInfo
+ Haskoin.Wallet.Database: type DBVersionId = Key DBVersion
+ Haskoin.Wallet.Database: type DBWalletId = Key DBWallet
+ Haskoin.Wallet.Database: updateAccountBalances :: forall (m :: Type -> Type). MonadUnliftIO m => DBAccountId -> DB m DBAccount
+ Haskoin.Wallet.Database: updateAddressBalances :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> [Balance] -> ExceptT String (DB m) ()
+ Haskoin.Wallet.Database: updateBest :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> BlockHash -> BlockHeight -> DB m ()
+ Haskoin.Wallet.Database: updateCoin :: forall (m :: Type -> Type). MonadUnliftIO m => Unspent -> DB m ()
+ Haskoin.Wallet.Database: xPubIndex :: XPubKey -> Natural
+ Haskoin.Wallet.Entropy: askInputDice :: IO Natural
+ Haskoin.Wallet.Entropy: base6ToBool :: Natural -> [Bool]
+ Haskoin.Wallet.Entropy: base6ToWord8 :: [Natural] -> [Bool] -> ([Word8], [Bool])
+ Haskoin.Wallet.Entropy: boolsToWord8 :: [Bool] -> Word8
+ Haskoin.Wallet.Entropy: devRandom :: Natural -> IO ByteString
+ Haskoin.Wallet.Entropy: genMnemonic :: forall (m :: Type -> Type). MonadIO m => Natural -> Bool -> Natural -> ExceptT String m (Text, Mnemonic, [Mnemonic])
+ Haskoin.Wallet.Entropy: getDiceEntropy :: Natural -> IO ByteString
+ Haskoin.Wallet.Entropy: mergeMnemonicParts :: [Text] -> Either String Mnemonic
+ Haskoin.Wallet.Entropy: packBools :: [Bool] -> ByteString
+ Haskoin.Wallet.Entropy: splitEntropy :: Natural -> ByteString -> IO [ByteString]
+ Haskoin.Wallet.Entropy: splitEntropyWith :: ByteString -> [ByteString] -> [ByteString]
+ Haskoin.Wallet.Entropy: systemEntropy :: Natural -> IO (Text, ByteString)
+ Haskoin.Wallet.Entropy: word8ToBase6 :: Word8 -> [Natural]
+ Haskoin.Wallet.Entropy: xorBytes :: ByteString -> ByteString -> ByteString
+ Haskoin.Wallet.FileIO: PubKeyDoc :: !XPubKey -> !Network -> !Text -> !Fingerprint -> PubKeyDoc
+ Haskoin.Wallet.FileIO: TxSignData :: !Tx -> ![Tx] -> ![SoftPath] -> ![SoftPath] -> !Bool -> TxSignData
+ Haskoin.Wallet.FileIO: [documentName] :: PubKeyDoc -> !Text
+ Haskoin.Wallet.FileIO: [documentNetwork] :: PubKeyDoc -> !Network
+ Haskoin.Wallet.FileIO: [documentPubKey] :: PubKeyDoc -> !XPubKey
+ Haskoin.Wallet.FileIO: [documentWallet] :: PubKeyDoc -> !Fingerprint
+ Haskoin.Wallet.FileIO: [txSignDataInputPaths] :: TxSignData -> ![SoftPath]
+ Haskoin.Wallet.FileIO: [txSignDataInputs] :: TxSignData -> ![Tx]
+ Haskoin.Wallet.FileIO: [txSignDataOutputPaths] :: TxSignData -> ![SoftPath]
+ Haskoin.Wallet.FileIO: [txSignDataSigned] :: TxSignData -> !Bool
+ Haskoin.Wallet.FileIO: [txSignDataTx] :: TxSignData -> !Tx
+ Haskoin.Wallet.FileIO: checkPathFree :: forall (m :: Type -> Type). MonadIO m => FilePath -> ExceptT String m ()
+ Haskoin.Wallet.FileIO: data PubKeyDoc
+ Haskoin.Wallet.FileIO: data TxSignData
+ Haskoin.Wallet.FileIO: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.FileIO.TxSignData
+ Haskoin.Wallet.FileIO: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.FileIO.TxSignData
+ Haskoin.Wallet.FileIO: instance GHC.Classes.Eq Haskoin.Wallet.FileIO.PubKeyDoc
+ Haskoin.Wallet.FileIO: instance GHC.Classes.Eq Haskoin.Wallet.FileIO.TxSignData
+ Haskoin.Wallet.FileIO: instance GHC.Show.Show Haskoin.Wallet.FileIO.PubKeyDoc
+ Haskoin.Wallet.FileIO: instance GHC.Show.Show Haskoin.Wallet.FileIO.TxSignData
+ Haskoin.Wallet.FileIO: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.FileIO.PubKeyDoc
+ Haskoin.Wallet.FileIO: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.FileIO.TxSignData
+ Haskoin.Wallet.FileIO: parseSecKeysFile :: Network -> [[Text]] -> [SecKey]
+ Haskoin.Wallet.FileIO: readFileWords :: FilePath -> IO [[Text]]
+ Haskoin.Wallet.FileIO: readJsonFile :: FromJSON a => FilePath -> IO (Either String a)
+ Haskoin.Wallet.FileIO: readMarshalFile :: MarshalJSON s a => s -> FilePath -> IO (Either String a)
+ Haskoin.Wallet.FileIO: removeComments :: [[Text]] -> [[Text]]
+ Haskoin.Wallet.FileIO: withParser :: (Text -> Maybe a) -> [[Text]] -> [a]
+ Haskoin.Wallet.FileIO: writeJsonFile :: FilePath -> Value -> IO ()
+ Haskoin.Wallet.FileIO: writeMarshalFile :: MarshalJSON s a => s -> FilePath -> a -> IO ()
+ Haskoin.Wallet.Main: clientMain :: IO ()
+ Haskoin.Wallet.Main: jsonPrinter :: Ctx -> Response -> IO ()
+ Haskoin.Wallet.Migration: databaseVersion :: forall (m :: Type -> Type). MonadUnliftIO m => DB m (Maybe SemVersion)
+ Haskoin.Wallet.Migration: getDatabaseTables :: forall (m :: Type -> Type). MonadUnliftIO m => DB m [Text]
+ Haskoin.Wallet.Migration: migrateDB :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> Config -> DB m ()
+ Haskoin.Wallet.Migration: migrateDB_ :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> SemVersion -> ExceptT String (DB m) ()
+ Haskoin.Wallet.Migration: printDBMigration :: SemVersion -> SemVersion -> IO ()
+ Haskoin.Wallet.Migration.SemVersion: VerMajor :: !Natural -> SemVersion
+ Haskoin.Wallet.Migration.SemVersion: VerMinor :: !Natural -> !Natural -> SemVersion
+ Haskoin.Wallet.Migration.SemVersion: VerPatch :: !Natural -> Natural -> Natural -> SemVersion
+ Haskoin.Wallet.Migration.SemVersion: currentVersion :: SemVersion
+ Haskoin.Wallet.Migration.SemVersion: currentVersionStr :: IsString a => a
+ Haskoin.Wallet.Migration.SemVersion: data SemVersion
+ Haskoin.Wallet.Migration.SemVersion: instance GHC.Classes.Eq Haskoin.Wallet.Migration.SemVersion.SemVersion
+ Haskoin.Wallet.Migration.SemVersion: instance GHC.Show.Show Haskoin.Wallet.Migration.SemVersion.SemVersion
+ Haskoin.Wallet.Migration.SemVersion: parseSemVersion :: String -> SemVersion
+ Haskoin.Wallet.Migration.SemVersion: toMajor :: SemVersion -> SemVersion
+ Haskoin.Wallet.Migration.SemVersion: toMinor :: SemVersion -> SemVersion
+ Haskoin.Wallet.Migration.SemVersion: verString :: SemVersion -> String
+ Haskoin.Wallet.Migration.V0_9_0: OldMyInputs :: !Natural -> !SoftPath -> [SigInput] -> OldMyInputs
+ Haskoin.Wallet.Migration.V0_9_0: OldMyOutputs :: !Natural -> !SoftPath -> OldMyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: OldTxInfo :: !TxHash -> !TxType -> !Integer -> !Map Address OldMyOutputs -> !Map Address Natural -> ![StoreOutput] -> !Map Address OldMyInputs -> !Map Address OtherInputs -> ![StoreInput] -> !Natural -> !Natural -> !Natural -> !BlockRef -> !Natural -> OldTxInfo
+ Haskoin.Wallet.Migration.V0_9_0: [oldMyInputsPath] :: OldMyInputs -> !SoftPath
+ Haskoin.Wallet.Migration.V0_9_0: [oldMyInputsSigInput] :: OldMyInputs -> [SigInput]
+ Haskoin.Wallet.Migration.V0_9_0: [oldMyInputsValue] :: OldMyInputs -> !Natural
+ Haskoin.Wallet.Migration.V0_9_0: [oldMyOutputsPath] :: OldMyOutputs -> !SoftPath
+ Haskoin.Wallet.Migration.V0_9_0: [oldMyOutputsValue] :: OldMyOutputs -> !Natural
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoAmount] :: OldTxInfo -> !Integer
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoBlockRef] :: OldTxInfo -> !BlockRef
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoConfirmations] :: OldTxInfo -> !Natural
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoFeeByte] :: OldTxInfo -> !Natural
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoFee] :: OldTxInfo -> !Natural
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoHash] :: OldTxInfo -> !TxHash
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoMyInputs] :: OldTxInfo -> !Map Address OldMyInputs
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoMyOutputs] :: OldTxInfo -> !Map Address OldMyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoNonStdInputs] :: OldTxInfo -> ![StoreInput]
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoNonStdOutputs] :: OldTxInfo -> ![StoreOutput]
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoOtherInputs] :: OldTxInfo -> !Map Address OtherInputs
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoOtherOutputs] :: OldTxInfo -> !Map Address Natural
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoSize] :: OldTxInfo -> !Natural
+ Haskoin.Wallet.Migration.V0_9_0: [oldTxInfoType] :: OldTxInfo -> !TxType
+ Haskoin.Wallet.Migration.V0_9_0: data OldMyInputs
+ Haskoin.Wallet.Migration.V0_9_0: data OldMyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: data OldTxInfo
+ Haskoin.Wallet.Migration.V0_9_0: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.Migration.V0_9_0.OldMyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.Migration.V0_9_0.OldMyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: instance GHC.Classes.Eq Haskoin.Wallet.Migration.V0_9_0.OldMyInputs
+ Haskoin.Wallet.Migration.V0_9_0: instance GHC.Classes.Eq Haskoin.Wallet.Migration.V0_9_0.OldMyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: instance GHC.Classes.Eq Haskoin.Wallet.Migration.V0_9_0.OldTxInfo
+ Haskoin.Wallet.Migration.V0_9_0: instance GHC.Show.Show Haskoin.Wallet.Migration.V0_9_0.OldMyInputs
+ Haskoin.Wallet.Migration.V0_9_0: instance GHC.Show.Show Haskoin.Wallet.Migration.V0_9_0.OldMyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: instance GHC.Show.Show Haskoin.Wallet.Migration.V0_9_0.OldTxInfo
+ Haskoin.Wallet.Migration.V0_9_0: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Internal.Context.Ctx) Haskoin.Wallet.Migration.V0_9_0.OldTxInfo
+ Haskoin.Wallet.Migration.V0_9_0: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.Migration.V0_9_0.OldMyInputs
+ Haskoin.Wallet.Migration.V0_9_0: migrateDB :: forall (m :: Type -> Type). MonadUnliftIO m => Ctx -> ExceptT String (DB m) ()
+ Haskoin.Wallet.Migration.V0_9_0: migrateFrom :: SemVersion
+ Haskoin.Wallet.Migration.V0_9_0: migrateMyInputs :: OldMyInputs -> MyInputs
+ Haskoin.Wallet.Migration.V0_9_0: migrateMyOutputs :: OldMyOutputs -> MyOutputs
+ Haskoin.Wallet.Migration.V0_9_0: migrateTo :: SemVersion
+ Haskoin.Wallet.Migration.V0_9_0: migrateTxInfo :: OldTxInfo -> TxInfo
+ Haskoin.Wallet.Parser: CommandAccounts :: !Maybe Text -> Command
+ Haskoin.Wallet.Parser: CommandAddrs :: !Maybe Text -> !Page -> Command
+ Haskoin.Wallet.Parser: CommandBackup :: !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandCoins :: !Maybe Text -> !Page -> Command
+ Haskoin.Wallet.Parser: CommandCreateAcc :: !Text -> !Network -> !Maybe Natural -> !Natural -> Command
+ Haskoin.Wallet.Parser: CommandDeleteAcc :: !Text -> !Network -> !HardPath -> Command
+ Haskoin.Wallet.Parser: CommandDeleteTx :: !TxHash -> Command
+ Haskoin.Wallet.Parser: CommandDiscoverAcc :: !Maybe Text -> Command
+ Haskoin.Wallet.Parser: CommandExportAcc :: !Maybe Text -> !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandExportTx :: !TxHash -> !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandImportAcc :: !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandImportTx :: !Maybe Text -> !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandLabel :: !Maybe Text -> !Natural -> !Text -> Command
+ Haskoin.Wallet.Parser: CommandMnemonic :: !Natural -> !Bool -> !Natural -> Command
+ Haskoin.Wallet.Parser: CommandPendingTxs :: !Maybe Text -> !Page -> Command
+ Haskoin.Wallet.Parser: CommandPrepareSweep :: !Maybe Text -> !FilePath -> ![Text] -> !Maybe FilePath -> !Natural -> !Natural -> Command
+ Haskoin.Wallet.Parser: CommandPrepareTx :: ![(Text, Text)] -> !Maybe Text -> !Natural -> !Natural -> !Bool -> !Natural -> !Maybe FilePath -> Command
+ Haskoin.Wallet.Parser: CommandReceive :: !Maybe Text -> !Maybe Text -> Command
+ Haskoin.Wallet.Parser: CommandRenameAcc :: !Text -> !Text -> Command
+ Haskoin.Wallet.Parser: CommandRestore :: !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandReviewTx :: !Maybe Text -> !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandRollDice :: !Natural -> Command
+ Haskoin.Wallet.Parser: CommandSendTx :: !TxHash -> Command
+ Haskoin.Wallet.Parser: CommandSignSweep :: !Maybe Text -> !Maybe TxHash -> !Maybe FilePath -> !Maybe FilePath -> !FilePath -> Command
+ Haskoin.Wallet.Parser: CommandSignTx :: !Maybe Text -> !Maybe TxHash -> !Maybe FilePath -> !Maybe FilePath -> !Natural -> Command
+ Haskoin.Wallet.Parser: CommandSyncAcc :: !Maybe Text -> !Bool -> Command
+ Haskoin.Wallet.Parser: CommandTestAcc :: !Maybe Text -> !Natural -> Command
+ Haskoin.Wallet.Parser: CommandTxs :: !Maybe Text -> !Page -> Command
+ Haskoin.Wallet.Parser: CommandVersion :: Command
+ Haskoin.Wallet.Parser: [commandAddrIndex] :: Command -> !Natural
+ Haskoin.Wallet.Parser: [commandCount] :: Command -> !Natural
+ Haskoin.Wallet.Parser: [commandDerivationMaybe] :: Command -> !Maybe Natural
+ Haskoin.Wallet.Parser: [commandDerivation] :: Command -> !HardPath
+ Haskoin.Wallet.Parser: [commandDust] :: Command -> !Natural
+ Haskoin.Wallet.Parser: [commandEntropy] :: Command -> !Natural
+ Haskoin.Wallet.Parser: [commandFeeByte] :: Command -> !Natural
+ Haskoin.Wallet.Parser: [commandFilePath] :: Command -> !FilePath
+ Haskoin.Wallet.Parser: [commandFull] :: Command -> !Bool
+ Haskoin.Wallet.Parser: [commandInputFileMaybe] :: Command -> !Maybe FilePath
+ Haskoin.Wallet.Parser: [commandLabel] :: Command -> !Text
+ Haskoin.Wallet.Parser: [commandMaybeAcc] :: Command -> !Maybe Text
+ Haskoin.Wallet.Parser: [commandMaybeLabel] :: Command -> !Maybe Text
+ Haskoin.Wallet.Parser: [commandMinConf] :: Command -> !Natural
+ Haskoin.Wallet.Parser: [commandName] :: Command -> !Text
+ Haskoin.Wallet.Parser: [commandNetwork] :: Command -> !Network
+ Haskoin.Wallet.Parser: [commandNewName] :: Command -> !Text
+ Haskoin.Wallet.Parser: [commandNoSigHashMaybe] :: Command -> !Maybe TxHash
+ Haskoin.Wallet.Parser: [commandNoSigHash] :: Command -> !TxHash
+ Haskoin.Wallet.Parser: [commandOldName] :: Command -> !Text
+ Haskoin.Wallet.Parser: [commandOutputFileMaybe] :: Command -> !Maybe FilePath
+ Haskoin.Wallet.Parser: [commandPage] :: Command -> !Page
+ Haskoin.Wallet.Parser: [commandRcptPay] :: Command -> !Bool
+ Haskoin.Wallet.Parser: [commandRecipients] :: Command -> ![(Text, Text)]
+ Haskoin.Wallet.Parser: [commandSecKeyPath] :: Command -> !FilePath
+ Haskoin.Wallet.Parser: [commandSplitIn] :: Command -> !Natural
+ Haskoin.Wallet.Parser: [commandSweepTo] :: Command -> ![Text]
+ Haskoin.Wallet.Parser: [commandUseDice] :: Command -> !Bool
+ Haskoin.Wallet.Parser: accountArg :: String -> Parser Text
+ Haskoin.Wallet.Parser: accountCompleter :: String -> IO [String]
+ Haskoin.Wallet.Parser: accountOption :: Parser (Maybe Text)
+ Haskoin.Wallet.Parser: accountsParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: addrIndexArg :: Parser Natural
+ Haskoin.Wallet.Parser: addressArg :: Parser Text
+ Haskoin.Wallet.Parser: addressFileOption :: Parser (Maybe FilePath)
+ Haskoin.Wallet.Parser: addrsParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: amountArg :: Parser Text
+ Haskoin.Wallet.Parser: backupParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: bitOption :: Parser AmountUnit
+ Haskoin.Wallet.Parser: coinsParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: commandParser :: Parser Command
+ Haskoin.Wallet.Parser: createAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: data Command
+ Haskoin.Wallet.Parser: deleteAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: deleteTxParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: derivationOption :: Parser Natural
+ Haskoin.Wallet.Parser: diceCountArg :: Parser Natural
+ Haskoin.Wallet.Parser: diceOption :: Parser Bool
+ Haskoin.Wallet.Parser: discoverAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: dustOption :: Parser Natural
+ Haskoin.Wallet.Parser: entropyOption :: Parser Natural
+ Haskoin.Wallet.Parser: exportAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: exportTxParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: feeOption :: Parser Natural
+ Haskoin.Wallet.Parser: fileArgument :: String -> Parser FilePath
+ Haskoin.Wallet.Parser: fullDerivationOption :: Parser HardPath
+ Haskoin.Wallet.Parser: fullOption :: Parser Bool
+ Haskoin.Wallet.Parser: importAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: importExportAccFooter :: String
+ Haskoin.Wallet.Parser: importExportTxFooter :: String
+ Haskoin.Wallet.Parser: importTxParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: inputFileMaybeOption :: Parser (Maybe FilePath)
+ Haskoin.Wallet.Parser: instance GHC.Classes.Eq Haskoin.Wallet.Parser.Command
+ Haskoin.Wallet.Parser: instance GHC.Show.Show Haskoin.Wallet.Parser.Command
+ Haskoin.Wallet.Parser: jsonOption :: Parser Bool
+ Haskoin.Wallet.Parser: labelOption :: Parser (Maybe Text)
+ Haskoin.Wallet.Parser: labelParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: limitOption :: Parser Natural
+ Haskoin.Wallet.Parser: minConfOption :: Parser Natural
+ Haskoin.Wallet.Parser: mnemonicParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: networkArg :: Parser Network
+ Haskoin.Wallet.Parser: networkOption :: Parser Network
+ Haskoin.Wallet.Parser: nosigHashArg :: Parser TxHash
+ Haskoin.Wallet.Parser: offline :: Doc -> Maybe Doc
+ Haskoin.Wallet.Parser: offsetOption :: Parser Natural
+ Haskoin.Wallet.Parser: outputFileMaybeOption :: Parser (Maybe FilePath)
+ Haskoin.Wallet.Parser: parserMain :: IO (Command, AmountUnit, Bool)
+ Haskoin.Wallet.Parser: pendingTxsParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: prepareSweepParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: prepareTxParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: programParser :: ParserInfo (Command, AmountUnit, Bool)
+ Haskoin.Wallet.Parser: prvKeyFileOption :: Parser FilePath
+ Haskoin.Wallet.Parser: rcptPayOption :: Parser Bool
+ Haskoin.Wallet.Parser: receiveParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: recipientArg :: Parser (Text, Text)
+ Haskoin.Wallet.Parser: renameAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: restoreParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: reviewTxParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: rollDiceParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: satoshiOption :: Parser AmountUnit
+ Haskoin.Wallet.Parser: sendTxParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: signSweepParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: signTxParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: splitInOption :: Parser Natural
+ Haskoin.Wallet.Parser: sweepFromOption :: Parser Text
+ Haskoin.Wallet.Parser: sweepToOption :: Parser Text
+ Haskoin.Wallet.Parser: syncAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: testAccParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: textArg :: String -> Parser Text
+ Haskoin.Wallet.Parser: txsParser :: ParserInfo Command
+ Haskoin.Wallet.Parser: unitOption :: Parser AmountUnit
+ Haskoin.Wallet.Parser: versionParser :: ParserInfo Command
+ Haskoin.Wallet.PrettyPrinter: (<+>) :: Printer -> Printer -> Printer
+ Haskoin.Wallet.PrettyPrinter: PConcat :: !Printer -> !Printer -> Printer
+ Haskoin.Wallet.PrettyPrinter: PEmpty :: Printer
+ Haskoin.Wallet.PrettyPrinter: PNest :: !Natural -> !Printer -> Printer
+ Haskoin.Wallet.PrettyPrinter: PNewline :: !Printer -> Printer
+ Haskoin.Wallet.PrettyPrinter: PNonEmpty :: Printer
+ Haskoin.Wallet.PrettyPrinter: PText :: [SGR] -> !String -> Printer
+ Haskoin.Wallet.PrettyPrinter: accountPrinter :: AmountUnit -> DBAccount -> Printer
+ Haskoin.Wallet.PrettyPrinter: addressPrinter :: AmountUnit -> Natural -> DBAddress -> Printer
+ Haskoin.Wallet.PrettyPrinter: amountPrinter :: AmountUnit -> Word64 -> Printer
+ Haskoin.Wallet.PrettyPrinter: block :: Natural -> String -> String
+ Haskoin.Wallet.PrettyPrinter: coinPrinter :: Network -> AmountUnit -> JsonCoin -> Printer
+ Haskoin.Wallet.PrettyPrinter: data Printer
+ Haskoin.Wallet.PrettyPrinter: feePrinter :: AmountUnit -> Natural -> Natural -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatAccount :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatAddress :: Bool -> String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatBlockHash :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatDeriv :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatDice :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatError :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatFalse :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatFilePath :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatKey :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatLabel :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatMnemonic :: Bool -> String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatNegAmount :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatNoSigTxHash :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatPosAmount :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatTitle :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatTrue :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatTxHash :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatValue :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: formatZeroAmount :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: hsep :: [Printer] -> Printer
+ Haskoin.Wallet.PrettyPrinter: instance GHC.Base.Monoid Haskoin.Wallet.PrettyPrinter.Printer
+ Haskoin.Wallet.PrettyPrinter: instance GHC.Base.Semigroup Haskoin.Wallet.PrettyPrinter.Printer
+ Haskoin.Wallet.PrettyPrinter: integerAmountPrinter :: AmountUnit -> Integer -> Printer
+ Haskoin.Wallet.PrettyPrinter: integerAmountPrinterWith :: (String -> Printer) -> AmountUnit -> Integer -> Printer
+ Haskoin.Wallet.PrettyPrinter: isEmptyPrinter :: Printer -> Bool
+ Haskoin.Wallet.PrettyPrinter: keyPrinter :: Natural -> Text -> Printer
+ Haskoin.Wallet.PrettyPrinter: mnemonicPrinter :: Bool -> [Text] -> Printer
+ Haskoin.Wallet.PrettyPrinter: naturalPrinter :: Natural -> Printer
+ Haskoin.Wallet.PrettyPrinter: nest :: Natural -> Printer -> Printer
+ Haskoin.Wallet.PrettyPrinter: parseAddr :: Network -> Address -> String
+ Haskoin.Wallet.PrettyPrinter: partsPrinter :: [[Text]] -> [Printer]
+ Haskoin.Wallet.PrettyPrinter: prettyPrinter :: AmountUnit -> Response -> IO ()
+ Haskoin.Wallet.PrettyPrinter: printFormat :: [SGR] -> String -> IO ()
+ Haskoin.Wallet.PrettyPrinter: rblock :: Natural -> String -> String
+ Haskoin.Wallet.PrettyPrinter: renderIO :: Printer -> IO ()
+ Haskoin.Wallet.PrettyPrinter: text :: String -> Printer
+ Haskoin.Wallet.PrettyPrinter: txInfoPrinter :: Network -> AmountUnit -> TxInfo -> Printer
+ Haskoin.Wallet.PrettyPrinter: unitPrinter :: AmountUnit -> Integer -> Printer
+ Haskoin.Wallet.PrettyPrinter: vcat :: [Printer] -> Printer
+ Haskoin.Wallet.Signing: MnemonicPass :: !Text -> !Text -> MnemonicPass
+ Haskoin.Wallet.Signing: [mnemonicPass] :: MnemonicPass -> !Text
+ Haskoin.Wallet.Signing: [mnemonicWords] :: MnemonicPass -> !Text
+ Haskoin.Wallet.Signing: buildSweepSignData :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> Ctx -> Config -> DBAccountId -> [SecKey] -> [Address] -> Natural -> Natural -> ExceptT String (DB m) TxSignData
+ Haskoin.Wallet.Signing: buildSweepTx :: Network -> Ctx -> StdGen -> [Unspent] -> [Address] -> Natural -> Natural -> Either String Tx
+ Haskoin.Wallet.Signing: buildTxSignData :: forall (m :: Type -> Type). MonadUnliftIO m => Network -> Ctx -> Config -> StdGen -> DBAccountId -> [(Address, Natural)] -> Natural -> Natural -> Bool -> Natural -> ExceptT String (DB m) TxSignData
+ Haskoin.Wallet.Signing: buildWalletTx :: Network -> Ctx -> StdGen -> [(Address, Natural)] -> Address -> [Unspent] -> Natural -> Natural -> Bool -> Either String (Tx, [Unspent])
+ Haskoin.Wallet.Signing: data MnemonicPass
+ Haskoin.Wallet.Signing: genPossibleAddrs :: Network -> Ctx -> SecKey -> [Address]
+ Haskoin.Wallet.Signing: instance GHC.Classes.Eq Haskoin.Wallet.Signing.MnemonicPass
+ Haskoin.Wallet.Signing: instance GHC.Show.Show Haskoin.Wallet.Signing.MnemonicPass
+ Haskoin.Wallet.Signing: makeRcptsPay :: Natural -> [(Address, Natural)] -> Either String [(Address, Natural)]
+ Haskoin.Wallet.Signing: noEmptyInputs :: Tx -> Bool
+ Haskoin.Wallet.Signing: randomRange :: (Random a, MonadState StdGen m) => a -> a -> m a
+ Haskoin.Wallet.Signing: randomShuffle :: MonadState StdGen m => [a] -> m [a]
+ Haskoin.Wallet.Signing: signTxInfo :: Tx -> TxInfo -> TxInfo
+ Haskoin.Wallet.Signing: signTxWithKeys :: Network -> Ctx -> TxSignData -> XPubKey -> [SecKey] -> Either String (TxSignData, TxInfo)
+ Haskoin.Wallet.Signing: signWalletTx :: Network -> Ctx -> TxSignData -> XPrvKey -> Either String (TxSignData, TxInfo)
+ Haskoin.Wallet.Signing: signingKey :: Network -> Ctx -> MnemonicPass -> Natural -> Either String XPrvKey
+ Haskoin.Wallet.Signing: verifyTxInfo :: Network -> Ctx -> Tx -> TxInfo -> Bool
+ Haskoin.Wallet.Signing: walletFingerprint :: Network -> Ctx -> MnemonicPass -> Either String Fingerprint
+ Haskoin.Wallet.TxInfo: MyInputs :: !Natural -> !SoftPath -> !Text -> [SigInput] -> MyInputs
+ Haskoin.Wallet.TxInfo: MyOutputs :: !Natural -> !SoftPath -> !Text -> MyOutputs
+ Haskoin.Wallet.TxInfo: OtherInputs :: !Natural -> [SigInput] -> OtherInputs
+ Haskoin.Wallet.TxInfo: TxCredit :: TxType
+ Haskoin.Wallet.TxInfo: TxDebit :: TxType
+ Haskoin.Wallet.TxInfo: TxInfo :: !Maybe TxHash -> !TxType -> !Integer -> !Map Address MyOutputs -> !Map Address Natural -> ![StoreOutput] -> !Map Address MyInputs -> !Map Address OtherInputs -> ![StoreInput] -> !Natural -> !Natural -> !Natural -> !BlockRef -> !Natural -> !Maybe TxInfoPending -> TxInfo
+ Haskoin.Wallet.TxInfo: TxInfoPending :: !TxHash -> !Bool -> !Bool -> TxInfoPending
+ Haskoin.Wallet.TxInfo: TxInternal :: TxType
+ Haskoin.Wallet.TxInfo: [myInputsLabel] :: MyInputs -> !Text
+ Haskoin.Wallet.TxInfo: [myInputsPath] :: MyInputs -> !SoftPath
+ Haskoin.Wallet.TxInfo: [myInputsSigInput] :: MyInputs -> [SigInput]
+ Haskoin.Wallet.TxInfo: [myInputsValue] :: MyInputs -> !Natural
+ Haskoin.Wallet.TxInfo: [myOutputsLabel] :: MyOutputs -> !Text
+ Haskoin.Wallet.TxInfo: [myOutputsPath] :: MyOutputs -> !SoftPath
+ Haskoin.Wallet.TxInfo: [myOutputsValue] :: MyOutputs -> !Natural
+ Haskoin.Wallet.TxInfo: [otherInputsSigInput] :: OtherInputs -> [SigInput]
+ Haskoin.Wallet.TxInfo: [otherInputsValue] :: OtherInputs -> !Natural
+ Haskoin.Wallet.TxInfo: [pendingNosigHash] :: TxInfoPending -> !TxHash
+ Haskoin.Wallet.TxInfo: [pendingOnline] :: TxInfoPending -> !Bool
+ Haskoin.Wallet.TxInfo: [pendingSigned] :: TxInfoPending -> !Bool
+ Haskoin.Wallet.TxInfo: [txInfoAmount] :: TxInfo -> !Integer
+ Haskoin.Wallet.TxInfo: [txInfoBlockRef] :: TxInfo -> !BlockRef
+ Haskoin.Wallet.TxInfo: [txInfoConfirmations] :: TxInfo -> !Natural
+ Haskoin.Wallet.TxInfo: [txInfoFeeByte] :: TxInfo -> !Natural
+ Haskoin.Wallet.TxInfo: [txInfoFee] :: TxInfo -> !Natural
+ Haskoin.Wallet.TxInfo: [txInfoHash] :: TxInfo -> !Maybe TxHash
+ Haskoin.Wallet.TxInfo: [txInfoMyInputs] :: TxInfo -> !Map Address MyInputs
+ Haskoin.Wallet.TxInfo: [txInfoMyOutputs] :: TxInfo -> !Map Address MyOutputs
+ Haskoin.Wallet.TxInfo: [txInfoNonStdInputs] :: TxInfo -> ![StoreInput]
+ Haskoin.Wallet.TxInfo: [txInfoNonStdOutputs] :: TxInfo -> ![StoreOutput]
+ Haskoin.Wallet.TxInfo: [txInfoOtherInputs] :: TxInfo -> !Map Address OtherInputs
+ Haskoin.Wallet.TxInfo: [txInfoOtherOutputs] :: TxInfo -> !Map Address Natural
+ Haskoin.Wallet.TxInfo: [txInfoPending] :: TxInfo -> !Maybe TxInfoPending
+ Haskoin.Wallet.TxInfo: [txInfoSize] :: TxInfo -> !Natural
+ Haskoin.Wallet.TxInfo: [txInfoType] :: TxInfo -> !TxType
+ Haskoin.Wallet.TxInfo: data MyInputs
+ Haskoin.Wallet.TxInfo: data MyOutputs
+ Haskoin.Wallet.TxInfo: data OtherInputs
+ Haskoin.Wallet.TxInfo: data TxInfo
+ Haskoin.Wallet.TxInfo: data TxInfoPending
+ Haskoin.Wallet.TxInfo: data TxType
+ Haskoin.Wallet.TxInfo: inputAddressMap :: [StoreInput] -> (Map Address Natural, [StoreInput])
+ Haskoin.Wallet.TxInfo: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.TxInfo.MyOutputs
+ Haskoin.Wallet.TxInfo: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.TxInfo.TxInfoPending
+ Haskoin.Wallet.TxInfo: instance Data.Aeson.Types.FromJSON.FromJSON Haskoin.Wallet.TxInfo.TxType
+ Haskoin.Wallet.TxInfo: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.TxInfo.MyOutputs
+ Haskoin.Wallet.TxInfo: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.TxInfo.TxInfoPending
+ Haskoin.Wallet.TxInfo: instance Data.Aeson.Types.ToJSON.ToJSON Haskoin.Wallet.TxInfo.TxType
+ Haskoin.Wallet.TxInfo: instance GHC.Classes.Eq Haskoin.Wallet.TxInfo.MyInputs
+ Haskoin.Wallet.TxInfo: instance GHC.Classes.Eq Haskoin.Wallet.TxInfo.MyOutputs
+ Haskoin.Wallet.TxInfo: instance GHC.Classes.Eq Haskoin.Wallet.TxInfo.OtherInputs
+ Haskoin.Wallet.TxInfo: instance GHC.Classes.Eq Haskoin.Wallet.TxInfo.TxInfo
+ Haskoin.Wallet.TxInfo: instance GHC.Classes.Eq Haskoin.Wallet.TxInfo.TxInfoPending
+ Haskoin.Wallet.TxInfo: instance GHC.Classes.Eq Haskoin.Wallet.TxInfo.TxType
+ Haskoin.Wallet.TxInfo: instance GHC.Show.Show Haskoin.Wallet.TxInfo.MyInputs
+ Haskoin.Wallet.TxInfo: instance GHC.Show.Show Haskoin.Wallet.TxInfo.MyOutputs
+ Haskoin.Wallet.TxInfo: instance GHC.Show.Show Haskoin.Wallet.TxInfo.OtherInputs
+ Haskoin.Wallet.TxInfo: instance GHC.Show.Show Haskoin.Wallet.TxInfo.TxInfo
+ Haskoin.Wallet.TxInfo: instance GHC.Show.Show Haskoin.Wallet.TxInfo.TxInfoPending
+ Haskoin.Wallet.TxInfo: instance GHC.Show.Show Haskoin.Wallet.TxInfo.TxType
+ Haskoin.Wallet.TxInfo: instance Haskoin.Util.Marshal.MarshalJSON (Haskoin.Network.Data.Network, Crypto.Secp256k1.Internal.Context.Ctx) Haskoin.Wallet.TxInfo.TxInfo
+ Haskoin.Wallet.TxInfo: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.TxInfo.MyInputs
+ Haskoin.Wallet.TxInfo: instance Haskoin.Util.Marshal.MarshalJSON Crypto.Secp256k1.Internal.Context.Ctx Haskoin.Wallet.TxInfo.OtherInputs
+ Haskoin.Wallet.TxInfo: mapAddrText :: Network -> Map Address v -> Map Text v
+ Haskoin.Wallet.TxInfo: mapTextAddr :: Network -> Map Text v -> Map Address v
+ Haskoin.Wallet.TxInfo: marshalMap :: MarshalJSON Ctx v => Network -> Ctx -> Map Address v -> Map Text Value
+ Haskoin.Wallet.TxInfo: maybeSetForkId :: Network -> SigHash -> SigHash
+ Haskoin.Wallet.TxInfo: outputAddressMap :: [StoreOutput] -> (Map Address Natural, [StoreOutput])
+ Haskoin.Wallet.TxInfo: parseTxSignData :: Network -> Ctx -> XPubKey -> TxSignData -> Either String TxInfo
+ Haskoin.Wallet.TxInfo: pathToAddr :: Ctx -> XPubKey -> SoftPath -> Address
+ Haskoin.Wallet.TxInfo: storeToTxInfo :: Map Address SoftPath -> Natural -> Transaction -> TxInfo
+ Haskoin.Wallet.TxInfo: txInAddressMap :: Network -> Ctx -> [(OutPoint, TxOut)] -> (Map Address (Natural, [SigInput]), [(OutPoint, TxOut)])
+ Haskoin.Wallet.TxInfo: txOutAddressMap :: Ctx -> [TxOut] -> (Map Address Natural, [TxOut])
+ Haskoin.Wallet.TxInfo: txSignDataCoins :: TxSignData -> Either String [(OutPoint, TxOut)]
+ Haskoin.Wallet.TxInfo: txType :: Integer -> Natural -> TxType
+ Haskoin.Wallet.TxInfo: unmarshalMap :: MarshalJSON Ctx v => Network -> Ctx -> Map Text Value -> Parser (Map Address v)
+ Haskoin.Wallet.Util: (!!?) :: [a] -> Natural -> Maybe a
+ Haskoin.Wallet.Util: (</>) :: String -> String -> String
+ Haskoin.Wallet.Util: Page :: !Natural -> !Natural -> Page
+ Haskoin.Wallet.Util: [pageLimit] :: Page -> !Natural
+ Haskoin.Wallet.Util: [pageOffset] :: Page -> !Natural
+ Haskoin.Wallet.Util: addrToText2 :: Network -> (Address, v) -> Either String (Text, v)
+ Haskoin.Wallet.Util: addrToText3 :: Network -> (Address, v, w) -> Either String (Text, v, w)
+ Haskoin.Wallet.Util: addrToTextE :: Network -> Address -> Either String Text
+ Haskoin.Wallet.Util: chunksOf :: Natural -> [a] -> [[a]]
+ Haskoin.Wallet.Util: chunksOfEnd :: Int -> Text -> [Text]
+ Haskoin.Wallet.Util: data Page
+ Haskoin.Wallet.Util: dropPatternEnd :: Text -> Text -> Text
+ Haskoin.Wallet.Util: encodeJsonPretty :: ToJSON a => a -> ByteString
+ Haskoin.Wallet.Util: encodeJsonPrettyLn :: ToJSON a => a -> ByteString
+ Haskoin.Wallet.Util: instance GHC.Classes.Eq Haskoin.Wallet.Util.Page
+ Haskoin.Wallet.Util: instance GHC.Show.Show Haskoin.Wallet.Util.Page
+ Haskoin.Wallet.Util: lastList :: Natural -> [a] -> [a]
+ Haskoin.Wallet.Util: liftExcept :: MonadError String m => ExceptT Except m a -> m a
+ Haskoin.Wallet.Util: padEnd :: Int -> Text -> Text -> Text
+ Haskoin.Wallet.Util: padStart :: Int -> Text -> Text -> Text
+ Haskoin.Wallet.Util: safeSubtract :: Integral a => a -> a -> Maybe a
+ Haskoin.Wallet.Util: sortDesc :: Ord a => [a] -> [a]
+ Haskoin.Wallet.Util: textToAddr2 :: Network -> (Text, v) -> Either String (Address, v)
+ Haskoin.Wallet.Util: textToAddr3 :: Network -> (Text, v, w) -> Either String (Address, v, w)
+ Haskoin.Wallet.Util: textToAddrE :: Network -> Text -> Either String Address
+ Haskoin.Wallet.Util: toPage :: Page -> [a] -> [a]
+ Haskoin.Wallet.Util: xPubChecksum :: Ctx -> XPubKey -> Text

Files

− Network/Haskoin/Wallet.hs
@@ -1,108 +0,0 @@-{-|-  This package provides a command line application called /hw/ (haskoin-  wallet). It is a lightweight bitcoin wallet featuring BIP32 key management,-  deterministic signatures (RFC-6979) and first order support for-  multisignature transactions. A library API for /hw/ is also exposed.--}-module Network.Haskoin.Wallet-(--- *Client-  clientMain-, OutputFormat(..)-, Config(..)---- *Server-, runSPVServer-, stopSPVServer-, SPVMode(..)---- *API JSON Types-, JsonAccount(..)-, JsonAddr(..)-, JsonCoin(..)-, JsonTx(..)---- *API Request Types-, WalletRequest(..)-, ListRequest(..)-, NewAccount(..)-, SetAccountGap(..)-, OfflineTxData(..)-, CoinSignData(..)-, TxAction(..)-, AddressLabel(..)-, NodeAction(..)-, AccountType(..)-, AddressType(..)-, addrTypeIndex-, TxType(..)-, TxConfidence(..)-, AddressInfo(..)-, BalanceInfo(..)---- *API Response Types-, WalletResponse(..)-, TxCompleteRes(..)-, ListResult(..)-, RescanRes(..)---- *Database Accounts-, initWallet-, accounts-, newAccount-, addAccountKeys-, getAccount-, isMultisigAccount-, isReadAccount-, isCompleteAccount---- *Database Addresses-, getAddress-, addressesAll-, addresses-, addressList-, unusedAddresses-, addressCount-, setAddrLabel-, addressPrvKey-, useAddress-, setAccountGap-, firstAddrTime-, getPathRedeem-, getPathPubKey---- *Database Bloom Filter-, getBloomFilter---- *Database transactions-, txs-, addrTxs-, getTx-, getAccountTx-, importTx-, importNetTx-, signAccountTx-, createWalletTx-, signOfflineTx-, getOfflineTxData---- *Database blocks-, importMerkles-, walletBestBlock---- *Database coins and balances-, spendableCoins-, accountBalance-, addressBalances---- *Rescan-, resetRescan-) where--import Network.Haskoin.Wallet.Client-import Network.Haskoin.Wallet.Server-import Network.Haskoin.Wallet.Settings-import Network.Haskoin.Wallet.Types-import Network.Haskoin.Wallet.Accounts-import Network.Haskoin.Wallet.Transaction-
− Network/Haskoin/Wallet/Accounts.hs
@@ -1,709 +0,0 @@-module Network.Haskoin.Wallet.Accounts-(--- *Database Wallet-  initWallet---- *Database Accounts-, accounts-, newAccount-, renameAccount-, addAccountKeys-, getAccount-, isMultisigAccount-, isReadAccount-, isCompleteAccount---- *Database Addresses-, getAddress-, addressesAll-, addresses-, addressList-, unusedAddresses-, lookupByPubKey-, addressCount-, setAddrLabel-, addressPrvKey-, useAddress-, generateAddrs-, setAccountGap-, firstAddrTime-, getPathRedeem-, getPathPubKey---- *Database Bloom Filter-, getBloomFilter---- * Helpers-, subSelectAddrCount-) where--import Control.Applicative ((<|>))-import Control.Monad (unless, void, when)-import Control.Monad.Trans (MonadIO, liftIO)-import Control.Monad.Base (MonadBase)-import Control.Monad.Catch (MonadThrow, throwM)-import Control.Monad.Trans.Resource (MonadResource)-import Control.Exception (throw)--import Data.Text (Text, unpack)-import Data.Maybe (mapMaybe, listToMaybe, isJust, isNothing)-import Data.Time.Clock (getCurrentTime)-import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)-import Data.List (nub)-import Data.Word (Word32)-import Data.String.Conversions (cs)-import Data.Serialize (encode)--import qualified Database.Persist as P (updateWhere, update , (=.))-import Database.Esqueleto-    ( Value(..), SqlExpr-    , select, from, where_, val, sub_select, countRows, count, unValue-    , orderBy, limit, asc, desc, offset, get, countDistinct-    , max_, case_, when_, then_, else_-    , (^.), (==.), (&&.), (>.), (-.), (<.)-    -- Reexports from Database.Persist-    , SqlPersistT, Entity(..)-    , insertUnique, insert_-    )--import Network.Haskoin.Crypto-import Network.Haskoin.Block-import Network.Haskoin.Script-import Network.Haskoin.Node-import Network.Haskoin.Util-import Network.Haskoin.Constants-import Network.Haskoin.Node.HeaderTree--import Network.Haskoin.Wallet.Types-import Network.Haskoin.Wallet.Model--{- Initialization -}--initWallet :: MonadIO m => Double -> SqlPersistT m ()-initWallet fpRate = do-    prevConfigRes <- select $ from $ \c -> return $ count $ c ^. WalletStateId-    let cnt = maybe 0 unValue $ listToMaybe prevConfigRes-    when (cnt == (0 :: Int)) $ do-        time <- liftIO getCurrentTime-        -- Create an initial bloom filter-        -- TODO: Compute a random nonce-        let bloom = bloomCreate (filterLen 0) fpRate 0 BloomUpdateNone-        insert_ WalletState-            { walletStateHeight      = 0-            , walletStateBlock       = headerHash genesisHeader-            , walletStateBloomFilter = bloom-            , walletStateBloomElems  = 0-            , walletStateBloomFp     = fpRate-            , walletStateVersion     = 1-            , walletStateCreated     = time-            }--{- Account -}---- | Fetch all accounts-accounts :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-         => ListRequest -> SqlPersistT m ([Account], Word32)-accounts ListRequest{..} = do-    cntRes <- select $ from $ \acc ->-        return $ countDistinct $ acc ^. AccountId--    let cnt = maybe 0 unValue $ listToMaybe cntRes--    when (listOffset > 0 && listOffset >= cnt) $ throw $ WalletException-        "Offset beyond end of data set"--    res <- fmap (map entityVal) $ select $ from $ \acc -> do-        limitOffset listLimit listOffset-        return acc--    return (res, cnt)--initGap :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-        => Entity Account -> SqlPersistT m ()-initGap accE = do-    void $ createAddrs accE AddressExternal 20-    void $ createAddrs accE AddressInternal 20---- | Create a new account-newAccount :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-           => NewAccount-           -> SqlPersistT m (Entity Account, Maybe Mnemonic)-newAccount NewAccount{..} = do-    unless (validAccountType newAccountType) $-        throwM $ WalletException "Invalid account type"-    let gen = isNothing newAccountMnemonic &&-              isNothing newAccountMaster &&-              null newAccountKeys-    (mnemonicM, masterM, keys) <- if gen-        then do-            when (isJust newAccountMaster || isJust newAccountMnemonic) $-                throwM $ WalletException-                "Master key or mnemonic not allowed for generate"-            ent <- liftIO $ getEntropy 16-            let ms = fromRight $ toMnemonic ent-                root = makeXPrvKey $ fromRight $ mnemonicToSeed "" ms-                master = case newAccountDeriv of-                    Nothing -> root-                    Just d  -> derivePath d root-                keys = deriveXPubKey master : newAccountKeys-            return (Just ms, Just master, keys)-        else case newAccountMnemonic of-             Just ms -> do-                 when (isJust newAccountMaster) $ throwM $ WalletException-                     "Cannot provide both master key and mnemonic"-                 root <- case mnemonicToSeed "" (cs ms) of-                     Right s -> return $ makeXPrvKey s-                     Left _ -> throwM $ WalletException-                         "Mnemonic sentence invalid"-                 let master = case newAccountDeriv of-                         Nothing -> root-                         Just d -> derivePath d root-                     keys = deriveXPubKey master : newAccountKeys-                 return (Nothing, Just master, keys)-             Nothing -> case newAccountMaster of-                 Just master -> do-                     let keys = deriveXPubKey master : newAccountKeys-                     return (Nothing, newAccountMaster, keys)-                 Nothing -> return (Nothing, newAccountMaster, newAccountKeys)--    -- Build the account-    now <- liftIO getCurrentTime-    let acc = Account-            { accountName       = newAccountName-            , accountType       = newAccountType-            , accountMaster     = if newAccountReadOnly-                                  then Nothing-                                  else masterM-            , accountDerivation = newAccountDeriv-            , accountKeys       = nub keys-            , accountGap        = 0-            , accountCreated    = now-            }--    -- Check if all the keys are valid-    unless (isValidAccKeys acc) $-        throwM $ WalletException "Invalid account keys"--    -- Insert our account in the database-    let canSetGap = isCompleteAccount acc-        newAcc    = acc{ accountGap = if canSetGap then 10 else 0 }--    insertUnique newAcc >>= \resM -> case resM of-        -- The account got created.-        Just ai -> do-            let accE = Entity ai newAcc-            -- If we can set the gap, create the gap addresses-            when canSetGap $ initGap accE-            return (accE, mnemonicM)-        -- The account already exists-        Nothing -> throwM $ WalletException "Account already exists"--renameAccount :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-              => Entity Account-              -> AccountName-              -> SqlPersistT m Account-renameAccount (Entity ai acc) name = do-    P.update ai [ AccountName P.=. name ]-    return $ acc{ accountName = name }---- | Add new thirdparty keys to a multisignature account. This function can--- fail if the multisignature account already has all required keys.-addAccountKeys :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-               => Entity Account        -- ^ Account Entity-               -> [XPubKey]             -- ^ Thirdparty public keys to add-               -> SqlPersistT m Account -- ^ Account information-addAccountKeys (Entity ai acc) keys-    -- We can only add keys on incomplete accounts-    | isCompleteAccount acc = throwM $-        WalletException "The account is already complete"-    | null keys || not (isValidAccKeys accKeys) = throwM $-        WalletException "Invalid account keys"-    | otherwise = do-        let canSetGap = isCompleteAccount accKeys-            updGap = [ AccountGap P.=. 10 | canSetGap]-            newAcc = accKeys{ accountGap = if canSetGap then 10 else 0 }-        -- Update the account with the keys and the new gap if it is complete-        P.update ai $ (AccountKeys P.=. newKeys) : updGap-        -- If we can set the gap, create the gap addresses-        when canSetGap $ initGap $ Entity ai newAcc-        return newAcc-  where-    newKeys = accountKeys acc ++ keys-    accKeys = acc{ accountKeys = newKeys }--isValidAccKeys :: Account -> Bool-isValidAccKeys Account{..} = testMaster && case accountType of-    AccountRegular -> length accountKeys == 1-    AccountMultisig _ n -> goMultisig n-  where-    goMultisig n =-        length accountKeys == length (nub accountKeys) &&-        length accountKeys <= n && not (null accountKeys)-    testMaster = case accountMaster of-        Just m -> deriveXPubKey m `elem` accountKeys-        Nothing -> True---- Helper functions to get an Account if it exists, or throw an exception--- otherwise.-getAccount :: (MonadIO m, MonadThrow m) => AccountName-           -> SqlPersistT m (Entity Account)-getAccount accountName = do-    as <- select $ from $ \a -> do-        where_ $ a ^. AccountName ==. val accountName-        return a-    case as of-        (accEnt:_) -> return accEnt-        _ -> throwM $ WalletException $ unwords-            [ "Account", unpack accountName, "does not exist" ]--{- Addresses -}---- | Get an address if it exists, or throw an exception otherwise. Fetching--- addresses in the hidden gap will also throw an exception.-getAddress :: (MonadIO m, MonadThrow m)-           => Entity Account                    -- ^ Account Entity-           -> AddressType                       -- ^ Address type-           -> KeyIndex                          -- ^ Derivation index (key)-           -> SqlPersistT m (Entity WalletAddr) -- ^ Address-getAddress accE@(Entity ai _) addrType index = do-    res <- select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               &&. x ^. WalletAddrIndex   ==. val index-               &&. x ^. WalletAddrIndex   <.  subSelectAddrCount accE addrType-               )-        limit 1-        return x-    case res of-        (addrE:_) -> return addrE-        _ -> throwM $ WalletException $ unwords-            [ "Invalid address index", show index ]---- | All addresses in the wallet, including hidden gap addresses. This is useful--- for building a bloom filter.-addressesAll :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-             => SqlPersistT m [WalletAddr]-addressesAll = fmap (map entityVal) $ select $ from return---- | All addresses in one account excluding hidden gap.-addresses :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-          => Entity Account             -- ^ Account Entity-          -> AddressType                -- ^ Address Type-          -> SqlPersistT m [WalletAddr] -- ^ Addresses-addresses accE@(Entity ai _) addrType = fmap (map entityVal) $-    select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               &&. x ^. WalletAddrIndex   <.  subSelectAddrCount accE addrType-               )-        return x---- | Get address list.-addressList :: MonadIO m-            => Entity Account -- ^ Account Entity-            -> AddressType    -- ^ Address type-            -> ListRequest    -- ^ List request-            -> SqlPersistT m ([WalletAddr], Word32)-            -- ^ List result-addressList accE@(Entity ai _) addrType ListRequest{..} = do-    cnt <- addressCount accE addrType--    when (listOffset > 0 && listOffset >= cnt) $ throw $ WalletException-        "Offset beyond end of data set"--    res <- fmap (map entityVal) $ select $ from $ \x -> do-        where_ (    x ^. WalletAddrAccount ==. val ai-                &&. x ^. WalletAddrType    ==. val addrType-                &&. x ^. WalletAddrIndex   <.  val cnt-                )-        let order = if listReverse then asc else desc-        orderBy [ order (x ^. WalletAddrIndex) ]-        when (listLimit  > 0) $ limit  $ fromIntegral listLimit-        when (listOffset > 0) $ offset $ fromIntegral listOffset-        return x--    return (res, cnt)---- | Get a count of all the addresses in an account-addressCount :: MonadIO m-             => Entity Account        -- ^ Account Entity-             -> AddressType           -- ^ Address type-             -> SqlPersistT m Word32  -- ^ Address Count-addressCount (Entity ai acc) addrType = do-    res <- select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               )-        return countRows-    let cnt = maybe 0 unValue $ listToMaybe res-    return $ if cnt > accountGap acc then cnt - accountGap acc else 0---- | Get a list of all unused addresses.-unusedAddresses :: MonadIO m-                => Entity Account                       -- ^ Account ID-                -> AddressType                          -- ^ Address type-                -> ListRequest-                -> SqlPersistT m ([WalletAddr], Word32) -- ^ Unused addresses-unusedAddresses (Entity ai acc) addrType ListRequest{..} = do-    cntRes <- select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               )-        return countRows--    let cnt = maybe 0 unValue $ listToMaybe cntRes--    when (listOffset > 0 && listOffset >= gap) $ throw $ WalletException-        "Offset beyond end of data set"--    res <- fmap (map entityVal) $ select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               )-        orderBy [ order $ x ^. WalletAddrIndex ]-        limit  $ fromIntegral $ lim cnt-        offset $ fromIntegral $ off cnt-        return x-    return (res, gap)-  where-    gap = accountGap acc-    lim' = if listLimit > 0 then listLimit else gap-    off cnt | listReverse = listOffset + gap-            | otherwise   = cnt - 2 * gap + listOffset-    lim cnt | listReverse = min lim' (gap - listOffset)-            | otherwise   = min lim' (cnt - off cnt - gap)-    order = if listReverse then desc else asc--lookupByPubKey :: (MonadIO m, MonadThrow m)-               => Entity Account         -- ^ Account Entity-               -> PubKeyC                -- ^ Pubkey of interest-               -> AddressType            -- ^ Address type-               -> SqlPersistT m [WalletAddr]-lookupByPubKey (Entity ai _) key addrType =-    fmap (map entityVal) $ select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               &&. x ^. WalletAddrKey     ==. val (Just key)-               )-        return x---- | Add a label to an address.-setAddrLabel :: (MonadIO m, MonadThrow m)-             => Entity Account        -- ^ Account ID-             -> KeyIndex              -- ^ Derivation index-             -> AddressType           -- ^ Address type-             -> Text                  -- ^ New label-             -> SqlPersistT m WalletAddr-setAddrLabel accE i addrType label = do-    Entity addrI addr <- getAddress accE addrType i-    P.update addrI [ WalletAddrLabel P.=. label ]-    return $ addr{ walletAddrLabel = label }---- | Returns the private key of an address.-addressPrvKey :: (MonadIO m, MonadThrow m)-              => Entity Account        -- ^ Account Entity-              -> Maybe XPrvKey         -- ^ If not in account-              -> KeyIndex              -- ^ Derivation index of the address-              -> AddressType           -- ^ Address type-              -> SqlPersistT m PrvKeyC -- ^ Private key-addressPrvKey accE@(Entity ai acc) masterM index addrType = do-    ret <- select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               &&. x ^. WalletAddrIndex   ==. val index-               &&. x ^. WalletAddrIndex   <.  subSelectAddrCount accE addrType-               )-        return $ x ^. WalletAddrIndex-    case ret of-        (Value idx:_) -> do-            accKey <- case accountMaster acc <|> masterM of-                Just key -> return key-                Nothing -> throwM $ WalletException "Could not get private key"-            let addrKey =-                  prvSubKey (prvSubKey accKey (addrTypeIndex addrType)) idx-            return $ xPrvKey addrKey-        _ -> throwM $ WalletException "Invalid address"---- | Create new addresses in an account and increment the internal bloom filter.--- This is a low-level function that simply creates the desired amount of new--- addresses in an account, disregarding visible and hidden address gaps. You--- should use the function `setAccountGap` if you want to control the gap of an--- account instead.-createAddrs :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-            => Entity Account-            -> AddressType-            -> Word32-            -> SqlPersistT m [WalletAddr]-createAddrs (Entity ai acc) addrType n-    | n == 0 = throwM $ WalletException $-        unwords [ "Invalid value", show n ]-    | not (isCompleteAccount acc) =-        throwM $ WalletException $ unwords-            [ "Keys are still missing from the incomplete account"-            , unpack $ accountName acc-            ]-    | otherwise = do-        now <- liftIO getCurrentTime-        -- Find the next derivation index from the last address-        lastRes <- select $ from $ \x -> do-            where_ (   x ^. WalletAddrAccount ==. val ai-                   &&. x ^. WalletAddrType    ==. val addrType-                   )-            return $ max_ (x ^. WalletAddrIndex)-        let nextI = case lastRes of-                (Value (Just lastI):_) -> lastI + 1-                _ -> 0-            build (addr, keyM, rdmM, i) = WalletAddr-                { walletAddrAccount    = ai-                , walletAddrAddress    = addr-                , walletAddrIndex      = i-                , walletAddrType       = addrType-                , walletAddrLabel      = ""-                , walletAddrRedeem     = rdmM-                , walletAddrKey        = keyM-                , walletAddrCreated    = now-                }-            res = map build $ take (fromIntegral n) $ deriveFrom nextI--        -- Save the addresses and increment the bloom filter-        splitInsertMany_ res-        incrementFilter res-        return res-  where-    -- Branch type (external = 0, internal = 1)-    branchType = addrTypeIndex addrType-    deriveFrom = case accountType acc of-        AccountMultisig m _ ->-            let f (a, r, i) = (a, Nothing, Just r, i)-                deriv  = Deriv :/ branchType-            in  map f . derivePathMSAddrs (accountKeys acc) deriv m-        AccountRegular -> case accountKeys acc of-            (key:_) -> let f (a, k, i) = (a, Just k, Nothing, i)-                       in  map f . derivePathAddrs key (Deriv :/ branchType)-            [] -> throw $ WalletException $ unwords-                [ "createAddrs: No key in regular account (corrupt database)"-                , unpack $ accountName acc-                ]---- | Generate all the addresses up to certain index.-generateAddrs :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-              => Entity Account-              -> AddressType-              -> KeyIndex-              -> SqlPersistT m Int-generateAddrs accE addrType genIndex = do-    cnt <- addressCount accE addrType-    let toGen = fromIntegral genIndex - fromIntegral cnt + 1-    if toGen > 0-        then do-            void $ createAddrs accE addrType $ fromIntegral toGen-            return toGen-        else return 0---- | Use an address and make sure we have enough gap addresses after it.--- Returns the new addresses that have been created.-useAddress :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-           => WalletAddr -> SqlPersistT m [WalletAddr]-useAddress WalletAddr{..} = do-    res <- select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val walletAddrAccount-               &&. x ^. WalletAddrType    ==. val walletAddrType-               &&. x ^. WalletAddrIndex   >.  val walletAddrIndex-               )-        return countRows-    case res of-        (Value cnt:_) -> get walletAddrAccount >>= \accM -> case accM of-            Just acc -> do-                let accE    = Entity walletAddrAccount acc-                    gap     = fromIntegral (accountGap acc) :: Int-                    missing = 2*gap - cnt-                if missing > 0-                    then createAddrs accE walletAddrType $ fromIntegral missing-                    else return []-            _ -> return [] -- Should not happen-        _ -> return [] -- Should not happen---- | Set the address gap of an account to a new value. This will create new--- internal and external addresses as required. The gap can only be increased,--- not decreased in size.-setAccountGap :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-              => Entity Account -- ^ Account Entity-              -> Word32         -- ^ New gap value-              -> SqlPersistT m (Entity Account)-setAccountGap accE@(Entity ai acc) gap-    | not (isCompleteAccount acc) =-        throwM $ WalletException $ unwords-            [ "Keys are still missing from the incomplete account"-            , unpack $ accountName acc-            ]-    | missing <= 0 = throwM $ WalletException-        "The gap of an account can only be increased"-    | otherwise = do-        _ <- createAddrs accE AddressExternal $ fromInteger $ missing*2-        _ <- createAddrs accE AddressInternal $ fromInteger $ missing*2-        P.update ai [ AccountGap P.=. gap ]-        return $ Entity ai acc{ accountGap = gap }-  where-    missing = toInteger gap - toInteger (accountGap acc)---- Return the creation time of the first address in the wallet.-firstAddrTime :: MonadIO m => SqlPersistT m (Maybe Timestamp)-firstAddrTime = do-    res <- select $ from $ \x -> do-        orderBy [ asc (x ^. WalletAddrId) ]-        limit 1-        return $ x ^. WalletAddrCreated-    return $ case res of-        (Value d:_) -> Just $ toPOSIX d-        _ -> Nothing-  where-    toPOSIX = fromInteger . round . utcTimeToPOSIXSeconds--{- Bloom filters -}---- | Add the given addresses to the bloom filter. If the number of elements--- becomes too large, a new bloom filter is computed from scratch.-incrementFilter :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-                => [WalletAddr]-                -> SqlPersistT m ()-incrementFilter addrs = do-    (bloom, elems, _) <- getBloomFilter-    let newElems = elems + (length addrs * 2)-    if filterLen newElems > filterLen elems-        then computeNewFilter-        else setBloomFilter (addToFilter bloom addrs) newElems---- | Generate a new bloom filter from the data in the database-computeNewFilter :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-                 => SqlPersistT m ()-computeNewFilter = do-    (_, _, fpRate) <- getBloomFilter-    -- Create a new empty bloom filter-    -- TODO: Choose a random nonce for the bloom filter-    -- TODO: Check global bloom filter length limits-    cntRes <- select $ from $ \x -> return $ count $ x ^. WalletAddrId-    let elems = maybe 0 unValue $ listToMaybe cntRes-        newBloom = bloomCreate (filterLen elems) fpRate 0 BloomUpdateNone-    addrs <- addressesAll-    let bloom = addToFilter newBloom addrs-    setBloomFilter bloom elems---- Compute the size of a filter given a number of elements. Scale--- the filter length by powers of 2.-filterLen :: Int -> Int-filterLen = round . pow2 . ceiling . log2-  where-    pow2 x = (2 :: Double) ** fromInteger x-    log2 x = logBase (2 :: Double) (fromIntegral x)---- | Add elements to a bloom filter-addToFilter :: BloomFilter -> [WalletAddr] -> BloomFilter-addToFilter bloom addrs =-    bloom3-  where-    pks  = mapMaybe walletAddrKey addrs-    rdms = mapMaybe walletAddrRedeem addrs-    -- Add the Hash160 of the addresses-    f1 b a  = bloomInsert b $ encode $ getAddrHash a-    bloom1 = foldl f1 bloom $ map walletAddrAddress addrs-    -- Add the redeem scripts-    f2 b r  = bloomInsert b $ encodeOutputBS r-    bloom2 = foldl f2 bloom1 rdms-    -- Add the public keys-    f3 b p  = bloomInsert b $ encode p-    bloom3 = foldl f3 bloom2 pks---- | Returns a bloom filter containing all the addresses in this wallet. This--- includes internal and external addresses. The bloom filter can be set on a--- peer connection to filter the transactions received by that peer.-getBloomFilter :: (MonadIO m, MonadThrow m)-               => SqlPersistT m (BloomFilter, Int, Double)-getBloomFilter = do-    res <- select $ from $ \c -> do-        limit 1-        return ( c ^. WalletStateBloomFilter-               , c ^. WalletStateBloomElems-               , c ^. WalletStateBloomFp-               )-    case res of-        ((Value b, Value n, Value fp):_) -> return (b, n, fp)-        _ -> throwM $-            WalletException "getBloomFilter: Database not initialized"---- | Save a bloom filter and the number of elements it contains-setBloomFilter :: MonadIO m => BloomFilter -> Int -> SqlPersistT m ()-setBloomFilter bloom elems =-    P.updateWhere [] [ WalletStateBloomFilter P.=. bloom-                     , WalletStateBloomElems  P.=. elems-                     ]---- Helper function to compute the redeem script of a given derivation path--- for a given multisig account.-getPathRedeem :: Account -> SoftPath -> RedeemScript-getPathRedeem acc@Account{..} deriv = case accountType of-    AccountMultisig m _ -> if isCompleteAccount acc-        then sortMulSig $ PayMulSig pubKeys m-        else throw $ WalletException $ unwords-            [ "getPathRedeem: Incomplete multisig account"-            , unpack accountName-            ]-    _ -> throw $ WalletException $ unwords-        [ "getPathRedeem: Account", unpack accountName-        , "is not a multisig account"-        ]-  where-    f       = toPubKeyG . xPubKey . derivePubPath deriv-    pubKeys = map f accountKeys---- Helper function to compute the public key of a given derivation path for--- a given non-multisig account.-getPathPubKey :: Account -> SoftPath -> PubKeyC-getPathPubKey acc@Account{..} deriv-    | isMultisigAccount acc = throw $ WalletException $-        unwords [ "getPathPubKey: Account", unpack accountName-                , "is not a regular non-multisig account"-                ]-    | otherwise = case accountKeys of-        (key:_) -> xPubKey $ derivePubPath deriv key-        _ -> throw $ WalletException $ unwords-            [ "getPathPubKey: No keys are available in account"-            , unpack accountName-            ]--{- Helpers -}--subSelectAddrCount :: Entity Account-                   -> AddressType-                   -> SqlExpr (Value KeyIndex)-subSelectAddrCount (Entity ai acc) addrType =-    sub_select $ from $ \x -> do-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. x ^. WalletAddrType    ==. val addrType-               )-        let gap = val $ accountGap acc-        return $ case_-            [ when_ (countRows >. gap)-              then_ (countRows -. gap)-            ] (else_ $ val 0)--validMultisigParams :: Int -> Int -> Bool-validMultisigParams m n = n >= 1 && n <= 15 && m >= 1 && m <= n--validAccountType :: AccountType -> Bool-validAccountType t = case t of-    AccountRegular      -> True-    AccountMultisig m n -> validMultisigParams m n--isMultisigAccount :: Account -> Bool-isMultisigAccount acc = case accountType acc of-    AccountRegular    -> False-    AccountMultisig{} -> True--isReadAccount :: Account -> Bool-isReadAccount = isNothing . accountMaster--isCompleteAccount :: Account -> Bool-isCompleteAccount acc = case accountType acc of-    AccountRegular      -> length (accountKeys acc) == 1-    AccountMultisig _ n -> length (accountKeys acc) == n-
− Network/Haskoin/Wallet/Block.hs
@@ -1,59 +0,0 @@-module Network.Haskoin.Wallet.Block where--import           Control.Exception                  (throw)-import           Control.Monad.Catch                (MonadThrow, throwM)-import           Control.Monad.Trans                (MonadIO)-import           Data.Maybe                         (fromMaybe)-import           Database.Persist.Sql               (SqlPersistT)-import           Network.Haskoin.Block-import           Network.Haskoin.Node.HeaderTree-import           Network.Haskoin.Wallet.Model-import           Network.Haskoin.Wallet.Transaction-import           Network.Haskoin.Wallet.Types--mainChain :: (MonadIO m, MonadThrow m)-          => Either BlockHeight BlockHash-          -> ListRequest-          -> SqlPersistT m (ListResult NodeBlock)-mainChain blockE ListRequest{..} = do-    bestHash <- fst <$> walletBestBlock-    bestM <- getBlockByHash bestHash-    best <- maybe (throwM $ WalletException "Could not find wallet best block")-        return bestM-    remoteNode <- case blockE of-        Right h -> do-            remoteNodeM <- getBlockByHash h-            maybe (throwM $ WalletException "Colud not get remote node")-                return remoteNodeM-        Left h -> do-            heightNodeM <- getBlockByHeight best h-            maybe (throwM $ WalletException "Could not find bock height")-                return heightNodeM-    frst <- (+1) . nodeBlockHeight <$> splitBlock best remoteNode-    if nodeBlockHeight best < frst-        then return $ ListResult [] 0-        else do-            let cnt = nodeBlockHeight best - frst-                limit = min listLimit (cnt - listOffset)-                offset =-                    if listReverse-                    then cnt - listOffset - limit-                    else listOffset-            nodes <- getBlocksFromHeight best limit (frst + offset)-            return $ ListResult nodes cnt--blockTxs :: [NodeBlock] -> [WalletTx] -> [(NodeBlock, [WalletTx])]-blockTxs blocks transactions = reverse $ go [] blocks transactions-  where-    go bs [] _ = bs-    go bs (n:ns) [] = go ((n,[]):bs) ns []-    go [] (n:ns) xs = go [(n,[])] ns xs-    go (b:bs) (n:ns) (x:xs)-       | nodeHash (fst b) == blockHashOf x =-           go ((fst b, x : snd b) : bs) (n:ns) xs-       | nodeHash n == blockHashOf x =-           go ((n, [x]) : b : bs) ns xs-       | otherwise = go ((n, []) : b : bs) ns (x:xs)-    blockHashOf t = fromMaybe-        (throw $ WalletException "Unexpected unconfirmed transaction")-        (walletTxConfirmedBy t)
− Network/Haskoin/Wallet/Client.hs
@@ -1,252 +0,0 @@-module Network.Haskoin.Wallet.Client (clientMain) where--import System.FilePath ((</>))-import System.Directory (createDirectoryIfMissing)-import System.Posix.Directory (changeWorkingDirectory)-import System.Posix.Files-    ( setFileMode-    , setFileCreationMask-    , unionFileModes-    , ownerModes-    , groupModes-    , otherModes-    , fileExist-    )-import System.Environment (getArgs, lookupEnv)-import System.Info (os)-import System.Console.GetOpt-    ( getOpt-    , usageInfo-    , OptDescr (Option)-    , ArgDescr (NoArg, ReqArg)-    , ArgOrder (Permute)-    )--import Control.Monad (when, forM_)-import Control.Monad.Trans (liftIO)-import qualified Control.Monad.Reader as R (runReaderT)--import Data.Default (def)-import Data.FileEmbed (embedFile)-import Data.Yaml (decodeFileEither)-import Data.String.Conversions (cs)--import Network.Haskoin.Constants-import Network.Haskoin.Crypto-import Network.Haskoin.Wallet.Settings-import Network.Haskoin.Wallet.Client.Commands-import Network.Haskoin.Wallet.Types--import System.Exit (exitFailure)-import System.IO (hPutStrLn, stderr)-import System.FilePath.Posix (isAbsolute)--usageHeader :: String-usageHeader = "Usage: hw [<options>] <command> [<args>]"--cmdHelp :: [String]-cmdHelp = lines $ cs $ $(embedFile "config/help")--warningMsg :: String-warningMsg = unwords-    [ "!!!", "This software is experimental."-    , "Use only small amounts of Bitcoins.", "!!!"-    ]--usage :: [String]-usage = warningMsg : usageInfo usageHeader options : cmdHelp--read' :: Read x => String -> String -> x-read' e s = case reads s of-    [(x, "")] -> x-    _ -> error e--options :: [OptDescr (Config -> Config)]-options =-    [ Option "c" ["count"]-        ( ReqArg-            (\s cfg -> cfg { configCount = read' "Could not parse count" s })-            "INT"-        ) $ "Items per page. Default: " ++ show (configCount def)-    , Option "m" ["minconf"]-        ( ReqArg-            (\s cfg ->-                cfg { configMinConf = read' "Colud not parse minconf" s }-            ) "INT"-        ) $ "Minimum confirmations. Default: "-            ++ show (configMinConf def)-    , Option "f" ["fee"]-        ( ReqArg-            (\s cfg -> cfg { configFee = read' "Could not parse fee" s })-            "INT"-        ) $ "Fee per kilobyte. Default: " ++ show (configFee def)-    , Option "R" ["rcptfee"]-        (NoArg $ \cfg -> cfg { configRcptFee = True }) $-        "Recipient pays fee. Default: " ++ show (configRcptFee def)-    , Option "S" ["nosig"]-        (NoArg $ \cfg -> cfg { configSignTx = False }) $-        "Do not sign. Default: " ++ show (not $ configSignTx def)-    , Option "i" ["internal"]-        (NoArg $ \cfg -> cfg { configAddrType = AddressInternal }) $-        "Internal addresses. Default: "-            ++ show (configAddrType def == AddressInternal)-    , Option "o" ["offline"]-        (NoArg $ \cfg -> cfg { configOffline = True }) $-        "Offline balance. Default: " ++ show (configOffline def)-    , Option "r" ["revpage"]-        (NoArg $ \cfg -> cfg { configReversePaging = True }) $-        "Reverse paging. Default: "-            ++ show (configReversePaging def)-    , Option "p" ["path"]-        ( ReqArg ( \s cfg -> case parseHard s of-                       Just p -> cfg { configPath = Just p }-                       Nothing -> error "Could not parse derivation path"-                 ) "PATH"-        )-        "Derivation path (e.g. m/44'/3')"-    , Option "j" ["json"]-        (NoArg $ \cfg -> cfg { configFormat = OutputJSON })-        "Output JSON"-    , Option "y" ["yaml"]-        (NoArg $ \cfg -> cfg { configFormat = OutputYAML })-        "Output YAML"-    , Option "s" ["socket"]-        (ReqArg (\s cfg -> cfg { configConnect = s }) "URI") $-        "Server socket. Default: " ++ configConnect def-    , Option "d" ["detach"]-        (NoArg $ \cfg -> cfg { configDetach = True }) $-        "Detach server. Default: " ++ show (configDetach def)-    , Option "t" ["testnet"]-        (NoArg $ \cfg -> cfg { configTestnet = True }) "Testnet3 network"-    , Option "g" ["config"]-        (ReqArg (\s cfg -> cfg { configFile = s }) "FILE") $-        "Config file. Default: " ++ configFile def-    , Option "w" ["workdir"]-        (ReqArg (\s cfg -> cfg { configDir = s }) "DIR")-        "Working directory. OS-dependent default"-    , Option "v" ["verbose"]-        (NoArg $ \cfg -> cfg { configVerbose = True }) "Verbose output"-    ]---- Create and change current working directory-setWorkDir :: Config -> IO ()-setWorkDir cfg = do-    let workDir = configDir cfg </> networkName-    _ <- setFileCreationMask $ otherModes `unionFileModes` groupModes-    createDirectoryIfMissing True workDir-    setFileMode workDir ownerModes-    changeWorkingDirectory workDir---- Build application configuration-getConfig :: [Config -> Config] -> IO Config-getConfig fs = do-    -- Create initial configuration from defaults and command-line arguments-    let initCfg = foldr ($) def fs--    -- If working directory set in initial configuration, use it-    dir <- case configDir initCfg of "" -> appDir-                                     d  -> return d--    -- Make configuration file relative to working directory-    let cfgFile = if isAbsolute (configFile initCfg)-                     then configFile initCfg-                     else dir </> configFile initCfg--    -- Get configuration from file, if it exists-    e <- fileExist cfgFile-    if e then do-            cfgE <- decodeFileEither cfgFile-            case cfgE of-                Left x -> error $ show x-                -- Override settings from file using command-line-                Right cfg -> return $ fixConfigDir (foldr ($) cfg fs) dir-         else return $ fixConfigDir initCfg dir-  where-    -- If working directory not set, use default-    fixConfigDir cfg dir = case configDir cfg of "" -> cfg{ configDir = dir }-                                                 _  -> cfg---clientMain :: IO ()-clientMain = getArgs >>= \args -> case getOpt Permute options args of-    (fs, commands, []) -> do-        cfg <- getConfig fs-        when (configTestnet cfg) switchToTestnet3-        setWorkDir cfg-        dispatchCommand cfg commands-    (_, _, msgs) -> forM_ (msgs ++ usage) putStrLn--dispatchCommand :: Config -> [String] -> IO ()-dispatchCommand cfg args = flip R.runReaderT cfg $ case args of-    "start"                               : [] -> cmdStart-    "stop"                                : [] -> cmdStop-    "newacc"      : name                  : [] -> cmdNewAcc False name []-    "newread"     : name                  : [] -> cmdNewAcc True  name []-    "newms"       : name : m : n          : [] -> cmdNewAcc False name [m, n]-    "newreadms"   : name : m : n          : [] -> cmdNewAcc True  name [m, n]-    "addkey"      : name                  : [] -> cmdAddKey name-    "setgap"      : name : gap            : [] -> cmdSetGap name gap-    "account"     : name                  : [] -> cmdAccount name-    "accounts"    : page                       -> cmdAccounts page-    "rename"      : name : new            : [] -> cmdRenameAcc name new-    "list"        : name : page                -> cmdList name page-    "pubkeys"     : name : page                -> cmdPubKeys name page-    "unused"      : name : page                -> cmdUnused name page-    "label"       : name : index : label  : [] -> cmdLabel name index label-    "txs"         : name : page                -> cmdTxs name page-    "addrtxs"     : name : index : page        -> cmdAddrTxs name index page-    "getindex"    : name : key            : [] -> cmdKeyIndex name key-    "genaddrs"    : name : i              : [] -> cmdGenAddrs name i-    "send"        : name : add : amnt     : [] -> cmdSend name add amnt-    "sendmany"    : name : xs                  -> cmdSendMany name xs-    "import"      : name                  : [] -> cmdImport name-    "sign"        : name : txid           : [] -> cmdSign name txid-    "gettx"       : name : txid           : [] -> cmdGetTx name txid-    "balance"     : name                  : [] -> cmdBalance name-    "getoffline"  : name : txid           : [] -> cmdGetOffline name txid-    "signoffline" : name : tx : dat       : [] -> cmdSignOffline name tx dat-    "rescan"      : rescantime                 -> cmdRescan rescantime-    "deletetx"    : txid                  : [] -> cmdDeleteTx txid-    "sync"        : name : block : page        -> cmdSync name block page-    "pending"     : name : page                -> cmdPending name page-    "dead"        : name : page                -> cmdDead name page-    "monitor"     : name                       -> cmdMonitor name-    "decodetx"                            : [] -> cmdDecodeTx-    "status"                              : [] -> cmdStatus-    "keypair"                             : [] -> cmdKeyPair-    "blockinfo"   : hashes                     -> cmdBlockInfo hashes-    "version"                             : [] -> cmdVersion-    "help"        : [] -> liftIO $ forM_ usage (hPutStrLn stderr)-    []                 -> liftIO $ forM_ usage (hPutStrLn stderr)-    _ -> liftIO $-        forM_ ("Invalid command" : usage) (hPutStrLn stderr) >> exitFailure--appDir :: IO FilePath-appDir = case os of "mingw"   -> windows-                    "mingw32" -> windows-                    "mingw64" -> windows-                    "darwin"  -> osx-                    "linux"   -> unix-                    _         -> unix-  where-    windows = do-        localAppData <- lookupEnv "LOCALAPPDATA"-        dirM <- case localAppData of-            Nothing -> lookupEnv "APPDATA"-            Just l -> return $ Just l-        case dirM of-            Just d -> return $ d </> "Haskoin Wallet"-            Nothing -> return "."-    osx = do-        homeM <- lookupEnv "HOME"-        case homeM of-            Just home -> return $ home </> "Library"-                                       </> "Application Support"-                                       </> "Haskoin Wallet"-            Nothing -> return "."-    unix = do-        homeM <- lookupEnv "HOME"-        case homeM of-            Just home -> return $ home </> ".hw"-            Nothing -> return "."-
− Network/Haskoin/Wallet/Client/Commands.hs
@@ -1,970 +0,0 @@-module Network.Haskoin.Wallet.Client.Commands-( cmdStart-, cmdStop-, cmdNewAcc-, cmdAddKey-, cmdSetGap-, cmdAccount-, cmdRenameAcc-, cmdAccounts-, cmdList-, cmdPubKeys-, cmdUnused-, cmdLabel-, cmdTxs-, cmdAddrTxs-, cmdKeyIndex-, cmdGenAddrs-, cmdSend-, cmdSendMany-, cmdImport-, cmdSign-, cmdBalance-, cmdGetTx-, cmdGetOffline-, cmdSignOffline-, cmdRescan-, cmdDecodeTx-, cmdVersion-, cmdStatus-, cmdBlockInfo-, cmdMonitor-, cmdSync-, cmdKeyPair-, cmdDeleteTx-, cmdPending-, cmdDead-)-where--import           Control.Applicative             ((<|>))-import           Control.Concurrent.Async.Lifted (async, wait)-import           Control.Monad                   (forM_, forever, liftM2,-                                                  unless, when)-import qualified Control.Monad.Reader            as R (ReaderT, ask, asks)-import           Control.Monad.Trans             (liftIO)-import           Data.Aeson                      (FromJSON, ToJSON, Value (..),-                                                  decode, eitherDecode, object,-                                                  toJSON, (.=))-import qualified Data.Aeson                      as Aeson (encode)-import qualified Data.ByteString.Char8           as B8 (hPutStrLn, putStrLn,-                                                        unwords)-import           Data.String                     (fromString)-import           Data.List                       (intercalate, intersperse)-import           Data.Maybe                      (fromMaybe, isJust, isNothing,-                                                  listToMaybe, maybeToList)-import           Data.Monoid                     ((<>))-import           Data.Restricted                 (rvalue)-import           Data.Serialize                  (encode)-import           Data.String.Conversions         (cs)-import           Data.Text                       (Text, pack, splitOn, unpack)-import           Data.Word                       (Word32, Word64)-import qualified Data.Yaml                       as YAML (encode)-import qualified Data.Time.Format                as Time-import           Network.Haskoin.Block-import           Network.Haskoin.Constants-import           Network.Haskoin.Crypto-import           Network.Haskoin.Node.STM-import           Network.Haskoin.Script-import           Network.Haskoin.Transaction-import           Network.Haskoin.Util-import           Network.Haskoin.Wallet.Server-import           Network.Haskoin.Wallet.Settings-import           Network.Haskoin.Wallet.Types-import qualified Network.Haskoin.Wallet.Client.PrettyJson as JSON-import qualified System.Console.Haskeline        as Haskeline-import           System.IO                       (stderr)-import           System.ZMQ4                     (KeyFormat (..), Req (..),-                                                  Socket, SocketType, Sub (..),-                                                  connect, curveKeyPair,-                                                  receive, receiveMulti,-                                                  restrict, send,-                                                  setCurvePublicKey,-                                                  setCurveSecretKey,-                                                  setCurveServerKey, setLinger,-                                                  subscribe, withContext,-                                                  withSocket)-import           Text.Read                       (readMaybe)--type Handler = R.ReaderT Config IO--defaultDeriv :: HardPath-defaultDeriv = Deriv :| 0---- hw start [config] [--detach]-cmdStart :: Handler ()-cmdStart = do-    cfg <- R.ask-    liftIO $ runSPVServer cfg---- hw stop [config]-cmdStop :: Handler ()-cmdStop = do-    resE <- sendZmq StopServerR-    handleResponse (resE :: Either String (WalletResponse (Maybe ()))) (const $ return ())-    liftIO $ putStrLn "Process stopped"--getSigningKeys :: String-               -> Handler (Maybe XPrvKey)-getSigningKeys name = do-    derivM <- R.asks configPath-    kM <- masterKey-    case kM of-        Just _ -> return Nothing-        Nothing -> do-            keyOrMnemonic <--                liftIO . Haskeline.runInputT-                    Haskeline.defaultSettings $-                    Haskeline.getPassword (Just '*')-                    "Mnemonic or private extended key: "-            case keyOrMnemonic of-                Just ms -> return $ go (cs ms) derivM-                Nothing -> error "No action due to EOF"-  where-    masterKey = do-        resE <- sendZmq $ GetAccountR $ pack name-        case resE of-            Right (ResponseValid (Just acc)) ->-                return $ jsonAccountMaster acc-            Right (ResponseError e) -> error $ cs e-            Left e -> error e-            _ -> error "You find yourself in a strange place"-    go "" _ = error "Need key to sign"-    go str derivM = case xPrvImport str of-        Just k -> case derivM of-            Just d -> Just $ derivePath d k-            Nothing -> Just k-        Nothing -> case mnemonicToSeed "" str of-            Right s -> Just (makeXPrvKey s)-            Left _ -> error "Could not parse key"--checkExists :: String -> Handler Bool-checkExists name = do-     resE <- sendZmq $ GetAccountR $ pack name-     case (resE :: Either String (WalletResponse JsonAccount)) of-         Right (ResponseValid _) -> return True-         Right (ResponseError  _) -> return False-         Left e -> error e--getKey :: Handler (Maybe Mnemonic, Maybe XPrvKey, Maybe HardPath, Maybe XPubKey)-getKey = do-    derivM <- R.asks configPath-    i <- liftIO . Haskeline.runInputT Haskeline.defaultSettings $-        Haskeline.getPassword (Just '*')-            "Type mnemonic, extended key or leave empty to generate: "-    case i of-        Just s -> go (cs s) derivM-        Nothing -> error "No action due to EOF"-  where-    go "" derivM = return-        ( Nothing-        , Nothing-        , derivM <|> Just defaultDeriv-        , Nothing-        )-    go str' derivM = case xPrvImport str' of-        Just k -> return-            ( Nothing-            , Just $ maybe k (`derivePath` k) derivM-            , derivM-            , Nothing-            )-        Nothing -> case xPubImport str' of-            Just p -> return-                ( Nothing-                , Nothing-                , derivM-                , Just p-                )-            Nothing -> return-                ( Just $ cs str'-                , Nothing-                , derivM <|> Just defaultDeriv-                , Nothing-                )---- First argument: is account read-only?-cmdNewAcc :: Bool -> String -> [String] -> Handler ()-cmdNewAcc r name ls = do-    _ <- return $! typ-    e <- checkExists name-    when e $ error "Account exists"-    (mnemonicM, masterM, derivM, keyM) <- getKey-    let newAcc = NewAccount-            { newAccountName     = pack name-            , newAccountType     = typ-            , newAccountMnemonic = cs <$> mnemonicM-            , newAccountMaster   = masterM-            , newAccountDeriv    = derivM-            , newAccountKeys     = maybeToList keyM-            , newAccountReadOnly = r-            }-    resE <- sendZmq $ PostAccountsR newAcc-    handleResponse resE $ liftIO . putStr . printAccount-  where-    typ = case ls of-        [] -> AccountRegular-        [mS, nS] -> fromMaybe (error "Account information incorrect") $ do-            m <- readMaybe mS-            n <- readMaybe nS-            return $ AccountMultisig m n-        _ -> error "Number of parametres incorrect"--cmdAddKey :: String -> Handler ()-cmdAddKey name = do-    e <- checkExists name-    unless e $ error "Account does not exist"-    (mnemonicM, masterM, derivM, pubM) <- getKey-    let key = case mnemonicM of-            Just ms -> case mnemonicToSeed "" (cs ms) of-                Right s -> deriveXPubKey $-                    derivePath (fromMaybe defaultDeriv derivM) $-                    makeXPrvKey s-                Left _ -> error "Could not decode mnemonic sentence"-            Nothing -> case masterM of-                Just m -> deriveXPubKey $ maybe m (`derivePath` m) derivM-                Nothing -> fromMaybe (error "No keys provided") pubM-    resE <- sendZmq (PostAccountKeysR (pack name) [key])-    handleResponse resE $ liftIO . putStr . printAccount--cmdSetGap :: String -> String -> Handler ()-cmdSetGap name gap = do-    resE <- sendZmq (PostAccountGapR (pack name) setGap)-    handleResponse resE $ liftIO . putStr . printAccount-  where-    setGap = SetAccountGap $ read gap--cmdAccount :: String -> Handler ()-cmdAccount name = do-    resE <- sendZmq (GetAccountR $ pack name)-    handleResponse resE $ liftIO . putStr . printAccount--cmdAccounts :: [String] -> Handler ()-cmdAccounts ls = do-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-    listAction page GetAccountsR $ \ts -> do-        let xs = map (liftIO . putStr . printAccount) ts-        sequence_ $ intersperse (liftIO $ putStrLn "-") xs--cmdRenameAcc :: String -> String -> Handler ()-cmdRenameAcc oldName newName = do-    resE <- sendZmq $ PostAccountRenameR (pack oldName) (pack newName)-    handleResponse resE $ liftIO . putStr . printAccount--listAction :: (FromJSON a, ToJSON a)-            => Word32-            -> (ListRequest -> WalletRequest)-            -> ([a] -> Handler ())-            -> Handler ()-listAction page requestBuilder action = do-    c <- R.asks configCount-    r <- R.asks configReversePaging-    case c of-        0 -> do-            let listReq = ListRequest 0 0 r-            resE <- sendZmq (requestBuilder listReq)-            handleResponse resE $ \(ListResult a _) -> action a-        _ -> do-            when (page < 1) $ error "Page cannot be less than 1"-            let listReq = ListRequest ((page - 1) * c) c r-            resE <- sendZmq (requestBuilder listReq)-            handleResponse resE $ \(ListResult a m) -> case m of-                0 -> liftIO . putStrLn $ "No elements"-                _ -> do-                    liftIO . putStrLn $-                        "Page " ++ show page ++ " of " ++ show (pages m c) ++-                        " (" ++ show m ++ " elements)"-                    action a-  where-    pages m c | m `mod` c == 0 = m `div` c-              | otherwise = m `div` c + 1--listJsonAddrs :: (JsonAddr -> String)-              -> String-              -> [String]-              -> Handler ()-listJsonAddrs showFunc name ls = do-    t <- R.asks configAddrType-    m <- R.asks configMinConf-    o <- R.asks configOffline-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-        f = GetAddressesR (pack name) t m o-    listAction page f $ \as -> forM_ as (liftIO . putStrLn . showFunc)--cmdList :: String -> [String] -> Handler ()-cmdList = listJsonAddrs printAddress--cmdPubKeys :: String -> [String] -> Handler ()-cmdPubKeys = listJsonAddrs printPubKey--cmdUnused :: String -> [String] -> Handler ()-cmdUnused name ls = do-    t <- R.asks configAddrType-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-        f = GetAddressesUnusedR (pack name) t-    listAction page f $ \as -> forM_ (as :: [JsonAddr]) $-        liftIO . putStrLn . printAddress--cmdLabel :: String -> String -> String -> Handler ()-cmdLabel name iStr label = do-    t <- R.asks configAddrType-    resE <- sendZmq (PutAddressR (pack name) i t addrLabel)-    handleResponse resE $ liftIO . putStrLn . printAddress-  where-    i         = read iStr-    addrLabel = AddressLabel $ pack label--cmdTxs :: String -> [String] -> Handler ()-cmdTxs name ls = do-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-    r <- R.asks configReversePaging-    listAction page (GetTxsR (pack name)) $ \ts -> do-        let xs = map (liftIO . putStr . printTx Nothing) ts-            xs' = if r then xs else reverse xs-        sequence_ $ intersperse (liftIO $ putStrLn "-") xs'--cmdPending :: String -> [String] -> Handler ()-cmdPending name ls = do-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-    r <- R.asks configReversePaging-    listAction page (GetPendingR (pack name)) $ \ts -> do-        let xs = map (liftIO . putStr . printTx Nothing) ts-            xs' = if r then xs else reverse xs-        sequence_ $ intersperse (liftIO $ putStrLn "-") xs'--cmdDead :: String -> [String] -> Handler ()-cmdDead name ls = do-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-    r <- R.asks configReversePaging-    listAction page (GetDeadR (pack name)) $ \ts -> do-        let xs = map (liftIO . putStr . printTx Nothing) ts-            xs' = if r then xs else reverse xs-        sequence_ $ intersperse (liftIO $ putStrLn "-") xs'--cmdAddrTxs :: String -> String -> [String] -> Handler ()-cmdAddrTxs name i ls = do-    t <- R.asks configAddrType-    m <- R.asks configMinConf-    o <- R.asks configOffline-    r <- R.asks configReversePaging-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-        f = GetAddrTxsR (pack name) index t-    resE <- sendZmq (GetAddressR (pack name) index t m o)-    handleResponse resE $ \JsonAddr{..} -> listAction page f $ \ts -> do-        let xs = map (liftIO . putStr . printTx (Just jsonAddrAddress)) ts-            xs' = if r then xs else reverse xs-        sequence_ $ intersperse (liftIO $ putStrLn "-") xs'-  where-    index = fromMaybe (error "Could not read index") $ readMaybe i--cmdKeyIndex :: String -> String -> Handler ()-cmdKeyIndex name k = do-    t <- R.asks configAddrType-    resE <- sendZmq $ GetIndexR (pack name) (fromString k) t-    handleResponse resE $ \res -> case res of-        []  -> liftIO $ putStrLn "No matching pubkeys found"-        lst -> liftIO $ putStrLn $ unlines $    -- Two or more pubkeys with the same index is extremely improbable. But let's print it out if it happens.-            map (\adr -> showLine (jsonAddrIndex adr, jsonAddrKey adr)) lst-  where-    showLine :: (KeyIndex, Maybe PubKeyC) -> String-    showLine (idx,k') = unwords [ show (idx :: KeyIndex), ":", showPubKey k' ]-    showPubKey = maybe "<no pubkey available>" (jsonStr2Str . toJSON)-    jsonStr2Str (String t) = cs t-    jsonStr2Str _          = ""--cmdGenAddrs :: String -> String -> Handler ()-cmdGenAddrs name i = do-    t <- R.asks configAddrType-    let req = PostAddressesR (pack name) index t-    resE <- sendZmq req-    handleResponse resE $ \cnt -> liftIO . putStrLn $-        unwords [ "Generated", show (cnt :: Int), "addresses" ]-  where-    index = read i--cmdSend :: String -> String -> String -> Handler ()-cmdSend name addrStr amntStr = cmdSendMany name [addrStr ++ ":" ++ amntStr]--cmdSendMany :: String -> [String] -> Handler ()-cmdSendMany name xs = case rcpsM of-    Just rcps -> do-        fee     <- R.asks configFee-        rcptFee <- R.asks configRcptFee-        minconf <- R.asks configMinConf-        sign    <- R.asks configSignTx-        masterM <- if sign then getSigningKeys name else return Nothing-        let action = CreateTx rcps fee minconf rcptFee sign-        resE <- sendZmq (PostTxsR (pack name) masterM action)-        handleResponse resE $ liftIO . putStr . printTx Nothing-    _ -> error "Could not parse recipient information"-  where-    g str   = map cs $ splitOn ":" (pack str)-    f [a,v] = liftM2 (,) (base58ToAddr a) (readMaybe $ cs v)-    f _     = Nothing-    rcpsM   = mapM (f . g) xs--getHexTx :: Handler Tx-getHexTx = do-    hexM <- Haskeline.runInputT Haskeline.defaultSettings $-        Haskeline.getInputLine ""-    let txM = case hexM of-            Nothing -> error "No action due to EOF"-            Just hex -> decodeToMaybe =<< decodeHex (cs hex)-    case txM of-        Just tx -> return tx-        Nothing -> error "Could not parse transaction"--cmdImport :: String -> Handler ()-cmdImport name = do-    tx <- getHexTx-    let action = ImportTx tx-    resE <- sendZmq (PostTxsR (pack name) Nothing action)-    handleResponse resE $ liftIO . putStr . printTx Nothing--cmdSign :: String -> String -> Handler ()-cmdSign name txidStr = case txidM of-    Just txid -> do-        masterM <- getSigningKeys name-        let action = SignTx txid-        resE <- sendZmq (PostTxsR (pack name) masterM action)-        handleResponse resE $ liftIO . putStr . printTx Nothing-    _ -> error "Could not parse txid"-  where-    txidM = hexToTxHash $ cs txidStr--cmdGetOffline :: String -> String -> Handler ()-cmdGetOffline name tidStr = case tidM of-    Just tid -> do-        resE <- sendZmq (GetOfflineTxR (pack name) tid)-        handleResponse resE $ \(OfflineTxData tx dat) -> do-            liftIO $ putStrLn $ unwords-                [ "Tx      :", cs $ encodeHex $ encode tx ]-            liftIO $ putStrLn $ unwords-                [ "CoinData:", cs $ encodeHex $ cs $ Aeson.encode dat ]-    _ -> error "Could not parse txid"-  where-    tidM = hexToTxHash $ cs tidStr--cmdSignOffline :: String -> String -> String -> Handler ()-cmdSignOffline name txStr datStr = case (txM, datM) of-    (Just tx, Just dat) -> do-        masterM <- getSigningKeys name-        resE <- sendZmq (PostOfflineTxR (pack name) masterM tx dat)-        handleResponse resE $ \(TxCompleteRes tx' c) -> do-            liftIO $ putStrLn $ unwords-                [ "Tx      :", cs $ encodeHex $ encode tx' ]-            liftIO $ putStrLn $ unwords-                [ "Complete:", if c then "Yes" else "No" ]-    _ -> error "Could not decode input data"-  where-    datM = decode . cs =<< decodeHex (cs datStr)-    txM  = decodeToMaybe =<< decodeHex (cs txStr)--cmdBalance :: String -> Handler ()-cmdBalance name = do-    m <- R.asks configMinConf-    o <- R.asks configOffline-    resE <- sendZmq (GetBalanceR (pack name) m o)-    handleResponse resE $ \bal ->-        liftIO $ putStrLn $ unwords [ "Balance:", show (bal :: Word64) ]--cmdGetTx :: String -> String -> Handler ()-cmdGetTx name tidStr = case tidM of-    Just tid -> do-        resE <- sendZmq (GetTxR (pack name) tid)-        handleResponse resE $ liftIO . putStr . printTx Nothing-    _ -> error "Could not parse txid"-  where-    tidM = hexToTxHash $ cs tidStr--cmdRescan :: [String] -> Handler ()-cmdRescan timeLs = do-    let timeM = case timeLs of-            [] -> Nothing-            str:_ -> case readMaybe str of-                Nothing -> error "Could not decode time"-                Just t -> Just t-    resE <- sendZmq (PostNodeR $ NodeActionRescan timeM)-    handleResponse resE $ \(RescanRes ts) ->-        liftIO $ putStrLn $ unwords [ "Timestamp:", show ts]--cmdDeleteTx :: String -> Handler ()-cmdDeleteTx tidStr = case tidM of-    Just tid -> do-        resE <- sendZmq (DeleteTxIdR tid)-        handleResponse resE $ \() -> return ()-    Nothing -> error "Could not parse txid"-  where-    tidM = hexToTxHash $ cs tidStr--cmdMonitor :: [String] -> Handler ()-cmdMonitor ls = do-    cfg@Config{..} <- R.ask-    -- TODO: I can do this in the same thread without ^C twice (see sendZmq)-    liftIO $ withContext $ \ctx -> withSocket ctx Sub $ \sock -> do-        setLinger (restrict (0 :: Int)) sock-        setupAuth cfg sock-        connect sock configConnectNotif-        subscribe sock "[block]"-        forM_ ls $ \name -> subscribe sock $ "{" <> cs name <> "}"-        forever $ do-            [_,m] <- receiveMulti sock-            handleNotif configFormat $ eitherDecode $ cs m--cmdSync :: String -> String -> [String] -> Handler ()-cmdSync acc block ls = do-    let page = fromMaybe 1 $ listToMaybe ls >>= readMaybe-        f = case length block of-            64 -> GetSyncR (cs acc) $-                fromMaybe (error "Could not decode block id") $-                hexToBlockHash $ cs block-            _  -> GetSyncHeightR (cs acc) $-                fromMaybe (error "Could not decode block height") $-                readMaybe block-    r <- R.asks configReversePaging-    listAction page f $ \blocks -> do-        let blocks' = if r then reverse blocks else blocks-        forM_ (blocks' :: [JsonSyncBlock]) $ liftIO . putStrLn . printSyncBlock--cmdDecodeTx :: Handler ()-cmdDecodeTx = do-    tx <- getHexTx-    format <- R.asks configFormat-    liftIO $ formatStr $ cs $ case format of-        OutputJSON -> cs $ jsn tx-        _          -> YAML.encode $ val tx-  where-    val = encodeTxJSON-    jsn = JSON.encodePretty . val--cmdVersion :: Handler ()-cmdVersion = liftIO $ do-    putStrLn $ unwords [ "network   :", cs networkName ]-    putStrLn $ unwords [ "user-agent:", cs haskoinUserAgent ]--cmdStatus :: Handler ()-cmdStatus = do-    v <- R.asks configVerbose-    resE <- sendZmq (PostNodeR NodeActionStatus)-    handleResponse resE $ mapM_ (liftIO . putStrLn) . printNodeStatus v--cmdKeyPair :: Handler ()-cmdKeyPair = do-    (pub, sec) <- curveKeyPair-    liftIO $ do-        B8.putStrLn $ B8.unwords [ "public :", rvalue pub ]-        B8.putStrLn $ B8.unwords [ "private:", rvalue sec ]--cmdBlockInfo :: [String] -> Handler ()-cmdBlockInfo headers = do-    -- Show best block if no arguments are provided-    hashL <- if null headers then-            -- Fetch best block hash from status msg, and return as list-            (: []) . parseRes <$> sendZmq (PostNodeR NodeActionStatus)-        else-            return (map fromString headers)-    sendZmq (GetBlockInfoR hashL) >>=-        \resE -> handleResponse resE (liftIO . printResults)-  where-    printResults :: [BlockInfo] -> IO ()-    printResults = mapM_ $ putStrLn . unlines . printBlockInfo-    parseRes :: Either String (WalletResponse NodeStatus) -> BlockHash-    parseRes = nodeStatusBestHeader . fromMaybe-        (error "No response to NodeActionStatus msg") . parseResponse---{- Helpers -}--handleNotif :: OutputFormat -> Either String Notif -> IO ()-handleNotif _   (Left e) = error e-handleNotif fmt (Right notif) = case fmt of-    OutputJSON -> formatStr $ cs $-        JSON.encodePretty notif-    OutputYAML -> do-        putStrLn "---"-        formatStr $ cs $ YAML.encode notif-        putStrLn "..."-    OutputNormal ->-        putStrLn $ printNotif notif--parseResponse-    :: Either String (WalletResponse a)-    -> Maybe a-parseResponse resE = case resE of-    Right (ResponseValid resM) -> resM-    Right (ResponseError err)  -> error $ unpack err-    Left err                   -> error err--handleResponse-    :: (FromJSON a, ToJSON a)-    => Either String (WalletResponse a)-    -> (a -> Handler ())-    -> Handler ()-handleResponse resE handle = case parseResponse resE of-    Just a  -> formatOutput a =<< R.asks configFormat-    Nothing -> return ()-  where-    formatOutput a format = case format of-        OutputJSON   -> liftIO . formatStr $ cs $-            JSON.encodePretty a-        OutputYAML   -> liftIO . formatStr $ cs $ YAML.encode a-        OutputNormal -> handle a--sendZmq :: (FromJSON a, ToJSON a)-        => WalletRequest-        -> Handler (Either String (WalletResponse a))-sendZmq req = do-    cfg <- R.ask-    let msg = cs $ Aeson.encode req-    when (configVerbose cfg) $ liftIO $-        B8.hPutStrLn stderr $ "Outgoing JSON: " `mappend` msg-    -- TODO: If I do this in the same thread I have to ^C twice to exit-    a <- async $ liftIO $ withContext $ \ctx ->-        withSocket ctx Req $ \sock -> do-            setLinger (restrict (0 :: Int)) sock-            setupAuth cfg sock-            connect sock (configConnect cfg)-            send sock [] (cs $ Aeson.encode req)-            eitherDecode . cs <$> receive sock-    wait a--setupAuth :: (SocketType t)-          => Config-          -> Socket t-          -> IO ()-setupAuth cfg sock = do-    let clientKeyM    = configClientKey    cfg-        clientKeyPubM = configClientKeyPub cfg-        serverKeyPubM = configServerKeyPub cfg-    forM_ clientKeyM $ \clientKey -> do-        let serverKeyPub = fromMaybe-              (error "Server public key not provided")-              serverKeyPubM-            clientKeyPub = fromMaybe-              (error "Client public key not provided")-              clientKeyPubM-        setCurveServerKey TextFormat serverKeyPub sock-        setCurvePublicKey TextFormat clientKeyPub sock-        setCurveSecretKey TextFormat clientKey sock--formatStr :: String -> IO ()-formatStr str = forM_ (lines str) putStrLn--encodeTxJSON :: Tx -> Value-encodeTxJSON tx = object-    [ "txid"     .= (cs $ txHashToHex (txHash tx) :: Text)-    , "version"  .= txVersion tx-    , "inputs"   .= map encodeTxInJSON (txIn tx)-    , "outputs"  .= map encodeTxOutJSON (txOut tx)-    , "locktime" .= txLockTime tx-    ]--encodeTxInJSON :: TxIn -> Value-encodeTxInJSON (TxIn o s i) = object $-    [ "outpoint"   .= encodeOutPointJSON o-    , "sequence"   .= i-    , "raw-script" .= (cs $ encodeHex s :: Text)-    , "script"     .= encodeScriptJSON sp-    ] ++ decoded-  where-    sp = fromMaybe (Script []) $ decodeToMaybe s-    decoded = either (const []) f $ decodeInputBS s-    f inp = ["decoded-script" .= encodeScriptInputJSON inp]--encodeTxOutJSON :: TxOut -> Value-encodeTxOutJSON (TxOut v s) = object $-    [ "value"      .= v-    , "raw-script" .= (cs $ encodeHex s :: Text)-    , "script"     .= encodeScriptJSON sp-    ] ++ decoded-  where-    sp = fromMaybe (Script []) $ decodeToMaybe s-    decoded = either (const [])-                 (\out -> ["decoded-script" .= encodeScriptOutputJSON out])-                 (decodeOutputBS s)--encodeOutPointJSON :: OutPoint -> Value-encodeOutPointJSON (OutPoint h i) = object-    [ "txid" .= (cs $ txHashToHex h :: Text)-    , "pos"  .= i-    ]--encodeScriptJSON :: Script -> Value-encodeScriptJSON (Script ops) =-    toJSON $ map f ops-  where-    f (OP_PUSHDATA bs _) = String $ pack $ unwords-        ["OP_PUSHDATA", cs $ encodeHex bs]-    f x = String $ pack $ show x--encodeScriptInputJSON :: ScriptInput -> Value-encodeScriptInputJSON si = case si of-    RegularInput (SpendPK s) -> object-        [ "spendpubkey" .= object [ "sig" .= encodeSigJSON s ] ]-    RegularInput (SpendPKHash s p) -> object-        [ "spendpubkeyhash" .= object-            [ "sig"            .= encodeSigJSON s-            , "pubkey"         .= (cs $ encodeHex (encode p) :: Text)-            , "sender-address" .= (cs $ addrToBase58 (pubKeyAddr p) :: Text)-            ]-        ]-    RegularInput (SpendMulSig sigs) -> object-        [ "spendmulsig" .= object [ "sigs" .= map encodeSigJSON sigs ] ]-    ScriptHashInput s r -> object-        [ "spendscripthash" .= object-            [ "scriptinput" .= encodeScriptInputJSON (RegularInput s)-            , "redeem" .= encodeScriptOutputJSON r-            , "raw-redeem" .= (cs $ encodeHex (encodeOutputBS r) :: Text)-            , "sender-address" .= (cs $ addrToBase58 (scriptAddr r) :: Text)-            ]-        ]--encodeScriptOutputJSON :: ScriptOutput -> Value-encodeScriptOutputJSON so = case so of-    PayPK p -> object-        [ "pay2pubkey" .= object-          [ "pubkey" .= (cs $ encodeHex (encode p) :: Text) ]-        ]-    PayPKHash a -> object-        [ "pay2pubkeyhash" .= object-            [ "address-base64" .=-              (cs $ encodeHex (encode $ getAddrHash a) :: Text)-            , "address-base58" .= (cs $ addrToBase58 a :: Text)-            ]-        ]-    PayMulSig ks r -> object-        [ "pay2mulsig" .= object-            [ "required-keys" .= r-            , "pubkeys"       .= (map (cs . encodeHex . encode) ks :: [Text])-            ]-        ]-    PayScriptHash a -> object-        [ "pay2scripthash" .= object-            [ "address-base64" .= (cs $ encodeHex $ encode $ getAddrHash a :: Text)-            , "address-base58" .= (cs (addrToBase58 a) :: Text)-            ]-        ]-    DataCarrier bs -> object-        [ "op_return" .= object-            [ "data" .= (cs $ encodeHex bs :: Text)-            ]-        ]--encodeSigJSON :: TxSignature -> Value-encodeSigJSON ts@(TxSignature _ sh) = object-    [ "raw-sig" .= (cs $ encodeHex (encodeSig ts) :: Text)-    , "sighash" .= encodeSigHashJSON sh-    ]--encodeSigHashJSON :: SigHash -> Value-encodeSigHashJSON sh = case sh of-    SigAll acp -> object-        [ "type" .= String "SigAll"-        , "acp"  .= acp-        ]-    SigNone acp -> object-        [ "type" .= String "SigNone"-        , "acp"  .= acp-        ]-    SigSingle acp -> object-        [ "type" .= String "SigSingle"-        , "acp"  .= acp-        ]-    SigUnknown acp v -> object-        [ "type"  .= String "SigUnknown"-        , "acp"   .= acp-        , "value" .= v-        ]--{- Print utilities -}--printAccount :: JsonAccount -> String-printAccount JsonAccount{..} = unlines $-    [ "Account : " ++ unpack jsonAccountName-    , "Type    : " ++ showType-    , "Gap     : " ++ show jsonAccountGap-    ]-    ++-    [ "Deriv   : " ++ pathToStr d-    | d <- maybeToList jsonAccountDerivation-    ]-    ++-    [ "Mnemonic: " ++ cs ms-    | ms <- maybeToList jsonAccountMnemonic-    ]-    ++-    concat [ printKeys | not (null jsonAccountKeys) ]-  where-    printKeys =-        ("Keys    : " ++ cs (xPubExport (head jsonAccountKeys))) :-        map (("          " ++) . cs . xPubExport) (tail jsonAccountKeys)-    showType = case jsonAccountType of-        AccountRegular -> if isNothing jsonAccountMaster-                              then "Read-Only" else "Regular"-        AccountMultisig m n -> unwords-            [ if isNothing jsonAccountMaster-                 then "Read-Only Multisig" else "Multisig"-            , show m, "of", show n-            ]--printAddress :: JsonAddr -> String-printAddress JsonAddr{..} = unwords $-    [ show jsonAddrIndex, ":", cs (addrToBase58 jsonAddrAddress) ]-    ++-    [ "(" ++ unpack jsonAddrLabel ++ ")" | not (null $ unpack jsonAddrLabel) ]-    ++ concat-    [ [ "[Received: "    ++ show (balanceInfoInBalance  bal) ++ "]"-      , "[Coins: "       ++ show (balanceInfoCoins      bal) ++ "]"-      , "[Spent Coins: " ++ show (balanceInfoSpentCoins bal) ++ "]"-      ]-    | isJust jsonAddrBalance && balanceInfoCoins bal > 0-    ]-  where-    bal = fromMaybe (error "Could not get address balance") jsonAddrBalance--printPubKey :: JsonAddr -> String-printPubKey JsonAddr{..} = unwords $-    [ show jsonAddrIndex, ":", showPubKey jsonAddrKey ]-    ++-    [ "(" ++ unpack jsonAddrLabel ++ ")" | not (null $ unpack jsonAddrLabel) ]-  where-    showPubKey = maybe "<no pubkey available>" (jsonStr2Str . toJSON)-    jsonStr2Str (String t) = cs t-    jsonStr2Str _          = ""     -- It totally is a String though--printNotif :: Notif -> String-printNotif (NotifTx   tx) = printTx Nothing tx-printNotif (NotifBlock b) = printBlock b--printTx :: Maybe Address -> JsonTx -> String-printTx aM tx@JsonTx{..} = unlines $-    [ "Id         : " ++ cs (txHashToHex jsonTxHash) ]-    ++-    [ "Value      : " ++ printTxType jsonTxType ++ " " ++ show jsonTxValue ]-    ++-    [ "Confidence : " ++ printTxConfidence tx ]-    ++ concat-    [ printAddrInfos "Inputs     : " jsonTxInputs-    | not (null jsonTxInputs)-    ]-    ++ concat-    [ printAddrInfos "Outputs    : " jsonTxOutputs-    | not (null jsonTxOutputs)-    ]-    ++ concat-    [ printAddrInfos "Change     : " jsonTxChange-    | not (null jsonTxChange)-    ]-  where-    printAddrInfos header xs =-        (header ++ f (head xs)) :-        map (("             " ++) . f) (tail xs)-    f (AddressInfo addr valM local) = unwords $-        cs (addrToBase58 addr) :-        [ show v | v <- maybeToList valM ]-        ++-        [ "<-" | maybe local (== addr) aM ]--printTxConfidence :: JsonTx -> String-printTxConfidence JsonTx{..} = case jsonTxConfidence of-    TxBuilding -> "Building" ++ confirmations-    TxPending  -> "Pending" ++ confirmations-    TxDead     -> "Dead" ++ confirmations-    TxOffline  -> "Offline"-  where-    confirmations = case jsonTxConfirmations of-        Just conf -> " (Confirmations: " ++ show conf ++ ")"-        _         -> ""--printTxType :: TxType -> String-printTxType t = case t of-    TxIncoming -> "Incoming"-    TxOutgoing -> "Outgoing"-    TxSelf     -> "Self"--printBlock :: JsonBlock -> String-printBlock JsonBlock{..} = unlines-    [ "Block Hash      : " ++ cs (blockHashToHex jsonBlockHash)-    , "Block Height    : " ++ show jsonBlockHeight-    , "Previous block  : " ++ cs (blockHashToHex jsonBlockPrev)-    ]--printSyncBlock :: JsonSyncBlock -> String-printSyncBlock JsonSyncBlock{..} = unlines-    [ "Block Hash      : " ++ cs (blockHashToHex jsonSyncBlockHash)-    , "Block Height    : " ++ show jsonSyncBlockHeight-    , "Previous block  : " ++ cs (blockHashToHex jsonSyncBlockPrev)-    , "Transactions    : " ++ show (length jsonSyncBlockTxs)-    ]--printNodeStatus :: Bool -> NodeStatus -> [String]-printNodeStatus verbose NodeStatus{..} =-    [ "Network Height    : " ++ show nodeStatusNetworkHeight-    , "Best Header       : " ++ cs (blockHashToHex nodeStatusBestHeader)-    , "Best Header Height: " ++ show nodeStatusBestHeaderHeight-    , "Best Block        : " ++ cs (blockHashToHex nodeStatusBestBlock)-    , "Best Block Height : " ++ show nodeStatusBestBlockHeight-    , "Bloom Filter Size : " ++ show nodeStatusBloomSize-    ] ++-    [ "Header Peer       : " ++ show h-    | h <- maybeToList nodeStatusHeaderPeer, verbose-    ] ++-    [ "Merkle Peer       : " ++ show m-    | m <- maybeToList nodeStatusMerklePeer, verbose-    ] ++-    [ "Pending Headers   : " ++ show nodeStatusHaveHeaders | verbose ] ++-    [ "Pending Tickles   : " ++ show nodeStatusHaveTickles | verbose ] ++-    [ "Pending Txs       : " ++ show nodeStatusHaveTxs | verbose ] ++-    [ "Pending GetData   : " ++ show (map txHashToHex nodeStatusGetData)-    | verbose-    ] ++-    [ "Pending Rescan    : " ++ show r-    | r <- maybeToList nodeStatusRescan, verbose-    ] ++-    [ "Synced Mempool    : " ++ show nodeStatusMempool | verbose ] ++-    [ "HeaderSync Lock   : " ++ show nodeStatusSyncLock | verbose ] ++-    [ "Peers: " ] ++-    intercalate ["-"] (map (printPeerStatus verbose) nodeStatusPeers)--printPeerStatus :: Bool -> PeerStatus -> [String]-printPeerStatus verbose PeerStatus{..} =-    [ "  Peer Id  : " ++ show peerStatusPeerId-    , "  Peer Host: " ++ peerHostString peerStatusHost-    , "  Connected: " ++ if peerStatusConnected then "yes" else "no"-    , "  Height   : " ++ show peerStatusHeight-    ] ++-    [ "  Protocol : " ++ show p | p <- maybeToList peerStatusProtocol-    ] ++-    [ "  UserAgent: " ++ ua | ua <- maybeToList peerStatusUserAgent-    ] ++-    [ "  Avg Ping : " ++ p | p <- maybeToList peerStatusPing-    ] ++-    [ "  DoS Score: " ++ show d | d <- maybeToList peerStatusDoSScore-    ] ++-    [ "  Merkles  : " ++ show peerStatusHaveMerkles | verbose ] ++-    [ "  Messages : " ++ show peerStatusHaveMessage | verbose ] ++-    [ "  Nonces   : " ++ show peerStatusPingNonces | verbose ] ++-    [ "  Reconnect: " ++ show t-    | t <- maybeToList peerStatusReconnectTimer, verbose-    ] ++-    [ "  Logs     : " | verbose ] ++-    [ "    - " ++ msg | msg <- fromMaybe [] peerStatusLog, verbose]--printBlockInfo :: BlockInfo -> [String]-printBlockInfo BlockInfo{..} =-    [ "Block Height     : " ++ show blockInfoHeight-    , "Block Hash       : " ++ cs (blockHashToHex blockInfoHash)-    , "Block Timestamp  : " ++ formatUTCTime blockInfoTimestamp-    , "Previous Block   : " ++ cs (blockHashToHex blockInfoPrevBlock)-    , "Merkle Root      : " ++ cs blockInfoMerkleRoot-    , "Block Version    : " ++ "0x" ++ cs (encodeHex versionData)-    , "Block Difficulty : " ++ show (blockDiff blockInfoBits)-    , "Chain Work       : " ++ show blockInfoChainWork-    ]-  where-    blockDiff :: Word32 -> Double-    blockDiff target = getTarget (blockBits genesisHeader) / getTarget target-    getTarget   = fromIntegral . decodeCompact-    versionData = integerToBS (fromIntegral blockInfoVersion)-    formatUTCTime = Time.formatTime Time.defaultTimeLocale-        "%Y-%m-%d %H:%M:%S (UTC)"
− Network/Haskoin/Wallet/Client/PrettyJson.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE CPP #-}-module Network.Haskoin.Wallet.Client.PrettyJson-( encodePretty )-where--import qualified Data.ByteString.Lazy  as BL-import Data.Aeson                                (ToJSON)-import Data.Aeson.Encode.Pretty        as Export (Config (..),-                                                  defConfig,-                                                  encodePretty')---- aeson-pretty 0.8.0 introduces a new way to specify indentation-#if MIN_VERSION_aeson_pretty(0,8,0)-import Data.Aeson.Encode.Pretty        as Export (Indent(..))-jsonIndent :: Indent-jsonIndent = Spaces 2-#else-jsonIndent :: Int-jsonIndent = 2-#endif--encodePretty :: ToJSON a => a -> BL.ByteString-encodePretty = encodePretty' defConfig{ confIndent = jsonIndent }
− Network/Haskoin/Wallet/Database.hs
@@ -1,17 +0,0 @@-module Network.Haskoin.Wallet.Database where--import Control.Monad.Logger (MonadLoggerIO)-import Control.Monad.Trans.Control (MonadBaseControl)--import Database.Persist.Sql (ConnectionPool)-import Database.Persist.Sqlite (SqliteConf(..), createSqlitePool)--type DatabaseConfType = SqliteConf--getDatabasePool :: (MonadLoggerIO m, MonadBaseControl IO m)-                => DatabaseConfType -> m ConnectionPool-getDatabasePool conf = createSqlitePool (sqlDatabase conf) (sqlPoolSize conf)--paramLimit :: Int-paramLimit = 20-
− Network/Haskoin/Wallet/Internals.hs
@@ -1,31 +0,0 @@-{-|-This module expose haskoin-wallet internals. No guarantee is made on the-stability of the interface of these internal modules.--}--module Network.Haskoin.Wallet.Internals-( module Network.Haskoin.Wallet-, module Network.Haskoin.Wallet.Accounts-, module Network.Haskoin.Wallet.Transaction-, module Network.Haskoin.Wallet.Client-, module Network.Haskoin.Wallet.Client.Commands-, module Network.Haskoin.Wallet.Server-, module Network.Haskoin.Wallet.Server.Handler-, module Network.Haskoin.Wallet.Settings-, module Network.Haskoin.Wallet.Database-, module Network.Haskoin.Wallet.Types-, module Network.Haskoin.Wallet.Model-) where--import Network.Haskoin.Wallet-import Network.Haskoin.Wallet.Accounts-import Network.Haskoin.Wallet.Transaction-import Network.Haskoin.Wallet.Client-import Network.Haskoin.Wallet.Client.Commands-import Network.Haskoin.Wallet.Server-import Network.Haskoin.Wallet.Server.Handler-import Network.Haskoin.Wallet.Settings-import Network.Haskoin.Wallet.Database-import Network.Haskoin.Wallet.Types-import Network.Haskoin.Wallet.Model-
− Network/Haskoin/Wallet/Model.hs
@@ -1,134 +0,0 @@-module Network.Haskoin.Wallet.Model-( -- Database types-  Account(..)-, AccountId-, WalletAddr(..)-, WalletAddrId-, WalletState(..)-, WalletStateId-, WalletCoin(..)-, WalletCoinId-, SpentCoin(..)-, SpentCoinId-, WalletTx(..)-, WalletTxId-, EntityField(..)-, Unique(..)-, migrateWallet---- JSON conversion-, toJsonAccount-, toJsonAddr-, toJsonCoin-, toJsonTx--) where--import Data.Word (Word32, Word64)-import Data.Time (UTCTime)-import Data.Text (Text)-import Data.String.Conversions (cs)--import Database.Persist (EntityField, Unique)-import Database.Persist.Quasi (lowerCaseSettings)-import Database.Persist.TH-    ( share-    , mkPersist-    , sqlSettings-    , mkMigrate-    , persistFileWith-    )--import Network.Haskoin.Wallet.Types-import Network.Haskoin.Block-import Network.Haskoin.Transaction-import Network.Haskoin.Script-import Network.Haskoin.Crypto-import Network.Haskoin.Node-import Network.Haskoin.Node.HeaderTree--share [ mkPersist sqlSettings-      , mkMigrate "migrateWallet"-      ]-    $(persistFileWith lowerCaseSettings "config/models")--{- JSON Types -}--toJsonAccount :: Maybe Mnemonic -> Account -> JsonAccount-toJsonAccount msM acc = JsonAccount-    { jsonAccountName         = accountName acc-    , jsonAccountType         = accountType acc-    , jsonAccountMnemonic     = fmap cs msM-    , jsonAccountMaster       = accountMaster acc-    , jsonAccountDerivation   = accountDerivation acc-    , jsonAccountKeys         = accountKeys acc-    , jsonAccountGap          = accountGap acc-    , jsonAccountCreated      = accountCreated acc-    }--toJsonAddr :: WalletAddr-           -> Maybe BalanceInfo-           -> JsonAddr-toJsonAddr addr balM = JsonAddr-    { jsonAddrAddress        = walletAddrAddress addr-    , jsonAddrIndex          = walletAddrIndex addr-    , jsonAddrType           = walletAddrType addr-    , jsonAddrLabel          = walletAddrLabel addr-    , jsonAddrRedeem         = walletAddrRedeem addr-    , jsonAddrKey            = walletAddrKey addr-    , jsonAddrCreated        = walletAddrCreated addr-    , jsonAddrBalance        = balM-    }--toJsonTx :: AccountName-         -> Maybe (BlockHash, BlockHeight) -- ^ Current best block-         -> WalletTx-         -> JsonTx-toJsonTx acc bbM tx = JsonTx-    { jsonTxHash            = walletTxHash tx-    , jsonTxNosigHash       = walletTxNosigHash tx-    , jsonTxType            = walletTxType tx-    , jsonTxInValue         = walletTxInValue tx-    , jsonTxOutValue        = walletTxOutValue tx-    , jsonTxValue           = fromIntegral (walletTxInValue tx) --                              fromIntegral (walletTxOutValue tx)-    , jsonTxInputs          = walletTxInputs tx-    , jsonTxOutputs         = walletTxOutputs tx-    , jsonTxChange          = walletTxChange tx-    , jsonTxTx              = walletTxTx tx-    , jsonTxIsCoinbase      = walletTxIsCoinbase tx-    , jsonTxConfidence      = walletTxConfidence tx-    , jsonTxConfirmedBy     = walletTxConfirmedBy tx-    , jsonTxConfirmedHeight = walletTxConfirmedHeight tx-    , jsonTxConfirmedDate   = walletTxConfirmedDate tx-    , jsonTxCreated         = walletTxCreated tx-    , jsonTxAccount         = acc-    , jsonTxConfirmations   = f =<< walletTxConfirmedHeight tx-    , jsonTxBestBlock       = fst <$> bbM-    , jsonTxBestBlockHeight = snd <$> bbM-    }-  where-    f confirmedHeight = case bbM of-        Just (_, h) -> return $ fromInteger $-            max 0 $ toInteger h - toInteger confirmedHeight + 1-        _ -> Nothing--toJsonCoin :: WalletCoin-           -> Maybe JsonTx   -- ^ Coin’s transaction-           -> Maybe JsonAddr -- ^ Coin’s address-           -> Maybe JsonTx   -- ^ Coin’s spending transaction-           -> JsonCoin-toJsonCoin coin txM addrM spendM = JsonCoin-    { jsonCoinHash       = walletCoinHash coin-    , jsonCoinPos        = walletCoinPos coin-    , jsonCoinValue      = walletCoinValue coin-    , jsonCoinScript     = walletCoinScript coin-    , jsonCoinCreated    = walletCoinCreated coin-    -- Optional tx-    , jsonCoinTx         = txM-    -- Optional address-    , jsonCoinAddress    = addrM-    -- Optional spending tx-    , jsonCoinSpendingTx = spendM-    }-
− Network/Haskoin/Wallet/Server.hs
@@ -1,510 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TemplateHaskell       #-}-module Network.Haskoin.Wallet.Server-( runSPVServer-, stopSPVServer-, runSPVServerWithContext-) where--import           Control.Concurrent.Async.Lifted       (async, link,-                                                        waitAnyCancel)-import           Control.Concurrent.STM                (atomically, retry)-import           Control.Concurrent.STM.TBMChan        (TBMChan, newTBMChan,-                                                        readTBMChan)-import           Control.DeepSeq                       (NFData (..))-import           Control.Exception.Lifted              (ErrorCall (..),-                                                        SomeException (..),-                                                        catches)-import qualified Control.Exception.Lifted              as E (Handler (..))-import           Control.Monad                         (forM_, forever, unless,-                                                        void, when)-import           Control.Monad.Base                    (MonadBase)-import           Control.Monad.Catch                   (MonadThrow)-import           Control.Monad.Fix                     (fix)-import           Control.Monad.Logger                  (MonadLoggerIO,-                                                        filterLogger, logDebug,-                                                        logError, logInfo,-                                                        logWarn,-                                                        runStdoutLoggingT)-import           Control.Monad.Trans                   (lift, liftIO)-import           Control.Monad.Trans.Control           (MonadBaseControl,-                                                        liftBaseOpDiscard)-import           Control.Monad.Trans.Resource          (MonadResource,-                                                        runResourceT)-import           Data.Aeson                            (Value, decode, encode)-import           Data.ByteString                       (ByteString)-import qualified Data.ByteString.Lazy                  as BL (fromStrict,-                                                              toStrict)-import           Data.Conduit                          (await, awaitForever,-                                                        ($$))-import qualified Data.HashMap.Strict                   as H (lookup)-import           Data.List.NonEmpty                    (NonEmpty ((:|)))-import qualified Data.Map.Strict                       as M (Map, assocs, elems,-                                                             empty,-                                                             fromListWith, null,-                                                             unionWith)-import           Data.Maybe                            (fromJust, fromMaybe,-                                                        isJust)-import           Data.Monoid                           ((<>))-import           Data.String.Conversions               (cs)-import           Data.Text                             (pack)-import           Data.Word                             (Word32)-import           Database.Esqueleto                    (from, val, where_,-                                                        (&&.), (<=.), (==.),-                                                        (^.))-import           Database.Persist.Sql                  (ConnectionPool,-                                                        runMigration)-import           Network.Haskoin.Block-import           Network.Haskoin.Constants-import           Network.Haskoin.Node.BlockChain-import           Network.Haskoin.Node.HeaderTree-import           Network.Haskoin.Node.Peer-import           Network.Haskoin.Node.STM-import           Network.Haskoin.Transaction-import           Network.Haskoin.Wallet.Accounts-import           Network.Haskoin.Wallet.Database-import           Network.Haskoin.Wallet.Model-import           Network.Haskoin.Wallet.Server.Handler-import           Network.Haskoin.Wallet.Settings-import           Network.Haskoin.Wallet.Transaction-import           Network.Haskoin.Wallet.Types-import           System.Posix.Daemon                   (Redirection (ToFile),-                                                        killAndWait,-                                                        runDetached)-import           System.ZMQ4                           (Context, KeyFormat (..),-                                                        Pub (..), Rep (..),-                                                        Socket, bind, receive,-                                                        receiveMulti, restrict,-                                                        send, sendMulti,-                                                        setCurveSecretKey,-                                                        setCurveServer,-                                                        setLinger, withContext,-                                                        withSocket, z85Decode)--data EventSession = EventSession-    { eventBatchSize :: !Int-    , eventNewAddrs  :: !(M.Map AccountId Word32)-    }-    deriving (Eq, Show, Read)--instance NFData EventSession where-    rnf EventSession{..} =-        rnf eventBatchSize `seq`-        rnf (M.elems eventNewAddrs)--runSPVServer :: Config -> IO ()-runSPVServer cfg = maybeDetach cfg $ -- start the server process-    withContext (run . runSPVServerWithContext cfg)-  where-    -- Setup logging monads-    run          = runResourceT . runLogging-    runLogging   = runStdoutLoggingT . filterLogger logFilter-    logFilter _ level = level >= configLogLevel cfg---- |Run the server, and use the specifed ZeroMQ context.---  Useful if you want to communicate with the server using---  the "inproc" ZeroMQ transport, where a shared context is---  required.-runSPVServerWithContext :: ( MonadLoggerIO m-                           , MonadBaseControl IO m-                           , MonadBase IO m-                           , MonadThrow m-                           , MonadResource m-                           )-                        => Config -> Context -> m ()-runSPVServerWithContext cfg ctx = do-    -- Initialize the database-    -- Check the operation mode of the server.-    pool <- initDatabase cfg-    -- Notification channel-    notif <- liftIO $ atomically $ newTBMChan 1000-    case configMode cfg of-        -- In this mode, we do not launch an SPV node. We only accept-        -- client requests through the ZMQ API.-        SPVOffline -> do-            let session = HandlerSession cfg pool Nothing notif-            as <- mapM async-                -- Run the ZMQ API-command server-                [ runWalletCmd ctx session-                -- Run the ZMQ notification thread-                , runWalletNotif ctx session-                ]-            mapM_ link as-            (_,r) <- waitAnyCancel as-            return r-        -- In this mode, we launch the client ZMQ API and we sync the-        -- wallet database with an SPV node.-        SPVOnline -> do-            -- Initialize the node state-            node <- getNodeState (Right pool)-            -- Spin up the node threads-            let session = HandlerSession cfg pool (Just node) notif-            as <- mapM async-                -- Start the SPV node-                [ runNodeT (spv pool) node-                -- Merkle block synchronization-                , runNodeT (runMerkleSync pool notif) node-                -- Import solo transactions as they arrive from peers-                , runNodeT (txSource $$ processTx pool notif) node-                -- Respond to transaction GetData requests-                , runNodeT (handleGetData $ (`runDBPool` pool) . getTx) node-                -- Re-broadcast pending transactions-                , runNodeT (broadcastPendingTxs pool) node-                -- Run the ZMQ API-command server-                , runWalletCmd ctx session-                -- Run the ZMQ notification thread-                , runWalletNotif ctx session-                ]-            mapM_ link as-            (_,r) <- waitAnyCancel as-            $(logDebug) "Exiting main thread"-            return r-  where-    spv pool = do-        -- Get our bloom filter-        (bloom, elems, _) <- runDBPool getBloomFilter pool-        startSPVNode hosts bloom elems-        -- Bitcoin nodes to connect to-    nodes = fromMaybe-        (error $ "BTC nodes for " ++ networkName ++ " not found")-        (pack networkName `H.lookup` configBTCNodes cfg)-    hosts = map (\x -> PeerHost (btcNodeHost x) (btcNodePort x)) nodes-    -- Run the merkle syncing thread-    runMerkleSync pool notif = do-        $(logDebug) "Waiting for a valid bloom filter for merkle downloads..."--        -- Only download merkles if we have a valid bloom filter-        _ <- atomicallyNodeT waitBloomFilter--        -- Provide a fast catchup time if we are at height 0-        fcM <- fmap (fmap adjustFCTime) $ (`runDBPool` pool) $ do-            (_, h) <- walletBestBlock-            if h == 0 then firstAddrTime else return Nothing-        maybe (return ()) (atomicallyNodeT . rescanTs) fcM--        -- Start the merkle sync-        merkleSync pool 500 notif-        $(logDebug) "Exiting Merkle-sync thread"-    -- Run a thread that will re-broadcast pending transactions-    broadcastPendingTxs pool = forever $ do-        (hash, _) <- runSqlNodeT $ walletBestBlock-        -- Wait until we are synced-        atomicallyNodeT $ do-            synced <- areBlocksSynced hash-            unless synced $ lift retry-        -- Send an INV for those transactions to all peers-        broadcastTxs =<< runDBPool (getPendingTxs 0) pool-        -- Wait until we are not synced-        atomicallyNodeT $ do-            synced <- areBlocksSynced hash-            when synced $ lift retry-        $(logDebug) "Exiting tx-broadcast thread"-    processTx pool notif = do-        awaitForever $ \tx -> lift $ do-            (_, newAddrs) <- runDBPool (importNetTx tx (Just notif)) pool-            unless (null newAddrs) $ do-                $(logInfo) $ pack $ unwords-                    [ "Generated", show $ length newAddrs-                    , "new addresses while importing the tx."-                    , "Updating the bloom filter"-                    ]-                (bloom, elems, _) <- runDBPool getBloomFilter pool-                atomicallyNodeT $ sendBloomFilter bloom elems-        $(logDebug) "Exiting tx-import thread"--initDatabase :: (MonadBaseControl IO m, MonadLoggerIO m)-             => Config -> m ConnectionPool-initDatabase cfg = do-    -- Create a database pool-    let dbCfg = fromMaybe-            (error $ "DB config settings for " ++ networkName ++ " not found")-            (pack networkName `H.lookup` configDatabase cfg)-    pool <- getDatabasePool dbCfg-    -- Initialize wallet database-    flip runDBPool pool $ do-        _ <- runMigration migrateWallet-        _ <- runMigration migrateHeaderTree-        initWallet $ configBloomFP cfg-    -- Return the semaphrone and the connection pool-    return pool--merkleSync-    :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m, MonadResource m)-    => ConnectionPool-    -> Word32-    -> TBMChan Notif-    -> NodeT m ()-merkleSync pool bSize notif = do-    -- Get our best block-    (hash, _) <- runDBPool walletBestBlock pool-    $(logDebug) "Starting merkle batch download"-    -- Wait for a new block or a rescan-    (action, source) <- merkleDownload hash bSize-    $(logDebug) "Received a merkle action and source. Processing the source..."--    -- Read and process the data from the source-    (lastMerkleM, mTxsAcc, aMap) <- source $$ go Nothing [] M.empty-    $(logDebug) "Merkle source processed and closed"--    -- Send a new bloom filter to our peers if new addresses were generated-    unless (M.null aMap) $ do-        $(logInfo) $ pack $ unwords-            [ "Generated", show $ sum $ M.elems aMap-            , "new addresses while importing the merkle block."-            , "Sending our bloom filter."-            ]-        (bloom, elems, _) <- runDBPool getBloomFilter pool-        atomicallyNodeT $ sendBloomFilter bloom elems--    -- Check if we should rescan the current merkle batch-    $(logDebug) "Checking if we need to rescan the current batch..."-    rescan <- shouldRescan aMap-    when rescan $ $(logDebug) "We need to rescan the current batch"-    -- Compute the new batch size-    let newBSize | rescan    = max 1 $ bSize `div` 2-                 | otherwise = min 500 $ bSize + max 1 (bSize `div` 20)--    when (newBSize /= bSize) $ $(logDebug) $ pack $ unwords-        [ "Changing block batch size from", show bSize, "to", show newBSize ]--    -- Did we receive all the merkles that we asked for ?-    let missing = (headerHash <$> lastMerkleM) /=-            Just (nodeHash $ last $ actionNodes action)--    when missing $ $(logWarn) $ pack $ unwords-        [ "Merkle block stream closed prematurely"-        , show lastMerkleM-        ]--    -- TODO: We could still salvage a partially received batch-    unless (rescan || missing) $ do-        $(logDebug) "Importing merkles into the wallet..."-        -- Confirm the transactions-        runDBPool (importMerkles action mTxsAcc (Just notif)) pool-        $(logDebug) "Done importing merkles into the wallet"-        logBlockChainAction action--    merkleSync pool newBSize notif-  where-    go lastMerkleM mTxsAcc aMap = await >>= \resM -> case resM of-        Just (Right tx) -> do-            $(logDebug) $ pack $ unwords-                [ "Importing merkle tx", cs $ txHashToHex $ txHash tx ]-            (_, newAddrs) <- lift $ runDBPool (importNetTx tx Nothing) pool-            $(logDebug) $ pack $ unwords-                [ "Generated", show $ length newAddrs-                , "new addresses while importing tx"-                , cs $ txHashToHex $ txHash tx-                ]-            let newMap = M.unionWith (+) aMap $ groupByAcc newAddrs-            go lastMerkleM mTxsAcc newMap-        Just (Left (MerkleBlock mHead _ _ _, mTxs)) -> do-            $(logDebug) $ pack $ unwords-                [ "Buffering merkle block"-                , cs $ blockHashToHex $ headerHash mHead-                ]-            go (Just mHead) (mTxs:mTxsAcc) aMap-        -- Done processing this batch. Reverse mTxsAcc as we have been-        -- prepending new values to it.-        _ -> return (lastMerkleM, reverse mTxsAcc, aMap)-    groupByAcc addrs =-        let xs = map (\a -> (walletAddrAccount a, 1)) addrs-        in  M.fromListWith (+) xs-    shouldRescan aMap = do-        -- Try to find an account whos gap is smaller than the number of new-        -- addresses generated in that account.-        res <- (`runDBPool` pool) $ splitSelect (M.assocs aMap) $ \ks ->-            from $ \a -> do-                let andCond (ai, cnt) =-                        a ^. AccountId ==. val ai &&.-                        a ^. AccountGap <=. val cnt-                where_ $ join2 $ map andCond ks-                return $ a ^. AccountId-        return $ not $ null res-    -- Some logging of the blocks-    logBlockChainAction action = case action of-        BestChain nodes -> $(logInfo) $ pack $ unwords-            [ "Best chain height"-            , show $ nodeBlockHeight $ last nodes-            , "(", cs $ blockHashToHex $ nodeHash $ last nodes-            , ")"-            ]-        ChainReorg _ o n -> $(logInfo) $ pack $ unlines $-            [ "Chain reorg."-            , "Orphaned blocks:"-            ]-            ++ map (("  " ++) . cs . blockHashToHex . nodeHash) o-            ++ [ "New blocks:" ]-            ++ map (("  " ++) . cs . blockHashToHex . nodeHash) n-            ++ [ unwords [ "Best merkle chain height"-                        , show $ nodeBlockHeight $ last n-                        ]-            ]-        SideChain n -> $(logWarn) $ pack $ unlines $-            "Side chain:" :-            map (("  " ++) . cs . blockHashToHex . nodeHash) n-        KnownChain n -> $(logWarn) $ pack $ unlines $-            "Known chain:" :-            map (("  " ++) . cs . blockHashToHex . nodeHash) n--maybeDetach :: Config -> IO () -> IO ()-maybeDetach cfg action =-    if configDetach cfg then runDetached pidFile logFile action >> logStarted else action-  where-    logStarted = putStrLn "Process started"-    pidFile = Just $ configPidFile cfg-    logFile = ToFile $ configLogFile cfg--stopSPVServer :: Config -> IO ()-stopSPVServer cfg =-    -- TODO: Should we send a message instead of killing the process ?-    killAndWait $ configPidFile cfg---- Run the main ZeroMQ loop--- TODO: Support concurrent requests using DEALER socket when we can do--- concurrent MySQL requests.-runWalletNotif :: ( MonadLoggerIO m-                , MonadBaseControl IO m-                , MonadBase IO m-                , MonadThrow m-                , MonadResource m-                )-               => Context -> HandlerSession -> m ()-runWalletNotif ctx session =-    liftBaseOpDiscard (withSocket ctx Pub) $ \sock -> do-        liftIO $ setLinger (restrict (0 :: Int)) sock-        setupCrypto ctx sock session-        liftIO $ bind sock $ configBindNotif $ handlerConfig session-        forever $ do-            xM <- liftIO $ atomically $ readTBMChan $ handlerNotifChan session-            forM_ xM $ \x ->-                let (typ, pay) = case x of-                        NotifBlock _ ->-                            ("[block]", cs $ encode x)-                        NotifTx JsonTx{..} ->-                            ("{" <> cs jsonTxAccount <> "}", cs $ encode x)-                in liftIO $ sendMulti sock $ typ :| [pay]--runWalletCmd :: ( MonadLoggerIO m-                , MonadBaseControl IO m-                , MonadBase IO m-                , MonadThrow m-                , MonadResource m-                )-             => Context -> HandlerSession -> m ()-runWalletCmd ctx session = do-    liftBaseOpDiscard (withSocket ctx Rep) $ \sock -> do-        liftIO $ setLinger (restrict (0 :: Int)) sock-        setupCrypto ctx sock session-        liftIO $ bind sock $ configBind $ handlerConfig session-        fix $ \loop -> do-            bs  <- liftIO $ receive sock-            let msg = decode $ BL.fromStrict bs-            res <- case msg of-                Just StopServerR -> do-                    $(logInfo) "Received StopServer request"-                    return (ResponseValid Nothing)-                Just r  -> catchErrors $-                    runHandler (dispatchRequest r) session-                Nothing -> return $ ResponseError "Could not decode request"-            liftIO $ send sock [] $ BL.toStrict $ encode res-            unless (msg == Just StopServerR) loop-    $(logInfo) "Exiting ZMQ command thread..."-  where-    catchErrors m = catches m-        [ E.Handler $ \(WalletException err) -> do-            $(logError) $ pack err-            return $ ResponseError $ pack err-        , E.Handler $ \(ErrorCall err) -> do-            $(logError) $ pack err-            return $ ResponseError $ pack err-        , E.Handler $ \(SomeException exc) -> do-            $(logError) $ pack $ show exc-            return $ ResponseError $ pack $ show exc-        ]--setupCrypto :: (MonadLoggerIO m, MonadBaseControl IO m)-            => Context -> Socket a -> HandlerSession -> m ()-setupCrypto ctx' sock session = do-    when (isJust serverKeyM) $ liftIO $ do-        let k = fromJust $ configServerKey $ handlerConfig session-        setCurveServer True sock-        setCurveSecretKey TextFormat k sock-    when (isJust clientKeyPubM) $ do-        k <- z85Decode (fromJust clientKeyPubM)-        void $ async $ runZapAuth ctx' k-  where-    cfg = handlerConfig session-    serverKeyM = configServerKey cfg-    clientKeyPubM = configClientKeyPub cfg--runZapAuth :: ( MonadLoggerIO m-              , MonadBaseControl IO m-              , MonadBase IO m-              )-           => Context -> ByteString -> m ()-runZapAuth ctx k = do-    $(logDebug) $ "Starting ØMQ authentication thread"-    liftBaseOpDiscard (withSocket ctx Rep) $ \zap -> do-        liftIO $ setLinger (restrict (0 :: Int)) zap-        liftIO $ bind zap "inproc://zeromq.zap.01"-        forever $ do-            buffer <- liftIO $ receiveMulti zap-            let actionE =-                    case buffer of-                      v:q:_:_:_:m:p:_ -> do-                          when (v /= "1.0") $-                              Left (q, "500", "Version number not valid")-                          when (m /= "CURVE") $-                              Left (q, "400", "Mechanism not supported")-                          when (p /= k) $-                              Left (q, "400", "Invalid client public key")-                          return q-                      _ -> Left ("", "500", "Malformed request")-            case actionE of-              Right q -> do-                  $(logInfo) "Authenticated client successfully"-                  liftIO $ sendMulti zap $-                      "1.0" :| [q, "200", "OK", "client", ""]-              Left (q, c, m) -> do-                  $(logError) $ pack $ unwords-                      [ "Failed to authenticate client:" , cs c, cs m ]-                  liftIO $ sendMulti zap $-                      "1.0" :| [q, c, m, "", ""]---dispatchRequest :: ( MonadLoggerIO m-                   , MonadBaseControl IO m-                   , MonadBase IO m-                   , MonadThrow m-                   , MonadResource m-                   )-                => WalletRequest -> Handler m (WalletResponse Value)-dispatchRequest req = fmap ResponseValid $ case req of-    GetAccountsR p            -> getAccountsR p-    PostAccountsR na          -> postAccountsR na-    PostAccountRenameR n n'   -> postAccountRenameR n n'-    GetAccountR n             -> getAccountR n-    PostAccountKeysR n ks     -> postAccountKeysR n ks-    PostAccountGapR n g       -> postAccountGapR n g-    GetAddressesR n t m o p   -> getAddressesR n t m o p-    GetAddressesUnusedR n t p -> getAddressesUnusedR n t p-    GetAddressR n i t m o     -> getAddressR n i t m o-    GetIndexR n k t           -> getIndexR n k t-    PutAddressR n i t l       -> putAddressR n i t l-    PostAddressesR n i t      -> postAddressesR n i t-    GetTxsR n p               -> getTxsR n p-    GetAddrTxsR n i t p       -> getAddrTxsR n i t p-    PostTxsR n k a            -> postTxsR n k a-    GetTxR n h                -> getTxR n h-    GetOfflineTxR n h         -> getOfflineTxR n h-    PostOfflineTxR n k t c    -> postOfflineTxR n k t c-    GetBalanceR n mc o        -> getBalanceR n mc o-    PostNodeR na              -> postNodeR na-    DeleteTxIdR t             -> deleteTxIdR t-    GetSyncR a n b            -> getSyncR a (Right n) b-    GetSyncHeightR a n b      -> getSyncR a (Left n) b-    GetPendingR a p           -> getPendingR a p-    GetDeadR a p              -> getDeadR a p-    GetBlockInfoR l           -> getBlockInfoR l-    StopServerR               -> error "Should be handled elsewhere"
− Network/Haskoin/Wallet/Server/Handler.hs
@@ -1,604 +0,0 @@-{-# LANGUAGE FlexibleContexts      #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RecordWildCards       #-}-{-# LANGUAGE TemplateHaskell       #-}-module Network.Haskoin.Wallet.Server.Handler where--import           Control.Arrow                      (first)-import           Control.Concurrent.STM.TBMChan     (TBMChan)-import           Control.Exception                  (SomeException (..),-                                                     tryJust)-import           Control.Monad                      (liftM, forM, unless, when)-import           Control.Monad.Base                 (MonadBase)-import           Control.Monad.Catch                (MonadThrow, throwM)-import           Control.Monad.Logger               (MonadLoggerIO, logError,-                                                     logInfo)-import           Control.Monad.Reader               (ReaderT, asks, runReaderT)-import           Control.Monad.Trans                (MonadIO, lift, liftIO)-import           Control.Monad.Trans.Control        (MonadBaseControl)-import           Control.Monad.Trans.Resource       (MonadResource)-import           Data.Aeson                         (Value (..), toJSON)-import qualified Data.Map.Strict                    as M (elems, fromList,-                                                          intersectionWith)-import           Data.String.Conversions            (cs)-import           Data.Text                          (Text, pack, unpack)-import           Data.Word                          (Word32)-import           Data.Maybe                         (catMaybes)-import           Database.Esqueleto                 (Entity (..), SqlPersistT)-import           Database.Persist.Sql               (ConnectionPool,-                                                     SqlPersistM,-                                                     runSqlPersistMPool,-                                                     runSqlPool)-import           Network.Haskoin.Block-import           Network.Haskoin.Crypto-import           Network.Haskoin.Node.BlockChain-import           Network.Haskoin.Node.HeaderTree-import           Network.Haskoin.Node.Peer-import           Network.Haskoin.Node.STM-import           Network.Haskoin.Transaction-import           Network.Haskoin.Wallet.Accounts-import           Network.Haskoin.Wallet.Block-import           Network.Haskoin.Wallet.Model-import           Network.Haskoin.Wallet.Settings-import           Network.Haskoin.Wallet.Transaction-import           Network.Haskoin.Wallet.Types-import           Network.Haskoin.Wallet.Types.BlockInfo (fromNodeBlock)--type Handler m = ReaderT HandlerSession m--data HandlerSession = HandlerSession-    { handlerConfig    :: !Config-    , handlerPool      :: !ConnectionPool-    , handlerNodeState :: !(Maybe SharedNodeState)-    , handlerNotifChan :: !(TBMChan Notif)-    }--runHandler :: Monad m => Handler m a -> HandlerSession -> m a-runHandler = runReaderT--runDB :: MonadBaseControl IO m => SqlPersistT m a -> Handler m a-runDB action = asks handlerPool >>= lift . runDBPool action--runDBPool :: MonadBaseControl IO m => SqlPersistT m a -> ConnectionPool -> m a-runDBPool = runSqlPool--tryDBPool :: MonadLoggerIO m => ConnectionPool -> SqlPersistM a -> m (Maybe a)-tryDBPool pool action = do-    resE <- liftIO $ tryJust f $ runSqlPersistMPool action pool-    case resE of-        Right res -> return $ Just res-        Left err -> do-            $(logError) $ pack $ unwords [ "A database error occured:", err]-            return Nothing-  where-    f (SomeException e) = Just $ show e--runNode :: MonadIO m => NodeT m a -> Handler m a-runNode action = do-    nodeStateM <- asks handlerNodeState-    case nodeStateM of-        Just nodeState -> lift $ runNodeT action nodeState-        _ -> error "runNode: No node state available"--{- Server Handlers -}--getAccountsR :: ( MonadLoggerIO m-                , MonadBaseControl IO m-                , MonadBase IO m-                , MonadThrow m-                , MonadResource m-                )-             => ListRequest-             -> Handler m (Maybe Value)-getAccountsR lq@ListRequest{..} = do-    $(logInfo) $ format $ unlines-        [ "GetAccountsR"-        , "  Offset      : " ++ show listOffset-        , "  Limit       : " ++ show listLimit-        , "  Reversed    : " ++ show listReverse-        ]-    (accs, cnt) <- runDB $ accounts lq-    return $ Just $ toJSON $ ListResult (map (toJsonAccount Nothing) accs) cnt--postAccountsR-    :: (MonadResource m, MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m)-    => NewAccount -> Handler m (Maybe Value)-postAccountsR newAcc@NewAccount{..} = do-    $(logInfo) $ format $ unlines-        [ "PostAccountsR"-        , "  Account name: " ++ unpack newAccountName-        , "  Account type: " ++ show newAccountType-        ]-    (Entity _ newAcc', mnemonicM) <- runDB $ newAccount newAcc-    -- Update the bloom filter if the account is complete-    whenOnline $ when (isCompleteAccount newAcc') updateNodeFilter-    return $ Just $ toJSON $ toJsonAccount mnemonicM newAcc'--postAccountRenameR-    :: (MonadResource m, MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m)-    => AccountName -> AccountName -> Handler m (Maybe Value)-postAccountRenameR oldName newName = do-    $(logInfo) $ format $ unlines-        [ "PostAccountRenameR"-        , "  Account name: " ++ unpack oldName-        , "  New name    : " ++ unpack newName-        ]-    newAcc <- runDB $ do-        accE <- getAccount oldName-        renameAccount accE newName-    return $ Just $ toJSON $ toJsonAccount Nothing newAcc--getAccountR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-            => AccountName -> Handler m (Maybe Value)-getAccountR name = do-    $(logInfo) $ format $ unlines-        [ "GetAccountR"-        , "  Account name: " ++ unpack name-        ]-    Entity _ acc <- runDB $ getAccount name-    return $ Just $ toJSON $ toJsonAccount Nothing acc--postAccountKeysR-    :: (MonadResource m, MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m)-    => AccountName -> [XPubKey] -> Handler m (Maybe Value)-postAccountKeysR name keys = do-    $(logInfo) $ format $ unlines-        [ "PostAccountKeysR"-        , "  Account name: " ++ unpack name-        , "  Key count   : " ++ show (length keys)-        ]-    newAcc <- runDB $ do-        accE <- getAccount name-        addAccountKeys accE keys-    -- Update the bloom filter if the account is complete-    whenOnline $ when (isCompleteAccount newAcc) updateNodeFilter-    return $ Just $ toJSON $ toJsonAccount Nothing newAcc--postAccountGapR :: ( MonadLoggerIO m-                   , MonadBaseControl IO m-                   , MonadBase IO m-                   , MonadThrow m-                   , MonadResource m-                   )-                => AccountName -> SetAccountGap-                -> Handler m (Maybe Value)-postAccountGapR name (SetAccountGap gap) = do-    $(logInfo) $ format $ unlines-        [ "PostAccountGapR"-        , "  Account name: " ++ unpack name-        , "  New gap size: " ++ show gap-        ]-    -- Update the gap-    Entity _ newAcc <- runDB $ do-        accE <- getAccount name-        setAccountGap accE gap-    -- Update the bloom filter-    whenOnline updateNodeFilter-    return $ Just $ toJSON $ toJsonAccount Nothing newAcc--getAddressesR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-              => AccountName-              -> AddressType-              -> Word32-              -> Bool-              -> ListRequest-              -> Handler m (Maybe Value)-getAddressesR name addrType minConf offline listReq = do-    $(logInfo) $ format $ unlines-        [ "GetAddressesR"-        , "  Account name: " ++ unpack name-        , "  Address type: " ++ show addrType-        , "  Start index : " ++ show (listOffset listReq)-        , "  Reversed    : " ++ show (listReverse listReq)-        , "  MinConf     : " ++ show minConf-        , "  Offline     : " ++ show offline-        ]--    (res, bals, cnt) <- runDB $ do-        accE <- getAccount name-        (res, cnt) <- addressList accE addrType listReq-        case res of-            [] -> return (res, [], cnt)-            _ -> do-                let is = map walletAddrIndex res-                    (iMin, iMax) = (minimum is, maximum is)-                bals <- addressBalances accE iMin iMax addrType minConf offline-                return (res, bals, cnt)--    -- Join addresses and balances together-    let g (addr, bal) = toJsonAddr addr (Just bal)-        addrBals = map g $ M.elems $ joinAddrs res bals-    return $ Just $ toJSON $ ListResult addrBals cnt-  where-    joinAddrs addrs bals =-        let f addr = (walletAddrIndex addr, addr)-        in  M.intersectionWith (,) (M.fromList $ map f addrs) (M.fromList bals)--getAddressesUnusedR-    :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-    => AccountName -> AddressType -> ListRequest -> Handler m (Maybe Value)-getAddressesUnusedR name addrType lq@ListRequest{..} = do-    $(logInfo) $ format $ unlines-        [ "GetAddressesUnusedR"-        , "  Account name: " ++ unpack name-        , "  Address type: " ++ show addrType-        , "  Offset      : " ++ show listOffset-        , "  Limit       : " ++ show listLimit-        , "  Reversed    : " ++ show listReverse-        ]--    (addrs, cnt) <- runDB $ do-        accE <- getAccount name-        unusedAddresses accE addrType lq--    return $ Just $ toJSON $ ListResult (map (`toJsonAddr` Nothing) addrs) cnt--getAddressR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-            => AccountName -> KeyIndex -> AddressType-            -> Word32 -> Bool-            -> Handler m (Maybe Value)-getAddressR name i addrType minConf offline = do-    $(logInfo) $ format $ unlines-        [ "GetAddressR"-        , "  Account name: " ++ unpack name-        , "  Index       : " ++ show i-        , "  Address type: " ++ show addrType-        ]--    (addr, balM) <- runDB $ do-        accE <- getAccount name-        addrE <- getAddress accE addrType i-        bals <- addressBalances accE i i addrType minConf offline-        return $ case bals of-            ((_,bal):_) -> (entityVal addrE, Just bal)-            _           -> (entityVal addrE, Nothing)-    return $ Just $ toJSON $ toJsonAddr addr balM--getIndexR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-          => AccountName-          -> PubKeyC-          -> AddressType-          -> Handler m (Maybe Value)-getIndexR name key addrType = do-    $(logInfo) $ format $ unlines-        [ "getIndexR"-        , "  Account name: " ++ unpack name-        , "  Key         : " ++ show key-        , "  Address type: " ++ show addrType-        ]-    addrLst <- runDB $ do-        accE <- getAccount name-        lookupByPubKey accE key addrType-    let hello = map (`toJsonAddr` Nothing) addrLst-    liftIO $ putStrLn $ show hello-    liftIO $ print $ toJSON $ hello-    return $ Just $ toJSON $ hello--putAddressR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-            => AccountName-            -> KeyIndex-            -> AddressType-            -> AddressLabel-            -> Handler m (Maybe Value)-putAddressR name i addrType (AddressLabel label) = do-    $(logInfo) $ format $ unlines-        [ "PutAddressR"-        , "  Account name: " ++ unpack name-        , "  Index       : " ++ show i-        , "  Label       : " ++ unpack label-        ]--    newAddr <- runDB $ do-        accE <- getAccount name-        setAddrLabel accE i addrType label--    return $ Just $ toJSON $ toJsonAddr newAddr Nothing--postAddressesR :: ( MonadLoggerIO m-                  , MonadBaseControl IO m-                  , MonadThrow m-                  , MonadBase IO m-                  , MonadResource m-                  )-               => AccountName-               -> KeyIndex-               -> AddressType-               -> Handler m (Maybe Value)-postAddressesR name i addrType = do-    $(logInfo) $ format $ unlines-        [ "PostAddressesR"-        , "  Account name: " ++ unpack name-        , "  Index       : " ++ show i-        ]--    cnt <- runDB $ do-        accE <- getAccount name-        generateAddrs accE addrType i--    -- Update the bloom filter-    whenOnline updateNodeFilter--    return $ Just $ toJSON cnt--getTxs :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-       => AccountName-       -> ListRequest-       -> String-       -> (AccountId -> ListRequest -> SqlPersistT m ([WalletTx], Word32))-       -> Handler m (Maybe Value)-getTxs name lq@ListRequest{..} cmd f = do-    $(logInfo) $ format $ unlines-        [ cmd-        , "  Account name: " ++ unpack name-        , "  Offset      : " ++ show listOffset-        , "  Limit       : " ++ show listLimit-        , "  Reversed    : " ++ show listReverse-        ]--    (res, cnt, bb) <- runDB $ do-        Entity ai _ <- getAccount name-        bb <- walletBestBlock-        (res, cnt) <- f ai lq-        return (res, cnt, bb)--    return $ Just $ toJSON $ ListResult (map (g bb) res) cnt-  where-    g bb = toJsonTx name (Just bb)--getTxsR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-        => AccountName -> ListRequest -> Handler m (Maybe Value)-getTxsR name lq = getTxs name lq "GetTxsR" (txs Nothing)--getPendingR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-            => AccountName -> ListRequest -> Handler m (Maybe Value)-getPendingR name lq = getTxs name lq "GetPendingR" (txs (Just TxPending))--getDeadR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-         => AccountName -> ListRequest -> Handler m (Maybe Value)-getDeadR name lq = getTxs name lq "GetDeadR" (txs (Just TxDead))--getAddrTxsR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-            => AccountName -> KeyIndex -> AddressType -> ListRequest-            -> Handler m (Maybe Value)-getAddrTxsR name index addrType lq@ListRequest{..} = do-    $(logInfo) $ format $ unlines-        [ "GetAddrTxsR"-        , "  Account name : " ++ unpack name-        , "  Address index: " ++ show index-        , "  Address type : " ++ show addrType-        , "  Offset       : " ++ show listOffset-        , "  Limit        : " ++ show listLimit-        , "  Reversed     : " ++ show listReverse-        ]--    (res, cnt, bb) <- runDB $ do-        accE <- getAccount name-        addrE <- getAddress accE addrType index-        bb <- walletBestBlock-        (res, cnt) <- addrTxs accE addrE lq-        return (res, cnt, bb)--    return $ Just $ toJSON $ ListResult (map (f bb) res) cnt-  where-    f bb = toJsonTx name (Just bb)--postTxsR :: ( MonadLoggerIO m, MonadBaseControl IO m, MonadBase IO m-            , MonadThrow m, MonadResource m-            )-         => AccountName -> Maybe XPrvKey -> TxAction -> Handler m (Maybe Value)-postTxsR name masterM action = do-    (accE@(Entity ai _), bb) <- runDB $ do-        accE <- getAccount name-        bb <- walletBestBlock-        return (accE, bb)--    notif <- asks handlerNotifChan-    (txRes, newAddrs) <- case action of-        CreateTx rs fee minconf rcptFee sign -> do-            $(logInfo) $ format $ unlines-                [ "PostTxsR CreateTx"-                , "  Account name: " ++ unpack name-                , "  Recipients  : " ++ show (map (first addrToBase58) rs)-                , "  Fee         : " ++ show fee-                , "  Minconf     : " ++ show minconf-                , "  Rcpt. Fee   : " ++ show rcptFee-                , "  Sign        : " ++ show sign-                ]-            runDB $ createWalletTx-                accE (Just notif) masterM rs fee minconf rcptFee sign-        ImportTx tx -> do-            $(logInfo) $ format $ unlines-                [ "PostTxsR ImportTx"-                , "  Account name: " ++ unpack name-                , "  TxId        : " ++ cs (txHashToHex (txHash tx))-                ]-            runDB $ do-                (res, newAddrs) <- importTx tx (Just notif) ai-                case filter ((== ai) . walletTxAccount) res of-                    (txRes:_) -> return (txRes, newAddrs)-                    _ -> throwM $ WalletException-                        "Could not import the transaction"-        SignTx txid -> do-            $(logInfo) $ format $ unlines-                [ "PostTxsR SignTx"-                , "  Account name: " ++ unpack name-                , "  TxId        : " ++ cs (txHashToHex txid)-                ]-            runDB $ do-                (res, newAddrs) <- signAccountTx accE (Just notif) masterM txid-                case filter ((== ai) . walletTxAccount) res of-                    (txRes:_) -> return (txRes, newAddrs)-                    _ -> throwM $ WalletException-                        "Could not import the transaction"-    whenOnline $ do-        -- Update the bloom filter-        unless (null newAddrs) updateNodeFilter-        -- If the transaction is pending, broadcast it to the network-        when (walletTxConfidence txRes == TxPending) $-            runNode $ broadcastTxs [walletTxHash txRes]-    return $ Just $ toJSON $ toJsonTx name (Just bb) txRes--getTxR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-       => AccountName -> TxHash -> Handler m (Maybe Value)-getTxR name txid = do-    $(logInfo) $ format $ unlines-        [ "GetTxR"-        , "  Account name: " ++ unpack name-        , "  TxId        : " ++ cs (txHashToHex txid)-        ]-    (res, bb) <- runDB $ do-        Entity ai _ <- getAccount name-        bb <- walletBestBlock-        res <- getAccountTx ai txid-        return (res, bb)-    return $ Just $ toJSON $ toJsonTx name (Just bb) res--deleteTxIdR :: (MonadLoggerIO m, MonadThrow m, MonadBaseControl IO m)-            => TxHash -> Handler m (Maybe Value)-deleteTxIdR txid = do-    $(logInfo) $ format $ unlines-        [ "DeleteTxR"-        , "  TxId: " ++ cs (txHashToHex txid)-        ]-    runDB $ deleteTx txid-    return Nothing--getBalanceR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-            => AccountName -> Word32 -> Bool-            -> Handler m (Maybe Value)-getBalanceR name minconf offline = do-    $(logInfo) $ format $ unlines-        [ "GetBalanceR"-        , "  Account name: " ++ unpack name-        , "  Minconf     : " ++ show minconf-        , "  Offline     : " ++ show offline-        ]-    bal <- runDB $ do-        Entity ai _ <- getAccount name-        accountBalance ai minconf offline-    return $ Just $ toJSON bal--getOfflineTxR :: ( MonadLoggerIO m, MonadBaseControl IO m-                 , MonadBase IO m, MonadThrow m, MonadResource m-                 )-              => AccountName -> TxHash -> Handler m (Maybe Value)-getOfflineTxR accountName txid = do-    $(logInfo) $ format $ unlines-        [ "GetOfflineTxR"-        , "  Account name: " ++ unpack accountName-        , "  TxId        : " ++ cs (txHashToHex txid)-        ]-    (dat, _) <- runDB $ do-        Entity ai _ <- getAccount accountName-        getOfflineTxData ai txid-    return $ Just $ toJSON dat--postOfflineTxR :: ( MonadLoggerIO m, MonadBaseControl IO m-                  , MonadBase IO m, MonadThrow m, MonadResource m-                  )-               => AccountName-               -> Maybe XPrvKey-               -> Tx-               -> [CoinSignData]-               -> Handler m (Maybe Value)-postOfflineTxR accountName masterM tx signData = do-    $(logInfo) $ format $ unlines-        [ "PostTxsR SignOfflineTx"-        , "  Account name: " ++ unpack accountName-        , "  TxId        : " ++ cs (txHashToHex (txHash tx))-        ]-    Entity _ acc <- runDB $ getAccount accountName-    let signedTx = signOfflineTx acc masterM tx signData-        complete = verifyStdTx signedTx $ map toDat signData-        toDat CoinSignData{..} = (coinSignScriptOutput, coinSignOutPoint)-    return $ Just $ toJSON $ TxCompleteRes signedTx complete--postNodeR :: (MonadLoggerIO m, MonadBaseControl IO m, MonadThrow m)-          => NodeAction -> Handler m (Maybe Value)-postNodeR action = case action of-    NodeActionRescan tM -> do-        t <- case tM of-            Just t  -> return $ adjustFCTime t-            Nothing -> do-                timeM <- runDB firstAddrTime-                maybe err (return . adjustFCTime) timeM-        $(logInfo) $ format $ unlines-            [ "NodeR Rescan"-            , "  Timestamp: " ++ show t-            ]-        whenOnline $ do-            runDB resetRescan-            runNode $ atomicallyNodeT $ rescanTs t-        return $ Just $ toJSON $ RescanRes t-    NodeActionStatus -> do-        status <- runNode $ atomicallyNodeT nodeStatus-        return $ Just $ toJSON status-  where-    err = throwM $ WalletException-        "No keys have been generated in the wallet"--getSyncR :: (MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m)-         => AccountName-         -> Either BlockHeight BlockHash-         -> ListRequest-         -> Handler m (Maybe Value)-getSyncR acc blockE lq@ListRequest{..} = runDB $ do-    $(logInfo) $ format $ unlines-        [ "GetSyncR"-        , "  Account name: " ++ cs acc-        , "  Block       : " ++ showBlock-        , "  Offset      : " ++ show listOffset-        , "  Limit       : " ++ show listLimit-        , "  Reversed    : " ++ show listReverse-        ]-    ListResult nodes cnt <- mainChain blockE lq-    case nodes of-        [] -> return $ Just $ toJSON $ ListResult ([] :: [()]) cnt-        b:_ -> do-            Entity ai _ <- getAccount acc-            ts <- accTxsFromBlock ai (nodeBlockHeight b)-                (fromIntegral $ length nodes)-            let bts = blockTxs nodes ts-            return $ Just $ toJSON $ ListResult (map f bts) cnt-  where-    f (block, txs') = JsonSyncBlock-        { jsonSyncBlockHash   = nodeHash          block-        , jsonSyncBlockHeight = nodeBlockHeight   block-        , jsonSyncBlockPrev   = nodePrev          block-        , jsonSyncBlockTxs    = map (toJsonTx acc Nothing) txs'-        }-    showBlock = case blockE of-        Left  e -> show e-        Right b -> cs $ blockHashToHex b--getBlockInfoR :: (MonadThrow m, MonadLoggerIO m, MonadBaseControl IO m)-              => [BlockHash]-              -> Handler m (Maybe Value)-getBlockInfoR headerLst = do-    lstMaybeBlk <- forM headerLst (runNode . runSqlNodeT . getBlockByHash)-    return $ toJSON <$> Just (handleRes lstMaybeBlk)-  where-    handleRes :: [Maybe NodeBlock] -> [BlockInfo]-    handleRes = map fromNodeBlock . catMaybes--{- Helpers -}--whenOnline :: Monad m => Handler m () -> Handler m ()-whenOnline handler = do-    mode <- configMode `liftM` asks handlerConfig-    when (mode == SPVOnline) handler--updateNodeFilter-    :: (MonadBaseControl IO m, MonadLoggerIO m, MonadThrow m)-    => Handler m ()-updateNodeFilter = do-    $(logInfo) $ format "Sending a new bloom filter"-    (bloom, elems, _) <- runDB getBloomFilter-    runNode $ atomicallyNodeT $ sendBloomFilter bloom elems--adjustFCTime :: Timestamp -> Timestamp-adjustFCTime ts = fromInteger $ max 0 $ toInteger ts - 86400 * 7--format :: String -> Text-format str = pack $ "[ZeroMQ] " ++ str-
− Network/Haskoin/Wallet/Settings.hs
@@ -1,196 +0,0 @@-module Network.Haskoin.Wallet.Settings-( SPVMode(..)-, OutputFormat(..)-, Config(..)-) where--import Control.Monad (mzero)-import Control.Exception (throw)-import Control.Monad.Logger (LogLevel(..))--import Data.Default (Default, def)-import Data.FileEmbed (embedFile)-import Data.Yaml (decodeEither')-import Data.Word (Word32, Word64)-import Data.HashMap.Strict (HashMap, unionWith)-import Data.String.Conversions (cs)-import Data.ByteString (ByteString)-import Data.Text (Text)-import Data.Text.Encoding (encodeUtf8)-import Data.Aeson-    ( Value(..), FromJSON, ToJSON-    , parseJSON, toJSON, withObject-    , (.:)-    )--import Network.Haskoin.Crypto-import Network.Haskoin.Wallet.Database-import Network.Haskoin.Wallet.Types--import Data.Restricted (Restricted, Div5)-import System.ZMQ4 (toRestricted)--data SPVMode = SPVOnline | SPVOffline-    deriving (Eq, Show, Read)--newtype LogLevelJSON = LogLevelJSON LogLevel-    deriving (Eq, Show, Read)--data OutputFormat-    = OutputNormal-    | OutputJSON-    | OutputYAML--data Config = Config-    { configCount         :: !Word32-    -- ^ Output size of commands-    , configMinConf       :: !Word32-    -- ^ Minimum number of confirmations-    , configSignTx        :: !Bool-    -- ^ Sign transactions-    , configFee           :: !Word64-    -- ^ Fee to pay per 1000 bytes when creating new transactions-    , configRcptFee       :: !Bool-    -- ^ Recipient pays fee (dangerous, no config file setting)-    , configAddrType      :: !AddressType-    -- ^ Return internal instead of external addresses-    , configOffline       :: !Bool-    -- ^ Display the balance including offline transactions-    , configReversePaging :: !Bool-    -- ^ Use reverse paging for displaying addresses and transactions-    , configPath          :: !(Maybe HardPath)-    -- ^ Derivation path when creating account-    , configFormat        :: !OutputFormat-    -- ^ How to format the command-line results-    , configConnect       :: !String-    -- ^ ZeroMQ socket to connect to (location of the server)-    , configConnectNotif  :: !String-    -- ^ ZeroMQ socket to connect for notifications-    , configDetach        :: !Bool-    -- ^ Detach server when launched from command-line-    , configFile          :: !FilePath-    -- ^ Configuration file-    , configTestnet       :: !Bool-    -- ^ Use Testnet3 network-    , configDir           :: !FilePath-    -- ^ Working directory-    , configBind          :: !String-    -- ^ Bind address for the ZeroMQ socket-    , configBindNotif     :: !String-    -- ^ Bind address for ZeroMQ notifications-    , configBTCNodes      :: !(HashMap Text [BTCNode])-    -- ^ Trusted Bitcoin full nodes to connect to-    , configMode          :: !SPVMode-    -- ^ Operation mode of the SPV node.-    , configBloomFP       :: !Double-    -- ^ False positive rate for the bloom filter.-    , configDatabase      :: !(HashMap Text DatabaseConfType)-    -- ^ Database configuration-    , configLogFile       :: !FilePath-    -- ^ Log file-    , configPidFile       :: !FilePath-    -- ^ PID File-    , configLogLevel      :: !LogLevel-    -- ^ Log level-    , configVerbose       :: !Bool-    -- ^ Verbose-    , configServerKey     :: !(Maybe (Restricted Div5 ByteString))-    -- ^ Server key for authentication and encryption (server config)-    , configServerKeyPub  :: !(Maybe (Restricted Div5 ByteString))-    -- ^ Server public key for authentication and encryption (client config)-    , configClientKey     :: !(Maybe (Restricted Div5 ByteString))-    -- ^ Client key for authentication and encryption (client config)-    , configClientKeyPub  :: !(Maybe (Restricted Div5 ByteString))-    -- ^ Client public key for authentication and encryption-    -- (client + server config)-    }--configBS :: ByteString-configBS = $(embedFile "config/config.yml")--instance ToJSON OutputFormat where-    toJSON OutputNormal = String "normal"-    toJSON OutputJSON   = String "json"-    toJSON OutputYAML   = String "yaml"--instance FromJSON OutputFormat where-    parseJSON (String "normal") = return OutputNormal-    parseJSON (String "json")   = return OutputJSON-    parseJSON (String "yaml")   = return OutputYAML-    parseJSON _ = mzero--instance ToJSON SPVMode where-    toJSON SPVOnline  = String "online"-    toJSON SPVOffline = String "offline"--instance FromJSON SPVMode where-    parseJSON (String "online")  = return SPVOnline-    parseJSON (String "offline") = return SPVOffline-    parseJSON _ = mzero--instance ToJSON LogLevelJSON where-    toJSON (LogLevelJSON LevelDebug)     = String "debug"-    toJSON (LogLevelJSON LevelInfo)      = String "info"-    toJSON (LogLevelJSON LevelWarn)      = String "warn"-    toJSON (LogLevelJSON LevelError)     = String "error"-    toJSON (LogLevelJSON (LevelOther t)) = String t--instance FromJSON LogLevelJSON where-    parseJSON (String "debug") = return $ LogLevelJSON LevelDebug-    parseJSON (String "info")  = return $ LogLevelJSON LevelInfo-    parseJSON (String "warn")  = return $ LogLevelJSON LevelWarn-    parseJSON (String "error") = return $ LogLevelJSON LevelError-    parseJSON (String x)       = return $ LogLevelJSON (LevelOther x)-    parseJSON _ = mzero--instance Default Config where-    def = either throw id $ decodeEither' "{}"--instance FromJSON Config where-    parseJSON = withObject "config" $ \o' -> do-        let defValue         = either throw id $ decodeEither' configBS-            (Object o)       = mergeValues defValue (Object o')-            configPath       = Nothing-        configFile                  <- o .: "config-file"-        configRcptFee               <- o .: "recipient-fee"-        configCount                 <- o .: "output-size"-        configMinConf               <- o .: "minimum-confirmations"-        configSignTx                <- o .: "sign-transactions"-        configFee                   <- o .: "transaction-fee"-        configAddrType              <- o .: "address-type"-        configOffline               <- o .: "offline"-        configReversePaging         <- o .: "reverse-paging"-        configFormat                <- o .: "display-format"-        configConnect               <- o .: "connect-uri"-        configConnectNotif          <- o .: "connect-uri-notif"-        configDetach                <- o .: "detach-server"-        configTestnet               <- o .: "use-testnet"-        configDir                   <- o .: "work-dir"-        configBind                  <- o .: "bind-socket"-        configBindNotif             <- o .: "bind-socket-notif"-        configBTCNodes              <- o .: "bitcoin-full-nodes"-        configMode                  <- o .: "server-mode"-        configBloomFP               <- o .: "bloom-false-positive"-        configLogFile               <- o .: "log-file"-        configPidFile               <- o .: "pid-file"-        LogLevelJSON configLogLevel <- o .: "log-level"-        configVerbose               <- o .: "verbose"-        configDatabase              <- o .: "database"-        configServerKey             <- getKey o "server-key"-        configServerKeyPub          <- getKey o "server-key-public"-        configClientKey             <- getKey o "client-key"-        configClientKeyPub          <- getKey o "client-key-public"-        return Config {..}-      where-        getKey o i = o .: i >>= \kM ->-            case kM of-              Nothing -> return Nothing-              Just k ->-                  case toRestricted $ encodeUtf8 k of-                    Just k' -> return $ Just k'-                    Nothing -> fail $ "Invalid " ++ cs k---mergeValues :: Value -> Value -> Value-mergeValues (Object d) (Object c) = Object (unionWith mergeValues d c)-mergeValues _ c = c
− Network/Haskoin/Wallet/Transaction.hs
@@ -1,1358 +0,0 @@-{-# LANGUAGE FlexibleContexts      #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RecordWildCards       #-}-module Network.Haskoin.Wallet.Transaction-(--- *Database transactions-  txs-, addrTxs-, accTxsFromBlock-, getTx-, getAccountTx-, importTx-, importNetTx-, signAccountTx-, createWalletTx-, signOfflineTx-, getOfflineTxData-, killTxs-, reviveTx-, getPendingTxs-, deleteTx---- *Database blocks-, importMerkles-, walletBestBlock---- *Database coins and balances-, spendableCoins-, accountBalance-, addressBalances---- *Rescan-, resetRescan---- *Helpers-, InCoinData(..)-) where--import           Control.Arrow                   (second)-import           Control.Concurrent.STM          (atomically)-import           Control.Concurrent.STM.TBMChan  (TBMChan, writeTBMChan)-import           Control.Exception               (throw, throwIO)-import           Control.Monad                   (forM, forM_, unless, when)-import           Control.Monad.Base              (MonadBase)-import           Control.Monad.Catch             (MonadThrow, throwM)-import           Control.Monad.Trans             (MonadIO, liftIO)-import           Control.Monad.Trans.Resource    (MonadResource)-import           Data.Either                     (rights)-import           Data.List                       (find, nub, nubBy, (\\))-import qualified Data.Map.Strict                 as M (Map, fromListWith, map,-                                                       toList, unionWith)-import           Data.Maybe                      (fromMaybe, isJust, isNothing,-                                                  listToMaybe, mapMaybe)-import           Data.String.Conversions         (cs)-import           Data.Time                       (UTCTime, getCurrentTime)-import           Data.Word                       (Word32, Word64)-import           Database.Esqueleto              (Entity (..), InnerJoin (..),-                                                  LeftOuterJoin (..), OrderBy,-                                                  SqlExpr, SqlPersistT,-                                                  SqlQuery, Value (..), asc,-                                                  case_, coalesceDefault, count,-                                                  countDistinct, countRows,-                                                  delete, desc, distinct, else_,-                                                  from, get, getBy, groupBy,-                                                  in_, just, limit, not_, on,-                                                  orderBy, replace, select, set,-                                                  sub_select, sum_, then_,-                                                  unValue, update, val, valList,-                                                  when_, where_, (!=.), (&&.),-                                                  (-.), (<.), (<=.), (=.),-                                                  (==.), (>=.), (?.), (^.),-                                                  (||.))-import qualified Database.Esqueleto              as E (isNothing)-import qualified Database.Persist                as P (Filter, deleteWhere,-                                                       insertBy, selectFirst)-import           Network.Haskoin.Block-import           Network.Haskoin.Constants-import           Network.Haskoin.Crypto-import           Network.Haskoin.Node.HeaderTree-import           Network.Haskoin.Node.STM-import           Network.Haskoin.Script-import           Network.Haskoin.Transaction-import           Network.Haskoin.Util-import           Network.Haskoin.Wallet.Accounts-import           Network.Haskoin.Wallet.Model-import           Network.Haskoin.Wallet.Types---- Input coin type with transaction and address information-data InCoinData = InCoinData-    { inCoinDataCoin :: !(Entity WalletCoin)-    , inCoinDataTx   :: !WalletTx-    , inCoinDataAddr :: !WalletAddr-    }--instance Coin InCoinData where-    coinValue (InCoinData (Entity _ c) _ _) = walletCoinValue c---- Output coin type with address information-data OutCoinData = OutCoinData-    { outCoinDataAddr   :: !(Entity WalletAddr)-    , outCoinDataPos    :: !KeyIndex-    , outCoinDataValue  :: !Word64-    , outCoinDataScript :: !ScriptOutput-    }--{- List transactions -}---- | Get transactions.-txs :: MonadIO m-    => Maybe TxConfidence-    -> AccountId        -- ^ Account ID-    -> ListRequest      -- ^ List request-    -> SqlPersistT m ([WalletTx], Word32)-    -- ^ List result-txs conf ai ListRequest{..} = do-    [cnt] <- fmap (map unValue) $ select $ from $ \t -> do-        cond t-        return countRows-    when (listOffset > 0 && listOffset >= cnt) $ throw $ WalletException-        "Offset beyond end of data set"-    res <- fmap (map entityVal) $ select $ from $ \t -> do-        cond t-        orderBy [ order (t ^. WalletTxId) ]-        limitOffset listLimit listOffset-        return t-    return (res, cnt)-  where-    account t = t ^. WalletTxAccount ==. val ai-    cond t = where_ $ case conf of-        Just  n -> account t &&. t ^. WalletTxConfidence ==. val n-        Nothing -> account t-    order = if listReverse then asc else desc--{- List transactions for an account and address -}--addrTxs :: MonadIO m-        => Entity Account        -- ^ Account entity-        -> Entity WalletAddr     -- ^ Address entity-        -> ListRequest           -- ^ List request-        -> SqlPersistT m ([WalletTx], Word32)-addrTxs (Entity ai _) (Entity addrI WalletAddr{..}) ListRequest{..} = do-    let joinSpentCoin c2 s =-                c2 ?. WalletCoinAccount ==. s ?. SpentCoinAccount-            &&. c2 ?. WalletCoinHash    ==. s ?. SpentCoinHash-            &&. c2 ?. WalletCoinPos     ==. s ?. SpentCoinPos-            &&. c2 ?. WalletCoinAddr    ==. just (val addrI)-        joinSpent s t =-            s ?. SpentCoinSpendingTx ==. just (t ^. WalletTxId)-        joinCoin c t =-                c ?. WalletCoinTx   ==. just (t ^. WalletTxId)-            &&. c ?. WalletCoinAddr ==. just (val addrI)-        joinAll t c c2 s = do-            on $ joinSpentCoin c2 s-            on $ joinSpent s t-            on $ joinCoin c t-        tables f = from $ \(t `LeftOuterJoin` c `LeftOuterJoin`-                            s `LeftOuterJoin` c2) -> f t c s c2-        query t c s c2 = do-            joinAll t c c2 s-            where_ (   t ^. WalletTxAccount ==. val ai-                    &&. (   not_ (E.isNothing (c  ?. WalletCoinId))-                        ||. not_ (E.isNothing (c2 ?. WalletCoinId))-                        )-                    )-            let order = if listReverse then asc else desc-            orderBy [ order (t ^. WalletTxId) ]---    cntRes <- select $ tables $ \t c s c2 -> do-        query t c s c2-        return $ countDistinct $ t ^. WalletTxId--    let cnt = maybe 0 unValue $ listToMaybe cntRes--    when (listOffset > 0 && listOffset >= cnt) $ throw $ WalletException-        "Offset beyond end of data set"--    res <- select $ distinct $ tables $ \t c s c2 -> do-        query t c s c2-        limitOffset listLimit listOffset-        return t--    return (map (updBals . entityVal) res, cnt)--  where-    agg = sum . mapMaybe addressInfoValue .-        filter ((== walletAddrAddress) . addressInfoAddress)-    updBals t =-      let-        input  = agg $ walletTxInputs  t-        output = agg $ walletTxOutputs t-        change = agg $ walletTxChange  t-      in-        t { walletTxInValue  = output + change-          , walletTxOutValue = input-          }--accTxsFromBlock :: (MonadIO m, MonadThrow m)-                => AccountId-                -> BlockHeight-                -> Word32 -- ^ Block count (0 for all)-                -> SqlPersistT m [WalletTx]-accTxsFromBlock ai bh n =-    fmap (map entityVal) $ select $ from $ \t -> do-        query t-        orderBy [ asc (t ^. WalletTxConfirmedHeight), asc (t ^. WalletTxId) ]-        return t-  where-    query t-        | n == 0 = where_ $-            t ^. WalletTxAccount ==. val ai &&.-            t ^. WalletTxConfirmedHeight >=.  just (val bh)-        | otherwise = where_ $-            t ^. WalletTxAccount ==. val ai &&.-            t ^. WalletTxConfirmedHeight >=.  just (val bh) &&.-            t ^. WalletTxConfirmedHeight <. just (val $ bh + n)---- Helper function to get a transaction from the wallet database. The function--- will look across all accounts and return the first available transaction. If--- the transaction does not exist, this function will throw a wallet exception.-getTx :: MonadIO m => TxHash -> SqlPersistT m (Maybe Tx)-getTx txid =-    fmap (listToMaybe . map unValue) $ select $ from $ \t -> do-        where_ $ t ^. WalletTxHash ==. val txid-        limit 1-        return $ t ^. WalletTxTx--getAccountTx :: MonadIO m-             => AccountId -> TxHash -> SqlPersistT m WalletTx-getAccountTx ai txid = do-    res <- select $ from $ \t -> do-        where_ (   t ^. WalletTxAccount ==. val ai-               &&. t ^. WalletTxHash    ==. val txid-               )-        return t-    case res of-        (Entity _ tx:_) -> return tx-        _ -> liftIO . throwIO $ WalletException $ unwords-            [ "Transaction does not exist:", cs $ txHashToHex txid ]---- Helper function to get all the pending transactions from the database. It is--- used to re-broadcast pending transactions in the wallet that have not been--- included into blocks yet.-getPendingTxs :: MonadIO m => Int -> SqlPersistT m [TxHash]-getPendingTxs i =-    fmap (map unValue) $ select $ from $ \t -> do-        where_ $ t ^. WalletTxConfidence ==. val TxPending-        when (i > 0) $ limit $ fromIntegral i-        return $ t ^. WalletTxHash--{- Transaction Import -}---- | Import a transaction into the wallet from an unknown source. If the--- transaction is standard, valid, all inputs are known and all inputs can be--- spent, then the transaction will be imported as a network transaction.--- Otherwise, the transaction will be imported into the local account as an--- offline transaction.-importTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-         => Tx               -- ^ Transaction to import-         -> Maybe (TBMChan Notif)-         -> AccountId        -- ^ Account ID-         -> SqlPersistT m ([WalletTx], [WalletAddr])-         -- ^ New transactions and addresses created-importTx tx notifChanM ai =-    importTx' tx notifChanM ai =<< getInCoins tx (Just ai)--importTx' :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-          => Tx                   -- ^ Transaction to import-          -> Maybe (TBMChan Notif)-          -> AccountId            -- ^ Account ID-          -> [InCoinData]         -- ^ Input coins-          -> SqlPersistT m ([WalletTx], [WalletAddr])-             -- ^ Transaction hash (after possible merges)-importTx' origTx notifChanM ai origInCoins = do-    -- Merge the transaction with any previously existing transactions-    mergeResM <- mergeNoSigHashTxs ai origTx origInCoins-    let tx       = fromMaybe origTx mergeResM-        origTxid = txHash origTx-        txid     = txHash tx--    -- If the transaction was merged into a new transaction,-    -- update the old hashes to the new ones. This allows us to-    -- keep the spending information of our coins. It is thus possible-    -- to spend partially signed multisignature transactions (as offline-    -- transactions) even before all signatures have arrived.-    inCoins <- if origTxid == txid then return origInCoins else do-        -- Update transactions-        update $ \t -> do-            set t [ WalletTxHash =. val txid-                  , WalletTxTx   =. val tx-                  ]-            where_ (   t ^. WalletTxAccount ==. val ai-                   &&. t ^. WalletTxHash    ==. val origTxid-                   )-        -- Update coins-        update $ \t -> do-            set t [ WalletCoinHash =. val txid ]-            where_ (   t ^. WalletCoinAccount ==. val ai-                   &&. t ^. WalletCoinHash    ==. val origTxid-                   )-        let f (InCoinData c t x) = if walletTxHash t == origTxid-                then InCoinData c-                     t{ walletTxHash = txid, walletTxTx = tx } x-                else InCoinData c t x-        return $ map f origInCoins--    spendingTxs <- getSpendingTxs tx (Just ai)--    let validTx = verifyStdTx tx $ map toVerDat inCoins-        validIn = length inCoins == length (txIn tx)-               && canSpendCoins inCoins spendingTxs False-    if validIn && validTx-        then importNetTx tx notifChanM-        else importOfflineTx tx notifChanM ai inCoins spendingTxs-  where-    toVerDat (InCoinData (Entity _ c) t _) =-        (walletCoinScript c, OutPoint (walletTxHash t) (walletCoinPos c))---- Offline transactions are usually multisignature transactions requiring--- additional signatures. This function will merge the signatures of--- the same offline transactions together into one single transaction.-mergeNoSigHashTxs :: MonadIO m-                  => AccountId-                  -> Tx-                  -> [InCoinData]-                  -> SqlPersistT m (Maybe Tx)-mergeNoSigHashTxs ai tx inCoins = do-    prevM <- getBy $ UniqueAccNoSig ai $ nosigTxHash tx-    return $ case prevM of-        Just (Entity _ prev) -> case walletTxConfidence prev of-            TxOffline -> eitherToMaybe $-                mergeTxs [tx, walletTxTx prev] outPoints-            _ -> Nothing-        -- Nothing to merge. Return the original transaction.-        _ -> Nothing-  where-    buildOutpoint c t = OutPoint (walletTxHash t) (walletCoinPos c)-    f (InCoinData (Entity _ c) t _) = (walletCoinScript c, buildOutpoint c t)-    outPoints = map f inCoins---- | Import an offline transaction into a specific account. Offline transactions--- are imported either manually or from the wallet when building a partially--- signed multisignature transaction. Offline transactions are only imported--- into one specific account. They will not affect the input or output coins--- of other accounts, including read-only accounts that may watch the same--- addresses as this account.------ We allow transactions to be imported manually by this function (unlike--- `importNetTx` which imports only transactions coming from the network). This--- means that it is possible to import completely crafted and invalid--- transactions into the wallet. It is thus important to limit the scope of--- those transactions to only the specific account in which it was imported.------ This function will not broadcast these transactions to the network as we--- have no idea if they are valid or not. Transactions are broadcast from the--- transaction creation function and only if the transaction is complete.-importOfflineTx-    :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-    => Tx-    -> Maybe (TBMChan Notif)-    -> AccountId-    -> [InCoinData]-    -> [Entity WalletTx]-    -> SqlPersistT m ([WalletTx], [WalletAddr])-importOfflineTx tx notifChanM ai inCoins spendingTxs = do-    -- Get all the new coins to be created by this transaction-    outCoins <- getNewCoins tx $ Just ai-    -- Only continue if the transaction is relevant to the account-    when (null inCoins && null outCoins) err-    -- Find the details of an existing transaction if it exists.-    prevM <- fmap (fmap entityVal) $ getBy $ UniqueAccTx ai txid-    -- Check if we can import the transaction-    unless (canImport $ walletTxConfidence <$> prevM) err-    -- Kill transactions that are spending our coins-    killTxIds notifChanM $ map entityKey spendingTxs-    -- Create all the transaction records for this account.-    -- This will spend the input coins and create the output coins-    txsRes <- buildAccTxs notifChanM tx TxOffline inCoins outCoins-    -- use the addresses (refill the gap addresses)-    newAddrs <- forM (nubBy sameKey $ map outCoinDataAddr outCoins) $-        useAddress . entityVal-    return (txsRes, concat newAddrs)-  where-    txid = txHash tx-    canImport prevConfM =-        -- We can only re-import offline txs through this function.-        (isNothing prevConfM || prevConfM == Just TxOffline) &&-        -- Check that all coins can be spent. We allow offline-        -- coins to be spent by this function unlike importNetTx.-        canSpendCoins inCoins spendingTxs True-    sameKey e1 e2 = entityKey e1 == entityKey e2-    err  = liftIO . throwIO $ WalletException-        "Could not import offline transaction"---- | Import a transaction from the network into the wallet. This function--- assumes transactions are imported in-order (parents first). It also assumes--- that the confirmations always arrive after the transaction imports. This--- function is idempotent.------ When re-importing an existing transaction, this function will recompute--- the inputs, outputs and transaction details for each account. A non-dead--- transaction could be set to dead due to new inputs being double spent.--- However, we do not allow dead transactions to be revived by reimporting them.--- Transactions can only be revived if they make it into the main chain.------ This function returns the network confidence of the imported transaction.-importNetTx-    :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-    => Tx -- Network transaction to import-    -> Maybe (TBMChan Notif)-    -> SqlPersistT m ([WalletTx], [WalletAddr])-    -- ^ Returns the new transactions and addresses created-importNetTx tx notifChanM = do-    -- Find all the coins spent by this transaction-    inCoins <- getInCoins tx Nothing-    -- Get all the new coins to be created by this transaction-    outCoins <- getNewCoins tx Nothing-    -- Only continue if the transaction is relevant to the wallet-    if null inCoins && null outCoins then return ([],[]) else do-        -- Update incomplete offline transactions when the completed-        -- transaction comes in from the network.-        updateNosigHash tx (nosigTxHash tx) txid-        -- Get the transaction spending our coins-        spendingTxs <- getSpendingTxs tx Nothing-        -- Compute the confidence-        let confidence | canSpendCoins inCoins spendingTxs False = TxPending-                       | otherwise = TxDead-        -- Kill transactions that are spending our coins if we are not dead-        when (confidence /= TxDead) $-            killTxIds notifChanM $ map entityKey spendingTxs-        -- Create all the transaction records for this account.-        -- This will spend the input coins and create the output coins-        txRes <- buildAccTxs notifChanM tx confidence inCoins outCoins-        -- Use up the addresses of our new coins (replenish gap addresses)-        newAddrs <- forM (nubBy sameKey $ map outCoinDataAddr outCoins) $-            useAddress . entityVal-        forM_ notifChanM $ \notifChan -> forM_ txRes $-            \tx' -> do-                let ai = walletTxAccount tx'-                Account{..} <--                    fromMaybe (error "Velociraptors ate you") <$> get ai-                liftIO $ atomically $ writeTBMChan notifChan $-                    NotifTx $ toJsonTx accountName Nothing tx'-        return (txRes, concat newAddrs)-  where-    sameKey e1 e2 = entityKey e1 == entityKey e2-    txid = txHash tx--updateNosigHash :: MonadIO m => Tx -> TxHash -> TxHash -> SqlPersistT m ()-updateNosigHash tx nosig txid = do-    res <- select $ from $ \t -> do-        where_ (   t ^. WalletTxNosigHash ==. val nosig-               &&. t ^. WalletTxHash      !=. val txid-               )-        return $ t ^. WalletTxHash-    let toUpdate = map unValue res-    unless (null toUpdate) $ do-        splitUpdate toUpdate $ \hs t -> do-            set t [ WalletTxHash =. val txid-                  , WalletTxTx   =. val tx-                  ]-            where_ $ t ^. WalletTxHash `in_` valList hs-        splitUpdate toUpdate $ \hs c -> do-            set c [ WalletCoinHash =. val txid ]-            where_ $ c ^. WalletCoinHash `in_` valList hs---- Check if the given coins can be spent.-canSpendCoins :: [InCoinData]-              -> [Entity WalletTx]-              -> Bool -- True for offline transactions-              -> Bool-canSpendCoins inCoins spendingTxs offline =-    all validCoin inCoins &&-    all validSpend spendingTxs-  where-    -- We can only spend pending and building coins-    validCoin (InCoinData _ t _)-        | offline   = walletTxConfidence t /= TxDead-        | otherwise = walletTxConfidence t `elem` [TxPending, TxBuilding]-    -- All transactions spending the same coins as us should be offline-    validSpend = (== TxOffline) . walletTxConfidence . entityVal---- Get the coins in the wallet related to the inputs of a transaction. You--- can optionally provide an account to limit the returned coins to that--- account only.-getInCoins :: MonadIO m-           => Tx-           -> Maybe AccountId-           -> SqlPersistT m [InCoinData]-getInCoins tx aiM = do-    res <- splitSelect ops $ \os -> from $ \(c `InnerJoin` t `InnerJoin` x) -> do-        on $ x ^. WalletAddrId  ==. c ^. WalletCoinAddr-        on $ t ^. WalletTxId ==. c ^. WalletCoinTx-        where_ $ case aiM of-            Just ai ->-                c ^. WalletCoinAccount ==. val ai &&. limitOutPoints c os-            _ -> limitOutPoints c os-        return (c, t, x)-    return $ map (\(c, t, x) -> InCoinData c (entityVal t) (entityVal x)) res-  where-    ops = map prevOutput $ txIn tx-    limitOutPoints c os = join2 $ map (f c) os-    f c (OutPoint h i) =-        c ^. WalletCoinHash ==. val h &&.-        c ^. WalletCoinPos  ==. val i---- Find all the transactions that are spending the same coins as the given--- transaction. You can optionally provide an account to limit the returned--- transactions to that account only.-getSpendingTxs :: MonadIO m-               => Tx-               -> Maybe AccountId-               -> SqlPersistT m [Entity WalletTx]-getSpendingTxs tx aiM-    | null txInputs = return []-    | otherwise =-        splitSelect txInputs $ \ins -> from $ \(s `InnerJoin` t) -> do-            on $ s ^. SpentCoinSpendingTx ==. t ^. WalletTxId-                        -- Filter out the given transaction-            let cond = t ^. WalletTxHash !=. val txid-                        -- Limit to only the input coins of the given tx-                       &&. limitSpent s ins-            where_ $ case aiM of-                Just ai -> cond &&. s ^. SpentCoinAccount ==. val ai-                _       -> cond-            return t-  where-    txid = txHash tx-    txInputs = map prevOutput $ txIn tx-    limitSpent s ins = join2 $ map (f s) ins-    f s (OutPoint h i) =-        s ^. SpentCoinHash ==. val h &&.-        s ^. SpentCoinPos  ==. val i---- Returns all the new coins that need to be created from a transaction.--- Also returns the addresses associted with those coins.-getNewCoins :: MonadIO m-            => Tx-            -> Maybe AccountId-            -> SqlPersistT m [OutCoinData]-getNewCoins tx aiM = do-    -- Find all the addresses which are in the transaction outputs-    addrs <- splitSelect uniqueAddrs $ \as -> from $ \x -> do-        let cond = x ^. WalletAddrAddress `in_` valList as-        where_ $ case aiM of-            Just ai -> cond &&. x ^. WalletAddrAccount ==. val ai-            _       -> cond-        return x-    return $ concatMap toCoins addrs-  where-    uniqueAddrs      = nub $ map (\(addr,_,_,_) -> addr) outList-    outList          = rights $ map toDat txOutputs-    txOutputs        = zip (txOut tx) [0..]-    toDat (out, pos) = getDataFromOutput out >>= \(addr, so) ->-        return (addr, out, pos, so)-    toCoins addrEnt@(Entity _ addr) =-        let f (a,_,_,_) = a == walletAddrAddress addr-        in  map (toCoin addrEnt) $ filter f outList-    toCoin addrEnt (_, out, pos, so) = OutCoinData-        { outCoinDataAddr   = addrEnt-        , outCoinDataPos    = pos-        , outCoinDataValue  = outValue out-        , outCoinDataScript = so-        }---- Decode an output and extract an output script and a recipient address-getDataFromOutput :: TxOut -> Either String (Address, ScriptOutput)-getDataFromOutput out = do-    so   <- decodeOutputBS $ scriptOutput out-    addr <- outputAddress so-    return (addr, so)--isCoinbaseTx :: Tx -> Bool-isCoinbaseTx tx =-    length (txIn tx) == 1 && outPointHash (prevOutput $ head (txIn tx)) ==-        "0000000000000000000000000000000000000000000000000000000000000000"---- | Spend the given input coins. We also create dummy coins for the inputs--- in a transaction that do not belong to us. This is to be able to detect--- double spends when reorgs occur.-spendInputs :: MonadIO m-            => AccountId-            -> WalletTxId-            -> Tx-            -> SqlPersistT m ()-spendInputs ai ti tx = do-    now <- liftIO getCurrentTime-    -- Spend the coins by inserting values in SpentCoin-    splitInsertMany_ $ map (buildSpentCoin now) txInputs-  where-    txInputs = map prevOutput $ txIn tx-    buildSpentCoin now (OutPoint h p) =-        SpentCoin{ spentCoinAccount    = ai-                 , spentCoinHash       = h-                 , spentCoinPos        = p-                 , spentCoinSpendingTx = ti-                 , spentCoinCreated    = now-                 }---- Build account transaction for the given input and output coins-buildAccTxs :: MonadIO m-            => Maybe (TBMChan Notif)-            -> Tx-            -> TxConfidence-            -> [InCoinData]-            -> [OutCoinData]-            -> SqlPersistT m [WalletTx]-buildAccTxs notifChanM tx confidence inCoins outCoins = do-    now <- liftIO getCurrentTime-    -- Group the coins by account-    let grouped = groupCoinsByAccount inCoins outCoins-    forM (M.toList grouped) $ \(ai, (is, os)) -> do-        let atx = buildAccTx tx confidence ai is os now-        -- Insert the new transaction. If it already exists, update the-        -- information with the newly computed values. Also make sure that the-        -- confidence is set to the new value (it could have changed to TxDead).-        Entity ti newAtx <- P.insertBy atx >>= \resE -> case resE of-            Left (Entity ti prev) -> do-                let prevConf = walletTxConfidence prev-                    newConf | confidence == TxDead     = TxDead-                            | prevConf   == TxBuilding = TxBuilding-                            | otherwise                = confidence-                -- If the transaction already exists, preserve confirmation data-                let newAtx = atx-                        { walletTxConfidence = newConf-                        , walletTxConfirmedBy = walletTxConfirmedBy prev-                        , walletTxConfirmedHeight = walletTxConfirmedHeight prev-                        , walletTxConfirmedDate = walletTxConfirmedDate prev-                        }-                replace ti newAtx-                -- Spend inputs only if the previous transaction was dead-                when (newConf /= TxDead && prevConf == TxDead) $-                    spendInputs ai ti tx-                -- If the transaction changed from non-dead to dead, kill it.-                -- This will remove spent coins and child transactions.-                when (prevConf /= TxDead && newConf == TxDead) $-                    killTxIds notifChanM [ti]-                return (Entity ti newAtx)-            Right ti -> do-                when (confidence /= TxDead) $ spendInputs ai ti tx-                return (Entity ti atx)--        -- Insert the output coins with updated accTx key-        let newOs = map (toCoin ai ti now) os-        forM_ newOs $ \c -> P.insertBy c >>= \resE -> case resE of-            Left (Entity ci _) -> replace ci c-            _ -> return ()--        -- Return the new transaction record-        return newAtx-  where-    toCoin ai accTxId now (OutCoinData addrEnt pos vl so) = WalletCoin-        { walletCoinAccount = ai-        , walletCoinHash    = txHash tx-        , walletCoinPos     = pos-        , walletCoinTx      = accTxId-        , walletCoinValue   = vl-        , walletCoinScript  = so-        , walletCoinAddr    = entityKey addrEnt-        , walletCoinCreated = now-        }---- | Build an account transaction given the input and output coins relevant to--- this specific account. An account transaction contains the details of how a--- transaction affects one particular account (value sent to and from the--- account). The first value is Maybe an existing transaction in the database--- which is used to get the existing confirmation values.-buildAccTx :: Tx-           -> TxConfidence-           -> AccountId-           -> [InCoinData]-           -> [OutCoinData]-           -> UTCTime-           -> WalletTx-buildAccTx tx confidence ai inCoins outCoins now = WalletTx-    { walletTxAccount = ai-    , walletTxHash    = txHash tx-    -- This is a hash of the transaction excluding signatures. This allows us-    -- to track the evolution of offline transactions as we add more signatures-    -- to them.-    , walletTxNosigHash = nosigTxHash tx-    , walletTxType      = txType-    , walletTxInValue   = inVal-    , walletTxOutValue  = outVal-    , walletTxInputs =-        let f h i (InCoinData (Entity _ c) t _) =-                walletTxHash t == h && walletCoinPos c == i-            toInfo (a, OutPoint h i) = case find (f h i) inCoins of-                Just (InCoinData (Entity _ c) _ _) ->-                    AddressInfo a (Just $ walletCoinValue c) True-                _ -> AddressInfo a Nothing False-        in  map toInfo allInAddrs-    , walletTxOutputs =-        let toInfo (a,i,v) = AddressInfo a (Just v) $ ours i-            ours i = isJust $ find ((== i) . outCoinDataPos) outCoins-        in  map toInfo allOutAddrs \\ changeAddrs-    , walletTxChange     = changeAddrs-    , walletTxTx         = tx-    , walletTxIsCoinbase = isCoinbaseTx tx-    , walletTxConfidence = confidence-        -- Reuse the confirmation information of the existing transaction if-        -- we have it.-    , walletTxConfirmedBy     = Nothing-    , walletTxConfirmedHeight = Nothing-    , walletTxConfirmedDate   = Nothing-    , walletTxCreated         = now-    }-  where-    -- The value going into the account is the sum of the output coins-    inVal  = sum $ map outCoinDataValue outCoins-    -- The value going out of the account is the sum on the input coins-    outVal = sum $ map coinValue inCoins-    allMyCoins = length inCoins  == length (txIn tx) &&-                 length outCoins == length (txOut tx)-    txType-        -- If all the coins belong to the same account, it is a self-        -- transaction (even if a fee was payed).-        | allMyCoins = TxSelf-        -- This case can happen in complex transactions where the total-        -- input/output sum for a given account is 0. In this case, we count-        -- that transaction as a TxSelf. This should not happen with simple-        -- transactions.-        | inVal == outVal = TxSelf-        | inVal > outVal  = TxIncoming-        | otherwise       = TxOutgoing-    -- List of all the decodable input addresses in the transaction-    allInAddrs =-        let f inp = do-                input <- decodeInputBS (scriptInput inp)-                addr <- inputAddress input-                return (addr, prevOutput inp)-        in  rights $ map f $ txIn tx-    -- List of all the decodable output addresses in the transaction-    allOutAddrs =-        let f op i = do-                addr <- outputAddress =<< decodeOutputBS (scriptOutput op)-                return (addr, i, outValue op)-        in  rights $ zipWith f (txOut tx) [0..]-    changeAddrs-        | txType == TxIncoming = []-        | otherwise =-            let isInternal = (== AddressInternal) . walletAddrType-                                . entityVal . outCoinDataAddr-                f = walletAddrAddress . entityVal . outCoinDataAddr-                toInfo c = AddressInfo (f c) (Just $ outCoinDataValue c) True-            in  map toInfo $ filter isInternal outCoins---- Group all the input and outputs coins from the same account together.-groupCoinsByAccount-    :: [InCoinData]-    -> [OutCoinData]-    -> M.Map AccountId ([InCoinData], [OutCoinData])-groupCoinsByAccount inCoins outCoins =-    M.unionWith merge inMap outMap-  where-    -- Build a map from accounts -> (inCoins, outCoins)-    f coin@(InCoinData _ t _) = (walletTxAccount t, [coin])-    g coin = (walletAddrAccount $ entityVal $  outCoinDataAddr coin, [coin])-    merge (is, _) (_, os) = (is, os)-    inMap  = M.map (\is -> (is, [])) $ M.fromListWith (++) $ map f inCoins-    outMap = M.map (\os -> ([], os)) $ M.fromListWith (++) $ map g outCoins--deleteTx :: (MonadIO m, MonadThrow m) => TxHash -> SqlPersistT m ()-deleteTx txid = do-    ts <- select $ from $ \t -> do-        where_ $ t ^. WalletTxHash ==. val txid-        return t-    case ts of-        [] -> throwM $ WalletException $ unwords-            [ "Cannot delete inexistent transaction"-            , cs (txHashToHex txid)-            ]-        Entity{entityVal = WalletTx{walletTxConfidence = TxBuilding}} : _ ->-            throwM $ WalletException $ unwords-                [ "Cannot delete confirmed transaction"-                , cs (txHashToHex txid)-                ]-        _ -> return ()-    children <- fmap (map unValue) $ select $ from $-        \(t `InnerJoin` c `InnerJoin` s `InnerJoin` t2) -> do-            on $   s ^. SpentCoinSpendingTx ==. t2 ^. WalletTxId-            on (   c ^. WalletCoinAccount   ==. t  ^. WalletTxAccount-               &&. c ^. WalletCoinHash      ==. s  ^. SpentCoinHash-               &&. c ^. WalletCoinPos       ==. s  ^. SpentCoinPos-               )-            on $   c ^. WalletCoinTx        ==. t  ^. WalletTxId-            where_ $ t ^. WalletTxHash ==. val txid-            return $ t2 ^. WalletTxHash-    forM_ children deleteTx-    forM_ ts $ \Entity{entityKey = ti} ->-        delete $ from $ \s -> where_ $ s ^. SpentCoinSpendingTx ==. val ti-    delete $ from $ \s -> where_ $ s ^. SpentCoinHash ==. val txid-    forM_ ts $ \Entity{entityKey = ti} -> do-        delete $ from $ \c -> where_ $ c ^. WalletCoinTx ==. val ti-        delete $ from $ \t -> where_ $ t ^. WalletTxId   ==. val ti---- Kill transactions and their children by ids.-killTxIds :: MonadIO m-          => Maybe (TBMChan Notif)-          -> [WalletTxId]-          -> SqlPersistT m ()-killTxIds notifChanM txIds = do-    -- Find all the transactions spending the coins of these transactions-    -- (Find all the child transactions)-    children <- splitSelect txIds $ \ts -> from $ \(t `InnerJoin` s) -> do-        on (   s ^. SpentCoinAccount ==. t ^. WalletTxAccount-           &&. s ^. SpentCoinHash    ==. t ^. WalletTxHash-           )-        where_ $ t ^. WalletTxId `in_` valList ts-        return $ s ^. SpentCoinSpendingTx--    -- Kill these transactions-    splitUpdate txIds $ \ts t -> do-        set t [ WalletTxConfidence =. val TxDead ]-        where_ $ t ^. WalletTxId `in_` valList ts--    case notifChanM of-        Nothing -> return ()-        Just notifChan -> do-            ts' <- fmap (map entityVal) $-                splitSelect txIds $ \ts -> from $ \t -> do-                    where_ $ t ^. WalletTxId `in_` valList ts-                    return t-            forM_ ts' $ \tx -> do-                let ai = walletTxAccount tx-                Account{..} <--                    fromMaybe (error "More velociraptors coming") <$> get ai-                liftIO $ atomically $ writeTBMChan notifChan $-                    NotifTx $ toJsonTx accountName Nothing tx---    -- This transaction doesn't spend any coins-    splitDelete txIds $ \ts -> from $ \s ->-        where_ $ s ^. SpentCoinSpendingTx `in_` valList ts--    -- Recursively kill all the child transactions.-    -- (Recurse at the end in case there are closed loops)-    unless (null children) $ killTxIds notifChanM $ nub $ map unValue children---- Kill transactions and their child transactions by hashes.-killTxs :: MonadIO m-        => Maybe (TBMChan Notif)-        -> [TxHash]-        -> SqlPersistT m ()-killTxs notifChanM txHashes = do-    res <- splitSelect txHashes $ \hs -> from $ \t -> do-        where_ $ t ^. WalletTxHash `in_` valList hs-        return $ t ^. WalletTxId-    killTxIds notifChanM $ map unValue res--{- Confirmations -}--importMerkles :: MonadIO m-              => BlockChainAction-              -> [MerkleTxs]-              -> Maybe (TBMChan Notif)-              -> SqlPersistT m ()-importMerkles action expTxsLs notifChanM =-    when (isBestChain action || isChainReorg action) $ do-        case action of-            ChainReorg _ os _ ->-                -- Unconfirm transactions from the old chain.-                let hs = map (Just . nodeHash) os-                in  splitUpdate hs $ \h t -> do-                        set t [ WalletTxConfidence      =. val TxPending-                              , WalletTxConfirmedBy     =. val Nothing-                              , WalletTxConfirmedHeight =. val Nothing-                              , WalletTxConfirmedDate   =. val Nothing-                              ]-                        where_ $ t ^. WalletTxConfirmedBy `in_` valList h-            _ -> return ()--        -- Find all the dead transactions which need to be revived-        deadTxs <- splitSelect (concat expTxsLs) $ \ts -> from $ \t -> do-            where_ (   t ^. WalletTxHash `in_` valList ts-                &&. t ^. WalletTxConfidence ==. val TxDead-                )-            return $ t ^. WalletTxTx--        -- Revive dead transactions (in no particular order)-        forM_ deadTxs $ reviveTx notifChanM . unValue--        -- Confirm the transactions-        forM_ (zip (actionNodes action) expTxsLs) $ \(node, hs) -> do-            let hash   = nodeHash node-                height = nodeBlockHeight node--            splitUpdate hs $ \h t -> do-                set t [ WalletTxConfidence =. val TxBuilding-                      , WalletTxConfirmedBy =. val (Just hash)-                      , WalletTxConfirmedHeight =.-                          val (Just height)-                      , WalletTxConfirmedDate =.-                          val (Just $ nodeTimestamp node)-                      ]-                where_ $ t ^. WalletTxHash `in_` valList h--            ts <- fmap (map entityVal) $ splitSelect hs $ \h -> from $ \t -> do-                    where_ $ t ^. WalletTxHash `in_` valList h-                    return t--            -- Update the best height in the wallet (used to compute the number-            -- of confirmations of transactions)-            setBestBlock hash height--            -- Send notification for block-            forM_ notifChanM $ \notifChan -> do-                liftIO $ atomically $ writeTBMChan notifChan $-                    NotifBlock JsonBlock-                        { jsonBlockHash = hash-                        , jsonBlockHeight = height-                        , jsonBlockPrev = nodePrev node-                        }-                sendTxs notifChan ts hash height-  where-    sendTxs notifChan ts hash height = forM_ ts $ \tx -> do-        let ai = walletTxAccount tx-        Account{..} <- fromMaybe (error "Dino crisis") <$> get ai-        liftIO $ atomically $ writeTBMChan notifChan $-            NotifTx $ toJsonTx accountName (Just (hash, height)) tx---- Helper function to set the best block and best block height in the DB.-setBestBlock :: MonadIO m => BlockHash -> Word32 -> SqlPersistT m ()-setBestBlock bid i = update $ \t -> set t [ WalletStateBlock  =. val bid-                                          , WalletStateHeight =. val i-                                          ]---- Helper function to get the best block and best block height from the DB-walletBestBlock :: MonadIO m => SqlPersistT m (BlockHash, Word32)-walletBestBlock = do-    cfgM <- fmap entityVal <$> P.selectFirst [] []-    return $ case cfgM of-        Just WalletState{..} -> (walletStateBlock, walletStateHeight)-        Nothing -> throw $ WalletException $ unwords-            [ "Could not get the best block."-            , "Wallet database is probably not initialized"-            ]---- Revive a dead transaction. All transactions that are in conflict with this--- one will be killed.-reviveTx :: MonadIO m-         => Maybe (TBMChan Notif)-         -> Tx-         -> SqlPersistT m ()-reviveTx notifChanM tx = do-    -- Kill all transactions spending our coins-    spendingTxs <- getSpendingTxs tx Nothing-    killTxIds notifChanM $ map entityKey spendingTxs--    -- Find all the WalletTxId that have to be revived-    ids <- select $ from $ \t -> do-        where_ $ t ^. WalletTxHash       ==. val (txHash tx)-            &&.  t ^. WalletTxConfidence ==. val TxDead-        return (t ^. WalletTxAccount, t ^. WalletTxId)--    -- Spend the inputs for all our transactions-    forM_ ids $ \(Value ai, Value ti) -> spendInputs ai ti tx--    let ids' = map (unValue . snd) ids-    -- Update the transactions-    splitUpdate ids' $ \is t -> do-        set t [ WalletTxConfidence      =. val TxPending-              , WalletTxConfirmedBy     =. val Nothing-              , WalletTxConfirmedHeight =. val Nothing-              , WalletTxConfirmedDate   =. val Nothing-              ]-        where_ $ t ^. WalletTxId `in_` valList is--    case notifChanM of-        Nothing -> return ()-        Just notifChan -> do-            ts' <- fmap (map entityVal) $-                splitSelect ids' $ \ts -> from $ \t -> do-                    where_ $ t ^. WalletTxId `in_` valList ts-                    return t-            forM_ ts' $ \tx' -> do-                let ai = walletTxAccount tx'-                Account{..} <--                    fromMaybe (error "Tyranossaurus Rex attacks") <$> get ai-                liftIO $ atomically $ writeTBMChan notifChan $-                    NotifTx $ toJsonTx accountName Nothing tx'--{- Transaction creation and signing (local wallet functions) -}---- | Create a transaction sending some coins to a list of recipient addresses.-createWalletTx-    :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-        => Entity Account        -- ^ Account Entity-        -> Maybe (TBMChan Notif) -- ^ Notification channel-        -> Maybe XPrvKey         -- ^ Key if not provided by account-        -> [(Address,Word64)]    -- ^ List of recipient addresses and amounts-        -> Word64                -- ^ Fee per 1000 bytes-        -> Word32                -- ^ Minimum confirmations-        -> Bool                  -- ^ Should fee be paid by recipient-        -> Bool                  -- ^ Should the transaction be signed-        -> SqlPersistT m (WalletTx, [WalletAddr])-        -- ^ (New transaction hash, Completed flag)-createWalletTx accE@(Entity ai acc) notifM masterM dests fee minConf rcptFee sign = do-    -- Build an unsigned transaction from the given recipient values and fee-    (unsignedTx, inCoins, newChangeAddrs) <--        buildUnsignedTx accE dests fee minConf rcptFee-    -- Sign our new transaction if signing was requested-    let dat = map toCoinSignData inCoins-        tx | sign      = signOfflineTx acc masterM unsignedTx dat-           | otherwise = unsignedTx-    -- Import the transaction in the wallet either as a network transaction if-    -- it is complete, or as an offline transaction otherwise.-    (res, newAddrs) <- importTx' tx notifM ai inCoins-    case res of-        (txRes:_) -> return (txRes, newAddrs ++ newChangeAddrs)-        _ -> liftIO . throwIO $ WalletException-            "Error while importing the new transaction"--toCoinSignData :: InCoinData -> CoinSignData-toCoinSignData (InCoinData (Entity _ c) t x) =-    CoinSignData (OutPoint (walletTxHash t) (walletCoinPos c))-                 (walletCoinScript c)-                 deriv-  where-    deriv = Deriv :/ addrTypeIndex (walletAddrType x) :/ walletAddrIndex x---- Build an unsigned transaction given a list of recipients and a fee. Returns--- the unsigned transaction together with the input coins that have been--- selected or spending.-buildUnsignedTx-    :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-    => Entity Account-    -> [(Address, Word64)]-    -> Word64-    -> Word32-    -> Bool-    -> SqlPersistT m (Tx, [InCoinData], [WalletAddr])-    -- ^ Generated change addresses-buildUnsignedTx _ [] _ _ _ = liftIO . throwIO $ WalletException-    "buildUnsignedTx: No transaction recipients have been provided"-buildUnsignedTx accE@(Entity ai acc) origDests origFee minConf rcptFee = do-    let p = case accountType acc of-                AccountMultisig m n -> (m, n)-                _ -> throw . WalletException $ "Invalid account type"-        fee = if rcptFee then 0 else origFee-        coins | isMultisigAccount acc = chooseMSCoins tot fee p True-              | otherwise             = chooseCoins   tot fee   True-        -- TODO: Add more policies like confirmations or coin age-        -- Sort coins by their values in descending order-        orderPolicy c _ = [desc $ c ^. WalletCoinValue]-        -- Find the spendable coins in the given account with the required number-        -- of minimum confirmations.-    selectRes <- spendableCoins ai minConf orderPolicy-    -- Find a selection of spendable coins that matches our target value-    let (selected, change) = either (throw . WalletException) id $ coins selectRes-        totFee | isMultisigAccount acc = getMSFee origFee p (length selected)-               | otherwise             = getFee   origFee   (length selected)-        -- Subtract fees from first destination if rcptFee-        value = snd $ head origDests--    -- First output must not be dust after deducting fees-    when (rcptFee && value < totFee + 5430) $ throw $ WalletException-        "First recipient cannot cover transaction fees"--        -- Subtract fees from first destination if rcptFee-    let dests | rcptFee =-                    second (const $ value - totFee) (head origDests) :-                    tail origDests-              | otherwise = origDests--    -- Make sure the first recipient has enough funds to cover the fee-    when (snd (head dests) <= 0) $ throw $-        WalletException "Transaction fees too high"--    -- If the change amount is not dust, we need to add a change address to-    -- our list of recipients.-    -- TODO: Put the dust value in a constant somewhere. We also need a more-    -- general way of detecting dust such as our transactions are not-    -- rejected by full nodes.-    (allDests, addrs) <- if change < 5430-        then return (dests, [])-        else do-             (addr, chng) <- newChangeAddr change-             return ((walletAddrAddress addr, chng) : dests, [addr])--    case buildAddrTx (map toOutPoint selected) $ map toBase58 allDests of-        Right tx -> return (tx, selected, addrs)-        Left err -> liftIO . throwIO $ WalletException err-  where-    tot = sum $ map snd origDests-    toBase58 (a, v) = (addrToBase58 a, v)-    toOutPoint (InCoinData (Entity _ c) t _) =-        OutPoint (walletTxHash t) (walletCoinPos c)-    newChangeAddr change = do-        let lq = ListRequest 0 0 False-        (as, _) <- unusedAddresses accE AddressInternal lq-        case as of-            (a:_) -> do-                -- Use the address to prevent reusing it again-                _ <- useAddress a-                -- TODO: Randomize the change position-                return (a, change)-            _ -> liftIO . throwIO $ WalletException-                "No unused addresses available"--signAccountTx :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-              => Entity Account-              -> Maybe (TBMChan Notif)-              -> Maybe XPrvKey-              -> TxHash-              -> SqlPersistT m ([WalletTx], [WalletAddr])-signAccountTx (Entity ai acc) notifChanM masterM txid = do-    (OfflineTxData tx dat, inCoins) <- getOfflineTxData ai txid-    let signedTx = signOfflineTx acc masterM tx dat-    importTx' signedTx notifChanM ai inCoins--getOfflineTxData-    :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-    => AccountId-    -> TxHash-    -> SqlPersistT m (OfflineTxData, [InCoinData])-getOfflineTxData ai txid = do-    txM <- getBy $ UniqueAccTx ai txid-    case txM of-        Just (Entity _ tx) -> do-            unless (walletTxConfidence tx == TxOffline) $ liftIO . throwIO $-                WalletException "Can only sign offline transactions."-            inCoins <- getInCoins (walletTxTx tx) $ Just ai-            return-                ( OfflineTxData (walletTxTx tx) $ map toCoinSignData inCoins-                , inCoins-                )-        _ -> liftIO . throwIO $ WalletException $ unwords-            [ "Invalid txid", cs $ txHashToHex txid ]---- Sign a transaction using a list of CoinSignData. This allows an offline--- signer without access to the coins to sign a given transaction.-signOfflineTx :: Account        -- ^ Account used for signing-              -> Maybe XPrvKey  -- ^ Key if not provided in account-              -> Tx             -- ^ Transaction to sign-              -> [CoinSignData] -- ^ Input signing data-              -> Tx-signOfflineTx acc masterM tx coinSignData-    | not validMaster = throw $ WalletException-        "Master key not valid"-    -- Sign the transaction deterministically-    | otherwise = either (throw . WalletException) id $-        signTx tx sigData $ map (toPrvKeyG . xPrvKey) prvKeys-  where-    -- Compute all the SigInputs-    sigData = map (toSigData acc) coinSignData-    -- Compute all the private keys-    prvKeys = map toPrvKey coinSignData-    -- Build a SigInput from a CoinSignData-    toSigData acc' (CoinSignData op so deriv) =-        -- TODO: Here we override the SigHash to be SigAll False all the time.-        -- Should we be more flexible?-        SigInput so op (SigAll False) $-            if isMultisigAccount acc-                then Just $ getPathRedeem acc' deriv-                else Nothing-    toPrvKey (CoinSignData _ _ deriv) = derivePath deriv master-    master = case masterM of-        Just m -> case accountDerivation acc of-            Just d -> derivePath d m-            Nothing -> m-        Nothing -> fromMaybe-            (throw $ WalletException "No extended private key available")-            (accountMaster acc)-    validMaster = deriveXPubKey master `elem` accountKeys acc---- Returns unspent coins that can be spent in an account that have a minimum--- number of confirmations. Coinbase coins can only be spent after 100--- confirmations.-spendableCoins-    :: (MonadIO m, MonadThrow m, MonadBase IO m, MonadResource m)-    => AccountId                   -- ^ Account key-    -> Word32                      -- ^ Minimum confirmations-    -> (    SqlExpr (Entity WalletCoin)-         -> SqlExpr (Entity WalletTx)-         -> [SqlExpr OrderBy]-       )-    -- ^ Coin ordering policy-    -> SqlPersistT m [InCoinData]       -- ^ Spendable coins-spendableCoins ai minConf orderPolicy =-    fmap (map f) $ select $ spendableCoinsFrom ai minConf orderPolicy-  where-    f (c, t, x) = InCoinData c (entityVal t) (entityVal x)--spendableCoinsFrom-    :: AccountId                   -- ^ Account key-    -> Word32                      -- ^ Minimum confirmations-    -> (    SqlExpr (Entity WalletCoin)-         -> SqlExpr (Entity WalletTx)-         -> [SqlExpr OrderBy]-       )-    -- ^ Coin ordering policy-    -> SqlQuery ( SqlExpr (Entity WalletCoin)-                , SqlExpr (Entity WalletTx)-                , SqlExpr (Entity WalletAddr)-                )-spendableCoinsFrom ai minConf orderPolicy =-    from $ \(c `InnerJoin` t `InnerJoin` x `LeftOuterJoin` s) -> do-        -- Joins have to be set in reverse order !-        -- Left outer join on spent coins-        on (   s ?. SpentCoinAccount ==. just (c ^. WalletCoinAccount)-           &&. s ?. SpentCoinHash    ==. just (c ^. WalletCoinHash)-           &&. s ?. SpentCoinPos     ==. just (c ^. WalletCoinPos)-           )-        on $ x ^. WalletAddrId ==. c ^. WalletCoinAddr-        -- Inner join on coins and transactions-        on $  t ^. WalletTxId ==. c ^. WalletCoinTx-        where_ (   c ^. WalletCoinAccount ==. val ai-               &&. t ^. WalletTxConfidence-                   `in_` valList [ TxPending, TxBuilding ]-                -- We only want unspent coins-               &&. E.isNothing (s ?. SpentCoinId)-               &&. limitConfirmations (Right t) minConf-               )-        orderBy (orderPolicy c t)-        return (c, t, x)---- If the current height is 200 and a coin was confirmed at height 198, then it--- has 3 confirmations. So, if we require 3 confirmations, we want coins with a--- confirmed height of 198 or less (200 - 3 + 1).-limitConfirmations :: Either (SqlExpr (Maybe (Entity WalletTx)))-                             (SqlExpr (Entity WalletTx))-                   -> Word32-                   -> SqlExpr (Value Bool)-limitConfirmations txE minconf-    | minconf == 0  = limitCoinbase-    | minconf < 100 = limitConfs minconf &&. limitCoinbase-    | otherwise     = limitConfs minconf-  where-    limitConfs i = case txE of-        Left t -> t ?. WalletTxConfirmedHeight-            <=. just (just (selectHeight -. val (i - 1)))-        Right t -> t ^. WalletTxConfirmedHeight-            <=. just (selectHeight -. val (i - 1))-    -- Coinbase transactions require 100 confirmations-    limitCoinbase = case txE of-        Left t ->-            not_ (coalesceDefault [t ?. WalletTxIsCoinbase] (val False)) ||.-            limitConfs 100-        Right t ->-            not_ (t ^. WalletTxIsCoinbase) ||. limitConfs 100-    selectHeight :: SqlExpr (Value Word32)-    selectHeight = sub_select $ from $ \co -> do-        limit 1-        return $ co ^. WalletStateHeight--{- Balances -}--accountBalance :: MonadIO m-               => AccountId-               -> Word32-               -> Bool-               -> SqlPersistT m Word64-accountBalance ai minconf offline = do-    res <- select $ from $ \(c `InnerJoin`-                             t `LeftOuterJoin` s `LeftOuterJoin` st) -> do-        on $ st ?. WalletTxId ==. s ?. SpentCoinSpendingTx-        on (   s ?. SpentCoinAccount ==. just (c ^. WalletCoinAccount)-           &&. s ?. SpentCoinHash    ==. just (c ^. WalletCoinHash)-           &&. s ?. SpentCoinPos     ==. just (c ^. WalletCoinPos)-           )-        on $ t ^. WalletTxId ==. c ^. WalletCoinTx-        let unspent = E.isNothing ( s ?. SpentCoinId )-            spentOffline = st ?. WalletTxConfidence ==. just (val TxOffline)-            cond =     c ^. WalletCoinAccount ==. val ai-                   &&. t ^. WalletTxConfidence `in_` valList validConfidence-                   -- For non-offline balances, we have to take into account-                   -- the coins which are spent by offline transactions.-                   &&. if offline then unspent else unspent ||. spentOffline-        where_ $ if minconf == 0-                    then cond-                    else cond &&. limitConfirmations (Right t) minconf-        return $ sum_ (c ^. WalletCoinValue)-    case res of-        (Value (Just s):_) -> return $ floor (s :: Double)-        _ -> return 0-  where-    validConfidence = TxPending : TxBuilding : [ TxOffline | offline ]--addressBalances :: MonadIO m-                => Entity Account-                -> KeyIndex-                -> KeyIndex-                -> AddressType-                -> Word32-                -> Bool-                -> SqlPersistT m [(KeyIndex, BalanceInfo)]-addressBalances accE@(Entity ai _) iMin iMax addrType minconf offline = do-        -- We keep our joins flat to improve performance in SQLite.-    res <- select $ from $ \(x `LeftOuterJoin` c `LeftOuterJoin`-                             t `LeftOuterJoin` s `LeftOuterJoin` st) -> do-        let joinCond = st ?. WalletTxId ==. s ?. SpentCoinSpendingTx-        -- Do not join the spending information for offline transactions if we-        -- request the online balances. This will count the coin as unspent.-        on $ if offline-            then joinCond-            else joinCond &&.-                 st ?. WalletTxConfidence !=. just (val TxOffline)-        on $   s ?. SpentCoinAccount ==. c ?. WalletCoinAccount-           &&. s ?. SpentCoinHash    ==. c ?. WalletCoinHash-           &&. s ?. SpentCoinPos     ==. c ?. WalletCoinPos-        let txJoin =     t ?. WalletTxId          ==.  c ?. WalletCoinTx-                     &&. t ?. WalletTxConfidence `in_` valList validConfidence-        on $ if minconf == 0-                then txJoin-                else txJoin &&. limitConfirmations (Left t) minconf-        on $ c ?. WalletCoinAddr ==. just (x ^. WalletAddrId)-        let limitIndex-                | iMin == iMax = x ^. WalletAddrIndex ==. val iMin-                | otherwise = x ^. WalletAddrIndex >=. val iMin-                          &&. x ^. WalletAddrIndex <=. val iMax-        where_ (   x ^. WalletAddrAccount ==. val ai-               &&. limitIndex-               &&. x ^. WalletAddrIndex <.  subSelectAddrCount accE addrType-               &&. x ^. WalletAddrType  ==. val addrType-               )-        groupBy $ x ^. WalletAddrIndex-        let unspent   = E.isNothing $ st ?. WalletTxId-            invalidTx = E.isNothing $ t ?. WalletTxId-        return ( x ^. WalletAddrIndex -- Address index-               , sum_ $ case_-                   [ when_ invalidTx-                     then_ (val (Just 0))-                   ] (else_ $ c ?. WalletCoinValue) -- Out value-               , sum_ $ case_-                   [ when_ (unspent ||. invalidTx)-                     then_ (val (Just 0))-                   ] (else_ $ c ?. WalletCoinValue) -- Out value-               , count $ t ?. WalletTxId -- New coins-               , count $ case_-                   [ when_ invalidTx-                     then_ (val Nothing)-                   ] (else_ $ st ?. WalletTxId) -- Spent coins-               )-    return $ map f res-  where-    validConfidence = Just TxPending : Just TxBuilding :-                        [ Just TxOffline | offline ]-    f (Value i, Value inM, Value outM, Value newC, Value spentC) =-        let b = BalanceInfo-                    { balanceInfoInBalance  =-                        floor $ fromMaybe (0 :: Double) inM-                    , balanceInfoOutBalance =-                        floor $ fromMaybe (0 :: Double) outM-                    , balanceInfoCoins      = newC-                    , balanceInfoSpentCoins = spentC-                    }-        in (i, b)--{- Rescans -}--resetRescan :: MonadIO m => SqlPersistT m ()-resetRescan = do-    P.deleteWhere ([] :: [P.Filter WalletCoin])-    P.deleteWhere ([] :: [P.Filter SpentCoin])-    P.deleteWhere ([] :: [P.Filter WalletTx])-    setBestBlock (headerHash genesisHeader) 0-
− Network/Haskoin/Wallet/Types.hs
@@ -1,790 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module Network.Haskoin.Wallet.Types-( AccountName---- JSON Types-, JsonAccount(..)-, JsonAddr(..)-, JsonCoin(..)-, JsonTx(..)---- Request Types-, WalletRequest(..)-, ListRequest(..)-, NewAccount(..)-, SetAccountGap(..)-, OfflineTxData(..)-, CoinSignData(..)-, TxAction(..)-, AddressLabel(..)-, NodeAction(..)-, AccountType(..)-, AddressType(..)-, addrTypeIndex-, TxType(..)-, TxConfidence(..)-, AddressInfo(..)-, BalanceInfo(..)---- Response Types-, WalletResponse(..)-, TxCompleteRes(..)-, ListResult(..)-, RescanRes(..)-, JsonSyncBlock(..)-, JsonBlock(..)-, Notif(..)-, BlockInfo(..)---- Helper Types-, WalletException(..)-, BTCNode(..)---- *Helpers-, splitSelect-, splitUpdate-, splitDelete-, splitInsertMany_-, join2-, limitOffset-) where--import           Control.DeepSeq                 (NFData (..))-import           Control.Exception               (Exception)-import           Control.Monad                   (forM, forM_, mzero, when)-import           Control.Monad.Trans             (MonadIO)-import           Data.Aeson                      (FromJSON, ToJSON, Value (..),-                                                  decodeStrict', object,-                                                  parseJSON, toJSON, withObject,-                                                  (.!=), (.:), (.:?), (.=))-import qualified Data.Aeson                      as Aeson-import           Data.Aeson.TH                   (deriveJSON)-import           Data.Aeson.Types                (Options (..),-                                                  SumEncoding (..),-                                                  defaultOptions,-                                                  defaultTaggedObject)-import           Data.Char                       (toLower)-import           Data.Int                        (Int64)-import           Data.List.Split                 (chunksOf)-import           Data.Maybe                      (maybeToList)-import           Data.Serialize                  (Serialize, decode, encode)-import           Data.String                     (fromString)-import           Data.String.Conversions         (cs)-import           Data.Text                       (Text)-import           Data.Time                       (UTCTime)-import           Data.Typeable                   (Typeable)-import           Data.Word                       (Word32, Word64)-import           Database.Esqueleto              (Entity (..), SqlBackend,-                                                  SqlExpr, SqlPersistT,-                                                  SqlQuery, limit, offset,-                                                  select, update, val, (||.))-import qualified Database.Esqueleto              as E (Value, delete)-import           Database.Esqueleto.Internal.Sql (SqlSelect)-import qualified Database.Persist                as P (PersistEntity,-                                                       PersistEntityBackend,-                                                       insertMany_)-import           Database.Persist.Class          (PersistField,-                                                  fromPersistValue,-                                                  toPersistValue)-import           Database.Persist.Sql            (PersistFieldSql, SqlType (..),-                                                  sqlType)-import           Database.Persist.Types          (PersistValue (..))-import           GHC.Generics                    (Generic)-import           Network.Haskoin.Block-import           Network.Haskoin.Crypto-import           Network.Haskoin.Node-import           Network.Haskoin.Node.HeaderTree-import           Network.Haskoin.Script-import           Network.Haskoin.Transaction-import           Network.Haskoin.Util-import           Network.Haskoin.Wallet.Database-import           Network.Haskoin.Wallet.Types.BlockInfo--type AccountName = Text---- TODO: Add NFData instances for all those types--{- Request Types -}--data TxType-    = TxIncoming-    | TxOutgoing-    | TxSelf-    deriving (Eq, Show, Read)--instance NFData TxType where-    rnf x = x `seq` ()--$(deriveJSON (dropSumLabels 2 0 "") ''TxType)--data TxConfidence-    = TxOffline-    | TxDead-    | TxPending-    | TxBuilding-    deriving (Eq, Show, Read)--instance NFData TxConfidence where-    rnf x = x `seq` ()--$(deriveJSON (dropSumLabels 2 0 "") ''TxConfidence)--data AddressInfo = AddressInfo-    { addressInfoAddress :: !Address-    , addressInfoValue   :: !(Maybe Word64)-    , addressInfoIsLocal :: !Bool-    }-    deriving (Eq, Show, Read, Generic)--instance Serialize AddressInfo--$(deriveJSON (dropFieldLabel 11) ''AddressInfo)--instance NFData AddressInfo where-    rnf AddressInfo{..} =-        rnf addressInfoAddress `seq`-        rnf addressInfoValue `seq`-        rnf addressInfoIsLocal--data BalanceInfo = BalanceInfo-    { balanceInfoInBalance  :: !Word64-    , balanceInfoOutBalance :: !Word64-    , balanceInfoCoins      :: !Int-    , balanceInfoSpentCoins :: !Int-    }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 11) ''BalanceInfo)--instance NFData BalanceInfo where-    rnf BalanceInfo{..} =-        rnf balanceInfoInBalance `seq`-        rnf balanceInfoOutBalance `seq`-        rnf balanceInfoCoins `seq`-        rnf balanceInfoSpentCoins--data AccountType-    = AccountRegular-    | AccountMultisig-        { accountTypeRequiredSigs :: !Int-        , accountTypeTotalKeys    :: !Int-        }-    deriving (Eq, Show, Read)--instance NFData AccountType where-    rnf t = case t of-        AccountRegular -> ()-        AccountMultisig m n -> rnf m `seq` rnf n--instance ToJSON AccountType where-    toJSON accType = case accType of-        AccountRegular -> object-            [ "type"         .= String "regular" ]-        AccountMultisig m n -> object-            [ "type"         .= String "multisig"-            , "requiredsigs" .= m-            , "totalkeys"    .= n-            ]--instance FromJSON AccountType where-    parseJSON = withObject "AccountType" $ \o ->-        o .: "type" >>= \t -> case (t :: Text) of-            "regular"  -> return AccountRegular-            "multisig" -> AccountMultisig <$> o .: "requiredsigs"-                                          <*> o .: "totalkeys"-            _ -> mzero--data NewAccount = NewAccount-    { newAccountName     :: !AccountName-    , newAccountType     :: !AccountType-    , newAccountMnemonic :: !(Maybe Text)-    , newAccountMaster   :: !(Maybe XPrvKey)-    , newAccountDeriv    :: !(Maybe HardPath)-    , newAccountKeys     :: ![XPubKey]-    , newAccountReadOnly :: !Bool-    }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 10) ''NewAccount)--data SetAccountGap = SetAccountGap { getAccountGap :: !Word32 }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 10) ''SetAccountGap)--data ListRequest = ListRequest-    { listOffset  :: !Word32-    , listLimit   :: !Word32-    , listReverse :: !Bool-    }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 4) ''ListRequest)--data CoinSignData = CoinSignData-    { coinSignOutPoint     :: !OutPoint-    , coinSignScriptOutput :: !ScriptOutput-    , coinSignDeriv        :: !SoftPath-    }-    deriving (Eq, Show)--$(deriveJSON (dropFieldLabel 8) ''CoinSignData)--data OfflineTxData = OfflineTxData-    { offlineTxDataTx       :: !Tx-    , offlineTxDataCoinData :: ![CoinSignData]-    }--$(deriveJSON (dropFieldLabel 13) ''OfflineTxData)--data TxAction-    = CreateTx-        { accTxActionRecipients :: ![(Address, Word64)]-        , accTxActionFee        :: !Word64-        , accTxActionMinConf    :: !Word32-        , accTxActionRcptFee    :: !Bool-        , accTxActionSign       :: !Bool-        }-    | ImportTx-        { accTxActionTx :: !Tx }-    | SignTx-        { accTxActionHash :: !TxHash }-    deriving (Eq, Show)--instance ToJSON TxAction where-    toJSON (CreateTx recipients fee minConf rcptFee sign) = object $-        [ "type" .= ("createtx" :: Text)-        , "recipients" .= recipients-        , "fee"  .= fee-        , "minconf" .= minConf-        , "sign" .= sign-        ] ++ [ "rcptfee" .= True | rcptFee ]-    toJSON (ImportTx tx) = object-        [ "type" .= ("importtx" :: Text)-        , "tx" .= tx-        ]-    toJSON (SignTx txid) = object-        [ "type" .= ("signtx" :: Text)-        , "hash" .= txid-        ]--instance FromJSON TxAction where-    parseJSON = withObject "TxAction" $ \o -> do-        t <- o .: "type"-        case (t :: Text) of-            "createtx" -> do-                recipients <- o .: "recipients"-                fee <- o .: "fee"-                minConf <- o .: "minconf"-                sign <- o .: "sign"-                rcptFee <- o .:? "rcptfee" .!= False-                return (CreateTx recipients fee minConf rcptFee sign)-            "importtx" -> do-                tx <- o .: "tx"-                return (ImportTx tx)-            "signtx" -> do-                txid <- o .: "hash"-                return (SignTx txid)-            _ -> mzero--data AddressLabel = AddressLabel { addressLabelLabel :: !Text }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 12) ''AddressLabel)--data NodeAction-    = NodeActionRescan { nodeActionTimestamp :: !(Maybe Word32) }-    | NodeActionStatus-    deriving (Eq, Show, Read)--instance ToJSON NodeAction where-    toJSON na = case na of-        NodeActionRescan tM -> object $-            ("type" .= String "rescan") : (("timestamp" .=) <$> maybeToList tM)-        NodeActionStatus -> object [ "type" .= String "status" ]--instance FromJSON NodeAction where-    parseJSON = withObject "NodeAction" $ \o -> do-        String t <- o .: "type"-        case t of-            "rescan" -> NodeActionRescan <$> o .:? "timestamp"-            "status" -> return NodeActionStatus-            _ -> mzero--data AddressType-    = AddressInternal-    | AddressExternal-    deriving (Eq, Show, Read)--$(deriveJSON (dropSumLabels 7 0 "") ''AddressType)--instance NFData AddressType where-    rnf x = x `seq` ()--addrTypeIndex :: AddressType -> KeyIndex-addrTypeIndex AddressExternal = 0-addrTypeIndex AddressInternal = 1--data WalletRequest-    = GetAccountsR !ListRequest-    | PostAccountsR !NewAccount-    | PostAccountRenameR !AccountName !AccountName-    | GetAccountR !AccountName-    | PostAccountKeysR !AccountName ![XPubKey]-    | PostAccountGapR !AccountName !SetAccountGap-    | GetAddressesR !AccountName !AddressType !Word32 !Bool !ListRequest-    | GetAddressesUnusedR !AccountName !AddressType !ListRequest-    | GetAddressR !AccountName !KeyIndex !AddressType !Word32 !Bool-    | GetIndexR !AccountName !PubKeyC !AddressType-    | PutAddressR !AccountName !KeyIndex !AddressType !AddressLabel-    | PostAddressesR !AccountName !KeyIndex !AddressType-    | GetTxsR !AccountName !ListRequest-    | GetAddrTxsR !AccountName !KeyIndex !AddressType !ListRequest-    | PostTxsR !AccountName !(Maybe XPrvKey) !TxAction-    | GetTxR !AccountName !TxHash-    | GetOfflineTxR !AccountName !TxHash-    | PostOfflineTxR !AccountName !(Maybe XPrvKey) !Tx ![CoinSignData]-    | GetBalanceR !AccountName !Word32 !Bool-    | PostNodeR !NodeAction-    | DeleteTxIdR !TxHash-    | GetSyncR !AccountName !BlockHash !ListRequest-    | GetSyncHeightR !AccountName !BlockHeight !ListRequest-    | GetPendingR !AccountName !ListRequest-    | GetDeadR !AccountName !ListRequest-    | GetBlockInfoR ![BlockHash]-    | StopServerR-        deriving (Show, Eq)---- TODO: Set omitEmptyContents on aeson-0.9-$(deriveJSON-    defaultOptions-        { constructorTagModifier = map toLower . init-        , sumEncoding = defaultTaggedObject-            { tagFieldName      = "method"-            , contentsFieldName = "request"-            }-        }-    ''WalletRequest- )--{- JSON Types -}--data JsonAccount = JsonAccount-    { jsonAccountName       :: !Text-    , jsonAccountType       :: !AccountType-    , jsonAccountMaster     :: !(Maybe XPrvKey)-    , jsonAccountMnemonic   :: !(Maybe Text)-    , jsonAccountDerivation :: !(Maybe HardPath)-    , jsonAccountKeys       :: ![XPubKey]-    , jsonAccountGap        :: !Word32-    , jsonAccountCreated    :: !UTCTime-    }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 11) ''JsonAccount)--data JsonAddr = JsonAddr-    { jsonAddrAddress :: !Address-    , jsonAddrIndex   :: !KeyIndex-    , jsonAddrType    :: !AddressType-    , jsonAddrLabel   :: !Text-    , jsonAddrRedeem  :: !(Maybe ScriptOutput)-    , jsonAddrKey     :: !(Maybe PubKeyC)-    , jsonAddrCreated :: !UTCTime-    -- Optional Balance-    , jsonAddrBalance :: !(Maybe BalanceInfo)-    }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 8) ''JsonAddr)--data JsonTx = JsonTx-    { jsonTxHash            :: !TxHash-    , jsonTxNosigHash       :: !TxHash-    , jsonTxType            :: !TxType-    , jsonTxInValue         :: !Word64-    , jsonTxOutValue        :: !Word64-    , jsonTxValue           :: !Int64-    , jsonTxInputs          :: ![AddressInfo]-    , jsonTxOutputs         :: ![AddressInfo]-    , jsonTxChange          :: ![AddressInfo]-    , jsonTxTx              :: !Tx-    , jsonTxIsCoinbase      :: !Bool-    , jsonTxConfidence      :: !TxConfidence-    , jsonTxConfirmedBy     :: !(Maybe BlockHash)-    , jsonTxConfirmedHeight :: !(Maybe Word32)-    , jsonTxConfirmedDate   :: !(Maybe Word32)-    , jsonTxCreated         :: !UTCTime-    , jsonTxAccount         :: !AccountName-    -- Optional confirmation-    , jsonTxConfirmations   :: !(Maybe Word32)-    , jsonTxBestBlock       :: !(Maybe BlockHash)-    , jsonTxBestBlockHeight :: !(Maybe BlockHeight)-    }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 6) ''JsonTx)--data JsonCoin = JsonCoin-    { jsonCoinHash       :: !TxHash-    , jsonCoinPos        :: !Word32-    , jsonCoinValue      :: !Word64-    , jsonCoinScript     :: !ScriptOutput-    , jsonCoinCreated    :: !UTCTime-    -- Optional Tx-    , jsonCoinTx         :: !(Maybe JsonTx)-    -- Optional Address-    , jsonCoinAddress    :: !(Maybe JsonAddr)-    -- Optional spender-    , jsonCoinSpendingTx :: !(Maybe JsonTx)-    }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 8) ''JsonCoin)--{- Response Types -}--data TxCompleteRes = TxCompleteRes-    { txCompleteTx       :: !Tx-    , txCompleteComplete :: !Bool-    } deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 10) ''TxCompleteRes)--data ListResult a = ListResult-    { listResultItems :: ![a]-    , listResultTotal :: !Word32-    }--$(deriveJSON (dropFieldLabel 10) ''ListResult)--data RescanRes = RescanRes { rescanTimestamp :: !Word32 }-    deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 6) ''RescanRes)--data WalletResponse a-    = ResponseError { responseError :: !Text }-    | ResponseValid { responseResult :: !(Maybe a)  }-    deriving (Eq, Show)--$(deriveJSON (dropSumLabels 8 8 "status") ''WalletResponse)--data JsonSyncBlock = JsonSyncBlock-    { jsonSyncBlockHash   :: !BlockHash-    , jsonSyncBlockHeight :: !BlockHeight-    , jsonSyncBlockPrev   :: !BlockHash-    , jsonSyncBlockTxs    :: ![JsonTx]-    } deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 13) ''JsonSyncBlock)--data JsonBlock = JsonBlock-    { jsonBlockHash   :: !BlockHash-    , jsonBlockHeight :: !BlockHeight-    , jsonBlockPrev   :: !BlockHash-    } deriving (Eq, Show, Read)--$(deriveJSON (dropFieldLabel 9) ''JsonBlock)--data Notif-    = NotifBlock !JsonBlock-    | NotifTx    !JsonTx-    deriving (Eq, Show, Read)--$(deriveJSON (dropSumLabels 5 5 "type") ''Notif)--{- Helper Types -}--data WalletException = WalletException !String-    deriving (Eq, Read, Show, Typeable)--instance Exception WalletException--data BTCNode = BTCNode { btcNodeHost :: !String, btcNodePort :: !Int }-    deriving (Eq, Read, Show)--$(deriveJSON (dropFieldLabel 7) ''BTCNode)--{- Persistent Instances -}--instance PersistField XPrvKey where-    toPersistValue = PersistText . cs . xPrvExport-    fromPersistValue (PersistText txt) =-        maybeToEither "Invalid Persistent XPrvKey" $ xPrvImport $ cs txt-    fromPersistValue (PersistByteString bs) =-        maybeToEither "Invalid Persistent XPrvKey" $ xPrvImport bs-    fromPersistValue _ = Left "Invalid Persistent XPrvKey"--instance PersistFieldSql XPrvKey where-    sqlType _ = SqlString--instance PersistField [XPubKey] where-    toPersistValue = PersistText . cs . Aeson.encode-    fromPersistValue (PersistText txt) =-        maybeToEither "Invalid Persistent XPubKey" $ decodeStrict' $ cs txt-    fromPersistValue (PersistByteString bs) =-        maybeToEither "Invalid Persistent XPubKey" $ decodeStrict' bs-    fromPersistValue _ = Left "Invalid Persistent XPubKey"--instance PersistFieldSql [XPubKey] where-    sqlType _ = SqlString--instance PersistField DerivPath where-    toPersistValue = PersistText . cs . pathToStr-    fromPersistValue (PersistText txt) = maybeToEither-        "Invalid Persistent DerivPath" . fmap getParsedPath .-        parsePath . cs $ txt-    fromPersistValue (PersistByteString bs) = maybeToEither-        "Invalid Persistent DerivPath" . fmap getParsedPath .-        parsePath . cs $ bs-    fromPersistValue _ = Left "Invalid Persistent DerivPath"--instance PersistFieldSql DerivPath where-    sqlType _ = SqlString--instance PersistField HardPath where-    toPersistValue = PersistText . cs . pathToStr-    fromPersistValue (PersistText txt) = maybeToEither-        "Invalid Persistent HardPath" $ parseHard $ cs txt-    fromPersistValue (PersistByteString bs) = maybeToEither-        "Invalid Persistent HardPath" $ parseHard $ cs bs-    fromPersistValue _ = Left "Invalid Persistent HardPath"--instance PersistFieldSql HardPath where-    sqlType _ = SqlString--instance PersistField SoftPath where-    toPersistValue = PersistText . cs . pathToStr-    fromPersistValue (PersistText txt) = maybeToEither-        "Invalid Persistent SoftPath" $ parseSoft $ cs txt-    fromPersistValue (PersistByteString bs) = maybeToEither-        "Invalid Persistent SoftPath" $ parseSoft $ cs bs-    fromPersistValue _ = Left "Invalid Persistent SoftPath"--instance PersistFieldSql SoftPath where-    sqlType _ = SqlString--instance PersistField AccountType where-    toPersistValue = PersistText . cs . Aeson.encode-    fromPersistValue (PersistText txt) = maybeToEither-        "Invalid Persistent AccountType" $ decodeStrict' $ cs txt-    fromPersistValue (PersistByteString bs) = maybeToEither-        "Invalid Persistent AccountType" $ decodeStrict' bs-    fromPersistValue _ = Left "Invalid Persistent AccountType"--instance PersistFieldSql AccountType where-    sqlType _ = SqlString--instance PersistField AddressType where-    toPersistValue ts = PersistBool $ case ts of-        AddressExternal -> True-        AddressInternal -> False--    fromPersistValue (PersistBool b) = return $-        if b then AddressExternal else AddressInternal--    fromPersistValue (PersistInt64 i) = return $ case i of-        0 -> AddressInternal-        _ -> AddressExternal--    fromPersistValue _ = Left "Invalid Persistent AddressType"--instance PersistFieldSql AddressType where-    sqlType _ = SqlBool--instance PersistField TxType where-    toPersistValue ts = PersistText $ case ts of-        TxIncoming -> "incoming"-        TxOutgoing -> "outgoing"-        TxSelf     -> "self"--    fromPersistValue (PersistText txt) = case txt of-        "incoming" -> return TxIncoming-        "outgoing" -> return TxOutgoing-        "self"     -> return TxSelf-        _ -> Left "Invalid Persistent TxType"--    fromPersistValue (PersistByteString bs) = case bs of-        "incoming" -> return TxIncoming-        "outgoing" -> return TxOutgoing-        "self"     -> return TxSelf-        _ -> Left "Invalid Persistent TxType"--    fromPersistValue _ = Left "Invalid Persistent TxType"--instance PersistFieldSql TxType where-    sqlType _ = SqlString--instance PersistField Address where-    toPersistValue = PersistText . cs . addrToBase58-    fromPersistValue (PersistText a) =-        maybeToEither "Invalid Persistent Address" $ base58ToAddr $ cs a-    fromPersistValue (PersistByteString a) =-        maybeToEither "Invalid Persistent Address" $ base58ToAddr a-    fromPersistValue _ = Left "Invalid Persistent Address"--instance PersistFieldSql Address where-    sqlType _ = SqlString--instance PersistField BloomFilter where-    toPersistValue = PersistByteString . encode-    fromPersistValue (PersistByteString bs) =-        case decode bs of-            Right x -> Right x-            Left  e -> Left  (fromString e)-    fromPersistValue _ = Left "Invalid Persistent BloomFilter"--instance PersistFieldSql BloomFilter where-    sqlType _ = SqlBlob--instance PersistField BlockHash where-    toPersistValue = PersistText . cs . blockHashToHex-    fromPersistValue (PersistText h) =-        maybeToEither "Could not decode BlockHash" $ hexToBlockHash $ cs h-    fromPersistValue (PersistByteString h) =-        maybeToEither "Could not decode BlockHash" $ hexToBlockHash h-    fromPersistValue _ = Left "Invalid Persistent BlockHash"--instance PersistFieldSql BlockHash where-    sqlType _ = SqlString--instance PersistField TxHash where-    toPersistValue = PersistText . cs . txHashToHex-    fromPersistValue (PersistText h) =-        maybeToEither "Invalid Persistent TxHash" $ hexToTxHash $ cs h-    fromPersistValue (PersistByteString h) =-        maybeToEither "Invalid Persistent TxHash" $ hexToTxHash h-    fromPersistValue _ = Left "Invalid Persistent TxHash"--instance PersistFieldSql TxHash where-    sqlType _ = SqlString--instance PersistField TxConfidence where-    toPersistValue tc = PersistText $ case tc of-        TxOffline  -> "offline"-        TxDead     -> "dead"-        TxPending  -> "pending"-        TxBuilding -> "building"--    fromPersistValue (PersistText txt) = case txt of-        "offline"  -> return TxOffline-        "dead"     -> return TxDead-        "pending"  -> return TxPending-        "building" -> return TxBuilding-        _ -> Left "Invalid Persistent TxConfidence"--    fromPersistValue (PersistByteString bs) = case bs of-        "offline"  -> return TxOffline-        "dead"     -> return TxDead-        "pending"  -> return TxPending-        "building" -> return TxBuilding-        _ -> Left "Invalid Persistent TxConfidence"--    fromPersistValue _ = Left "Invalid Persistent TxConfidence"--instance PersistFieldSql TxConfidence where-    sqlType _ = SqlString--instance PersistField Tx where-    toPersistValue = PersistByteString . encode-    fromPersistValue (PersistByteString bs) =-        case decode bs of-            Right x -> Right x-            Left  e -> Left (fromString e)-    fromPersistValue _ = Left "Invalid Persistent Tx"--instance PersistFieldSql Tx where-    sqlType _ = SqlOther "MEDIUMBLOB"--instance PersistField PubKeyC where-    toPersistValue = PersistText . cs . encodeHex . encode-    fromPersistValue (PersistText txt) =-        case hex >>= decode of-            Right x -> Right x-            Left  e -> Left (fromString e)-      where-        hex = maybeToEither "Could not decode hex" (decodeHex (cs txt))-    fromPersistValue (PersistByteString bs) =-        case hex >>= decode of-            Right x -> Right x-            Left  e -> Left (fromString e)-      where-        hex = maybeToEither "Could not decode hex" (decodeHex bs)-    fromPersistValue _ = Left "Invalid Persistent PubKeyC"--instance PersistFieldSql PubKeyC where-    sqlType _ = SqlString--instance PersistField ScriptOutput where-    toPersistValue = PersistByteString . encodeOutputBS-    fromPersistValue (PersistByteString bs) =-        case decodeOutputBS bs of-            Right x -> Right x-            Left  e -> Left (fromString e)-    fromPersistValue _ = Left "Invalid Persistent ScriptOutput"--instance PersistFieldSql ScriptOutput where-    sqlType _ = SqlBlob--instance PersistField [AddressInfo] where-    toPersistValue = PersistByteString . encode-    fromPersistValue (PersistByteString bs) =-        case decode bs of-            Right x -> Right x-            Left  e -> Left (fromString e)-    fromPersistValue _ = Left "Invalid Persistent AddressInfo"--instance PersistFieldSql [AddressInfo] where-    sqlType _ = SqlOther "MEDIUMBLOB"--{- Helpers -}---- Join AND expressions with OR conditions in a binary way-join2 :: [SqlExpr (E.Value Bool)] -> SqlExpr (E.Value Bool)-join2 xs = case xs of-    [] -> val False-    [x] -> x-    _ -> let (ls,rs) = splitAt (length xs `div` 2) xs-         in  join2 ls ||. join2 rs--splitSelect :: (SqlSelect a r, MonadIO m)-            => [t]-            -> ([t] -> SqlQuery a)-            -> SqlPersistT m [r]-splitSelect ts queryF =-    fmap concat $ forM vals $ select . queryF-  where-    vals = chunksOf paramLimit ts--splitUpdate :: ( MonadIO m-               , P.PersistEntity val-               , P.PersistEntityBackend val ~ SqlBackend-               )-            => [t]-            -> ([t] -> SqlExpr (Entity val) -> SqlQuery ())-            -> SqlPersistT m ()-splitUpdate ts updateF =-    forM_ vals $ update . updateF-  where-    vals = chunksOf paramLimit ts--splitDelete :: MonadIO m => [t] -> ([t] -> SqlQuery ()) -> SqlPersistT m ()-splitDelete ts deleteF =-    forM_ vals $ E.delete . deleteF-  where-    vals = chunksOf paramLimit ts--splitInsertMany_ :: ( MonadIO m-                    , P.PersistEntity val-                    , P.PersistEntityBackend val ~ SqlBackend-                    )-                 => [val] -> SqlPersistT m ()-splitInsertMany_ = mapM_ P.insertMany_ . chunksOf paramLimit--limitOffset :: Word32 -> Word32 -> SqlQuery ()-limitOffset l o = do-    when (l > 0) $ limit  $ fromIntegral l-    when (o > 0) $ offset $ fromIntegral o-
− Network/Haskoin/Wallet/Types/BlockInfo.hs
@@ -1,77 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Network.Haskoin.Wallet.Types.BlockInfo-(-  BlockInfo(..)-, JsonHash256-, fromNodeBlock-)-where--import Data.String.Conversions  (cs, ConvertibleStrings(..))-import Data.Maybe               (fromMaybe)-import Data.Aeson.TH            (deriveJSON)-import Data.Word                (Word32)-import Data.Time                (UTCTime)-import Data.ByteString          (ByteString)-import Data.Time.Clock.POSIX    (posixSecondsToUTCTime)-import Data.Aeson               (FromJSON, ToJSON, Value (String),-                                 parseJSON, toJSON, withText)--import Network.Haskoin.Block-import Network.Haskoin.Node.HeaderTree-import Network.Haskoin.Crypto-import Network.Haskoin.Util---newtype JsonHash256 = JsonHash256 { jsonGetHash256 :: Hash256 }-    deriving (Eq, Show)--jsonHashToHex :: JsonHash256 -> ByteString-jsonHashToHex = blockHashToHex . BlockHash . jsonGetHash256--instance ToJSON JsonHash256 where-    toJSON = String . cs . jsonHashToHex--instance FromJSON JsonHash256 where-    parseJSON = withText "JsonHash256" $-        return . JsonHash256 . getBlockHash <$>-            fromMaybe (error "Invalid 256 bit hash") .-                hexToBlockHash . cs--instance ConvertibleStrings JsonHash256 String where-    convertString = cs . jsonHashToHex--data BlockInfo = BlockInfo-   { blockInfoHash          :: !BlockHash-   , blockInfoHeight        :: !BlockHeight-   , blockInfoVersion       :: !Word32-   , blockInfoTimestamp     :: !UTCTime-   , blockInfoPrevBlock     :: !BlockHash-   , blockInfoMerkleRoot    :: !JsonHash256-   , blockInfoBits          :: !Word32-   , blockInfoNonce         :: !Word32-   , blockInfoChain         :: !Word32-   , blockInfoChainWork     :: !Double-   } deriving (Eq, Show)--$(deriveJSON (dropFieldLabel 9) ''BlockInfo)--fromNodeBlock :: NodeBlock -> BlockInfo-fromNodeBlock nb =-    BlockInfo-       { blockInfoHash          =   headerHash header-       , blockInfoVersion       = blockVersion header-       , blockInfoPrevBlock     =    prevBlock header-       , blockInfoNonce         =      bhNonce header-       , blockInfoBits          =    blockBits header-       , blockInfoMerkleRoot    = JsonHash256 $ merkleRoot header-       , blockInfoTimestamp     = utcTimestamp-       , blockInfoChainWork     = nodeWork   nb-       , blockInfoHeight        = nodeHeight nb-       , blockInfoChain         = nodeChain  nb-       }-  where-    header = nodeHeader nb-    utcTimestamp = posixSecondsToUTCTime . realToFrac .-        blockTimestamp $ header-
app/Main.hs view
@@ -1,6 +1,4 @@-module Main where--import Network.Haskoin.Wallet.Client+import Haskoin.Wallet.Main  main :: IO () main = clientMain
− config/config.yml
@@ -1,118 +0,0 @@-# ZeroMQ socket on which to listen to. Either absolute path or relative to-# work-dir/network.-bind-socket:       ipc://hw.sock-bind-socket-notif: ipc://notif.sock--# ZeroMQ socket to communicate with the server.-# Either absolute path or relative to the server work-dir/network path.-connect-uri:       ipc://hw.sock-connect-uri-notif: ipc://notif.sock--# Server mode. Can be either online or offline. In offline mode, the SPV-# daemon does not start and only the local wallet is available to query.-server-mode: online--# False positive rate for the bloom filters.-bloom-false-positive: 0.00001--# Database connection information.-database:-    testnet:-        database: hw-wallet.sqlite3-        poolsize: 1-    prodnet:-        database: hw-wallet.sqlite3-        poolsize: 1--# List of trusted bitcoin full-nodes to connect to.-bitcoin-full-nodes:-    testnet:-        - host: testnet-seed.alexykot.me-          port: 18333-        - host: testnet-seed.bitcoin.petertodd.org-          port: 18333-        - host: testnet-seed.bluematt.me-          port: 18333-        - host: testnet-seed.bitcoin.schildbach.de-          port: 18333-    prodnet:-        - host: seed.bitcoin.sipa.be-          port: 8333-        - host: dnsseed.bluematt.me-          port: 8333-        - host: dnsseed.bitcoin.dashjr.org-          port: 8333-        - host: seed.bitcoinstats.com-          port: 8333-        - host: bitseed.xf2.org-          port: 8333-        - host: seed.bitcoin.jonasschnelli.ch-          port: 8333---# Log file name. Either absolute path or relative to work-dir/network-log-file: hw.log--# PID file name. Either absolute path or relative to work-dir/network.-pid-file: hw.pid--# Compile time configuration value. Either absolute path or relative to-# work-dir. Can only be set as environment variable.-config-file: config.yml--# Default output size for commands such as page sizes.-output-size: 10--# Type of addresses to display. Example: external, internal-address-type: external--# Use reverse paging for diplaying addresses and txs when set to True.-reverse-paging: false--# Sign new and imported transactions.-sign-transactions: true--# Default fee to pay (in satoshi) for every 1000 bytes.-transaction-fee: 10000--# Minimum number of confirmations for spending coins and displaying balances.-minimum-confirmations: 0--# Display the balance including offline transactions-offline: false--# How command-line output should be displayed. Supported values are:-# normal, json or yaml.-display-format: normal--# Detach the SPV server from the terminal when launched-detach-server: false--# Use Testnet3-use-testnet: false--# Haskoin working directory. Either absolute path or relative to user’s home.-# Defaults to an appropriate OS-specific value.-work-dir: ""--# Log level. Valid values are debug, info, warn and error.-log-level: info--# Print verbose-verbose: false--# Recipient pays transaction fee. DANGEROUS.-recipient-fee: false--# Configuration file name. Only set at compile time.-config-file: config.yml--# Server key for authentication and encryption (server config).-server-key:-# Server public key for authentication and encryption (client config).-server-key-public:--# Client key for authentication and encryption (client config).-client-key:-# Client public key for aunthentication and encryption (client + server config).-client-key-public:
− config/help
@@ -1,52 +0,0 @@-Server commands:-  start [--detach]                         Start the haskoin daemon-  stop                                     Stop the haskoin daemon-  status [--verbose]                       Display node runtime information--Account commands:-  newacc    name                           Create a new account-  newread   name                           Create a new read-only account-  newms     name M N                       Create a new multisig account-  newreadms name M N                       Create a new read-only ms account-  addkey    acc                            Add pubkeys to a multisig account-  setgap    acc gap                        Set the address gap for an account-  accounts  [page] [-c pagesize] [-r]      List all accounts in a keyring-  account   acc                            Display an account by name-  rename    old new                        Rename account--Address commands:-  list      acc [page] [-c pagesize] [-r]  Display addresses by page-  pubkeys   acc [page] [-c pagesize] [-r]  Display address public keys-  unused    acc [page] [-c pagesize] [-r]  Display unused addresses-  label     acc index label                Set the label of an address-  addrtxs   acc index [page] [-c pagesize] Display txs related to an address-  getindex  acc key                        Get key index by pubkey-  genaddrs  acc index [--internal]         Generate addresses up to this index--Transaction commands:-  txs       acc [page] [-c pagesize] [-r]  Display all transactions by page-  pending   acc [page] [-c pagesize] [-r]  Display pending transactions by page-  dead      acc [page] [-c pagesize] [-r]  Display dead transactions by page-  send      acc addr amount [-S]           Send coins to an address-  sendmany  acc {addr:amount...} [-S]      Send coins to many addresses-  import    acc                            Import a transaction (does not sign)-  sign      acc txid                       Sign one of your offline transactions-  balance   acc [--minconf] [--offline]    Display account balance-  gettx     acc txid                       Get a transaction by txid-  deletetx  txid                           Delete unconfirmed transaction-  sync      acc {hash|height} [page]       Get blocks following specified one--Offline tx signing commands:-  getoffline  acc txhash                   Get data to sign a tx offline-  signoffline acc tx coindata              Sign a tx with offline signing data--Utility commands:-  decodetx                                 Decode HEX transaction-  rescan   [timestamp]                     Rescan the wallet-  keypair                                  Get curve key pair for ØMQ auth-  monitor [account]                        Monitor events (no account: blocks)-  blockinfo [hash1] [hash2] [...]          Get block header information by hash--Other commands:-  version                                  Display version information-  help                                     Display this help information
− config/models
@@ -1,84 +0,0 @@-Account-    name Text maxlen=200-    type AccountType maxlen=64-    derivation HardPath Maybe maxlen=200-    master XPrvKey Maybe-    keys [XPubKey]-    gap Word32-    created UTCTime-    UniqueAccount name-    deriving Show--WalletAddr-    account AccountId-    address Address maxlen=64-    index KeyIndex-    type AddressType maxlen=16-    label Text-    redeem ScriptOutput Maybe-    key PubKeyC Maybe maxlen=66-    created UTCTime-    UniqueAddr account address-    UniqueAddrRev address account-    UniqueAddrIndex account type index-    deriving Show--WalletTx-    account AccountId-    hash TxHash maxlen=64-    nosigHash TxHash maxlen=64-    type TxType maxlen=16-    inValue Word64-    outValue Word64-    inputs [AddressInfo]-    outputs [AddressInfo]-    change [AddressInfo]-    tx Tx-    isCoinbase Bool-    confidence TxConfidence maxlen=16-    confirmedBy BlockHash Maybe maxlen=64-    confirmedHeight BlockHeight Maybe-    confirmedDate Timestamp Maybe-    created UTCTime-    UniqueAccTx account hash-    UniqueAccTxRev hash account-    UniqueAccNoSig account nosigHash-    UniqueAccNoSigRev nosigHash account-    deriving Show--WalletCoin-    account AccountId-    hash TxHash maxlen=64-    pos Word32-    tx WalletTxId-    addr WalletAddrId-    value Word64-    script ScriptOutput-    created UTCTime-    UniqueCoin account hash pos-    UniqueCoinRev hash pos account-    UniqueCoinTx tx pos-    UniqueCoinTxRev pos tx-    deriving Show--SpentCoin-    account AccountId-    hash TxHash maxlen=64-    pos Word32-    spendingTx WalletTxId-    created UTCTime-    UniqueSpentCoins account hash pos-    UniqueSpentCoinsRev hash pos account-    UniqueSpentTx spendingTx hash pos-    UniqueSpentTxRev hash pos spendingTx-    deriving Show--WalletState-    height BlockHeight-    block BlockHash maxlen=64-    bloomFilter BloomFilter-    bloomElems Int-    bloomFp Double-    version Int-    created UTCTime-    deriving Show
− examples/embedded-inproc-wallet-server/Main.hs
@@ -1,183 +0,0 @@-module Main where--import           Network.Haskoin.Wallet           (Config(..),-                                                   WalletRequest(..), WalletResponse(..),-                                                   AddressType(..),   OutputFormat(..),-                                                   SPVMode(..),       NodeAction(..))--import           Network.Haskoin.Wallet.Server    (runSPVServerWithContext)-import           Network.Haskoin.Wallet.Internals (BTCNode(..), Notif(..))-import qualified Network.Haskoin.Node.STM       as Node--import           Data.String.Conversions          (cs)-import qualified System.ZMQ4                    as ZMQ-import qualified Control.Monad.Logger           as Log-import qualified Data.HashMap.Strict            as HM-import qualified Database.Persist.Sqlite        as DB-import qualified Control.Monad.Trans.Resource   as Resource-import qualified Data.Aeson                     as JSON-import qualified Control.Concurrent             as Con-import qualified Data.Aeson.Encode.Pretty       as PrettyJSON-import qualified Control.Monad                  as M-import qualified Control.Exception              as Except---databaseConf :: DB.SqliteConf-databaseConf = DB.SqliteConf "/tmp/tmpdb" 1--cmdSocket :: String-cmdSocket = "inproc://cmd"--notifSocket :: String-notifSocket = "inproc://notif"----- |Simple example app that embeds a haskoin-wallet server.---  Start wallet server + notification thread, and execute Status command when pressing ENTER-main :: IO ()-main = ZMQ.withContext $ \ctx -> do-    -- Server-    putStrLn "Starting server..."-    _ <- Con.forkIO $ runWallet walletServerConf ctx-    -- Notify thread-    putStrLn "Starting notification thread..."-    _ <- Con.forkIO $ notifyThread ctx notifyHandler-    -- Status loop-    M.forever $ do-        putStrLn "Press ENTER to get server status..."-        _ <- getLine-        cmdGetStatus ctx >>= printStatusJSON-  where-    printStatusJSON     = putStrLn . cs . PrettyJSON.encodePretty-    notifyHandler notif =-        putStrLn $ "NOTIFY: New block: " ++ cs (PrettyJSON.encodePretty notif)---- |Run haskoin-wallet using the specified ZeroMQ Context,---  and log to stderr.-runWallet :: Config -> ZMQ.Context -> IO ()-runWallet cfg ctx = run $ runSPVServerWithContext cfg ctx-    where run           = Resource.runResourceT . runLogging-          runLogging    = Log.runStderrLoggingT . Log.filterLogger logFilter-          logFilter _ l = l >= configLogLevel cfg--cmdGetStatus :: ZMQ.Context -> IO Node.NodeStatus-cmdGetStatus ctx =-    sendCmdOrFail (PostNodeR NodeActionStatus) ctx >>=-    \res -> case res of-        Nothing     -> error "ERROR: Status command: no response."-        Just status -> return status--sendCmdOrFail :: (JSON.FromJSON a, JSON.ToJSON a)-              => WalletRequest-              -> ZMQ.Context-              -> IO (Maybe a)-sendCmdOrFail cmd ctx =-    sendCmd cmd ctx >>=-    either error return >>=-    \res -> case res of-        ResponseError e -> error $ "ERROR: Send cmd, ResponseError: " ++ cs e-        ResponseValid r -> return r--sendCmd :: (JSON.FromJSON a, JSON.ToJSON a)-        => WalletRequest-        -> ZMQ.Context-        -> IO (Either String (WalletResponse a))-sendCmd req ctx =-    ZMQ.withSocket ctx ZMQ.Req $ \sock -> do-        ZMQ.setLinger (ZMQ.restrict (0 :: Int)) sock-        ZMQ.connect sock cmdSocket-        ZMQ.send sock [] (cs $ JSON.encode req)-        JSON.eitherDecode . cs <$> ZMQ.receive sock---- |Connect to notify socket, subscribe to new blocks,---  and execute the supplied handler for each new block as it arrives.-notifyThread :: ZMQ.Context -> (Notif -> IO ()) -> IO ()-notifyThread ctx handler = waitAndCatch $-    ZMQ.withSocket ctx ZMQ.Sub $ \sock -> do-        ZMQ.setLinger (ZMQ.restrict (0 :: Int)) sock-        ZMQ.connect sock notifSocket-        ZMQ.subscribe sock "[block]"-        putStrLn "NOTIFY: Connected. Subscribed to new blocks."-        M.forever $ do-            [_,m] <- ZMQ.receiveMulti sock-            notif <- either failOnErr return $ JSON.eitherDecode (cs m)-            handler notif-  where-    failOnErr = fail . ("NOTIFY: ERROR: recv failed: " ++)-    waitAndCatch ioa = Con.threadDelay 10000 >> ioa `Except.finally` waitAndCatch ioa--btcNodes :: [BTCNode]-btcNodes =-    [ BTCNode "dnsseed.bluematt.me"             8333-    , BTCNode "dnsseed.bitcoin.dashjr.org"      8333-    , BTCNode "dnsseed.bluematt.me"             8333-    , BTCNode "seed.bitcoinstats.com"           8333-    , BTCNode "seed.bitcoin.jonasschnelli.ch"   8333-    , BTCNode "seed.bitcoin.sipa.be"            8333-    , BTCNode "seed.bitnodes.io"                8333-    , BTCNode "seed.btcc.com"                   8333-    ]--walletServerConf :: Config-walletServerConf = Config-    { configCount         = 100-    -- ^ Output size of commands-    , configMinConf       = 6-    -- ^ Minimum number of confirmations-    , configSignTx        = True-    -- ^ Sign transactions-    , configFee           = 50000-    -- ^ Fee to pay per 1000 bytes when creating new transactions-    , configRcptFee       = False-    -- ^ Recipient pays fee (dangerous, no config file setting)-    , configAddrType      = AddressExternal-    -- ^ Return internal instead of external addresses-    , configOffline       = False-    -- ^ Display the balance including offline transactions-    , configReversePaging = False-    -- ^ Use reverse paging for displaying addresses and transactions-    , configPath          = Nothing-    -- ^ Derivation path when creating account-    , configFormat        = OutputNormal-    -- ^ How to format the command-line results-    , configConnect       = cmdSocket-    -- ^ ZeroMQ socket to connect to (location of the server)-    , configConnectNotif  = notifSocket-    -- ^ ZeroMQ socket to connect for notifications-    , configDetach        = False-    -- ^ Detach server when launched from command-line-    , configFile          = ""-    -- ^ Configuration file-    , configTestnet       = False-    -- ^ Use Testnet3 network-    , configDir           = ""-    -- ^ Working directory-    , configBind          = cmdSocket-    -- ^ Bind address for the ZeroMQ socket-    , configBindNotif     = notifSocket-    -- ^ Bind address for ZeroMQ notifications-    , configBTCNodes      = HM.fromList [ ( "prodnet", btcNodes ) ]-    -- ^ Trusted Bitcoin full nodes to connect to-    , configMode          = SPVOnline-    -- ^ Operation mode of the SPV node.-    , configBloomFP       = 0.00001-    -- ^ False positive rate for the bloom filter.-    , configDatabase      = HM.fromList [ ( "prodnet", databaseConf ) ]-    -- ^ Database configuration-    , configLogFile       = ""-    -- ^ Log file-    , configPidFile       = ""-    -- ^ PID File-    , configLogLevel      = Log.LevelInfo-    -- ^ Log level-    , configVerbose       = True-    -- ^ Verbose-    , configServerKey     = Nothing-    -- ^ Server key for authentication and encryption (server config)-    , configServerKeyPub  = Nothing-    -- ^ Server public key for authentication and encryption (client config)-    , configClientKey     = Nothing-    -- ^ Client key for authentication and encryption (client config)-    , configClientKeyPub  = Nothing-    -- ^ Client public key for authentication and encryption-    }
haskoin-wallet.cabal view
@@ -1,180 +1,195 @@-name:                  haskoin-wallet-version:               0.4.2-synopsis:-    Implementation of a Bitcoin SPV Wallet with BIP32 and multisig support.-description:-    This package provides a SPV (simple payment verification) wallet-    implementation. It features BIP32 key management, deterministic signatures-    (RFC-6979) and first order support for multi-signature transactions. You-    can communicate with the wallet process through a ZeroMQ API or through a-    command-line tool called "hw" which is also provided in this package.-homepage:              http://github.com/haskoin/haskoin-bug-reports:           http://github.com/haskoin/haskoin/issues-tested-with:           GHC==8.0.2-license:               PublicDomain-license-file:          UNLICENSE-author:                Philippe Laprade, Jean-Pierre Rupp-maintainer:            xenog@protonmail.com-category:              Bitcoin, Finance, Network-build-type:            Simple-cabal-version:         >= 1.9.2-extra-source-files:    config/help, config/config.yml, config/models--source-repository head-    type:     git-    location: git://github.com/haskoin/haskoin.git--Flag library-only-    Description:   Do not build the executables-    Default:       False--library-    exposed-modules: Network.Haskoin.Wallet-                     Network.Haskoin.Wallet.Model-                     Network.Haskoin.Wallet.Client-                     Network.Haskoin.Wallet.Server-                     Network.Haskoin.Wallet.Settings-                     Network.Haskoin.Wallet.Internals+cabal-version: 1.12 -    other-modules: Network.Haskoin.Wallet.Types-                   Network.Haskoin.Wallet.Types.BlockInfo-                   Network.Haskoin.Wallet.Accounts-                   Network.Haskoin.Wallet.Transaction-                   Network.Haskoin.Wallet.Block-                   Network.Haskoin.Wallet.Server.Handler-                   Network.Haskoin.Wallet.Client.Commands-                   Network.Haskoin.Wallet.Client.PrettyJson-                   Network.Haskoin.Wallet.Database+-- This file has been generated from package.yaml by hpack version 0.38.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 3e39505c3793edd3fb6ec57bd710521a7ac480b59144d1b82744f777acfbf6d8 -    extensions: TemplateHaskell-                QuasiQuotes-                OverloadedStrings-                MultiParamTypeClasses-                TypeFamilies-                GADTs-                FlexibleContexts-                FlexibleInstances-                EmptyDataDecls-                DeriveDataTypeable-                RecordWildCards-                GeneralizedNewtypeDeriving+name:           haskoin-wallet+version:        0.9.4+synopsis:       Lightweight CLI wallet for Bitcoin and Bitcoin Cash+description:    Lightweight command-line interface offline-friendly hierarchical-deterministic BTC/BCH wallet compatible with BIP44 and BIP39.+category:       Bitcoin, Finance, Network+homepage:       http://github.com/haskoin/haskoin-wallet+bug-reports:    http://github.com/haskoin/haskoin-wallet/issues+author:         JP Rupp+maintainer:     jprupp@protonmail.ch+license:        PublicDomain+license-file:   UNLICENSE+build-type:     Simple -    build-depends: aeson                         >= 0.7       && < 1.1-                 , aeson-pretty                  >= 0.7       && < 0.9-                 , base                          >= 4.8       && < 5-                 , bytestring                    >= 0.10      && < 0.11-                 , cereal                        >= 0.5       && < 0.6-                 , containers                    >= 0.5       && < 0.6-                 , conduit                       >= 1.2       && < 1.3-                 , deepseq                       >= 1.4       && < 1.5-                 , data-default                  >= 0.5       && < 0.8-                 , directory                     >= 1.2       && < 1.4-                 , daemons                       >= 0.2       && < 0.3-                 , exceptions                    >= 0.6       && < 0.9-                 , esqueleto                     >= 2.4       && < 2.6-                 , file-embed                    >= 0.0       && < 0.1-                 , filepath                      >= 1.4       && < 1.5-                 , haskeline-                 , haskoin-core                  >= 0.3       && < 0.5-                 , haskoin-node                  >= 0.3       && < 0.5-                 , lifted-async                  >= 0.2       && < 0.10-                 , lifted-base                   >= 0.2       && < 0.3-                 , monad-logger                  >= 0.3.13    && < 0.4-                 , monad-control                 >= 1.0       && < 1.1-                 , mtl                           >= 2.1       && < 2.3-                 , persistent                    >= 2.2       && < 2.7-                 , persistent-template           >= 2.1       && < 2.6-                 , persistent-sqlite             >= 2.2       && < 2.7-                 , resourcet                     >= 1.1       && < 1.2-                 , semigroups-                 , split                         >= 0.2       && < 0.3-                 , stm                           >= 2.4       && < 2.5-                 , stm-chans                     >= 3.0       && < 3.1-                 , stm-conduit                   >= 2.6       && < 3.1-                 , string-conversions            >= 0.4       && < 0.5-                 , text                          >= 0.11      && < 1.3-                 , time                          >= 1.5       && < 1.7-                 , transformers-base             >= 0.4       && < 0.5-                 , unix                          >= 2.6       && < 2.8-                 , unordered-containers          >= 0.2       && < 0.3-                 , yaml                          >= 0.8       && < 0.9-                 , zeromq4-haskell               >= 0.6       && < 0.7+source-repository head+  type: git+  location: https://github.com/haskoin/haskoin-wallet.git -    ghc-options: -Wall+library+  hs-source-dirs:+      src+  ghc-options: -Wall+  build-depends:+      Decimal >=0.5.1+    , aeson >=1.4.6.0+    , aeson-pretty >=0.8.8+    , ansi-terminal >=1.0+    , base >=4.9 && <5+    , base16-bytestring >=1.0.0.0+    , base64-bytestring >=1.0.0.3+    , bytestring >=0.10.10.0+    , cereal >=0.5.8.1+    , conduit >=1.3.5+    , containers >=0.6.2.1+    , data-default >=0.7.1.1+    , directory >=1.3.6.0+    , entropy >=0.4.1.6+    , esqueleto >=3.5.10.1+    , haskeline >=0.7.5.0+    , haskoin-core >=1.0.4+    , haskoin-store-data >=1.2.2+    , http-types >=0.12.3+    , lens >=4.18.1+    , lens-aeson >=1.1+    , monad-logger >=0.3.40+    , mtl >=2.2.2+    , optparse-applicative >=0.15.1.0+    , persistent >=2.14.5.1+    , persistent-sqlite >=2.13.1.1+    , pretty >=1.1.3.6+    , random >=1.1+    , raw-strings-qq >=1.1+    , secp256k1-haskell >=1.1.0+    , split >=0.2.3.5+    , string-conversions >=0.4.0.1+    , text >=1.2.4.0+    , time >=1.12.2+    , transformers >=0.5.6.2+    , unordered-containers >=0.2.10.0+    , wreq >=0.5.3.2+  exposed-modules:+      Haskoin.Wallet+      Haskoin.Wallet.Amounts+      Haskoin.Wallet.Backup+      Haskoin.Wallet.Commands+      Haskoin.Wallet.Config+      Haskoin.Wallet.Database+      Haskoin.Wallet.Entropy+      Haskoin.Wallet.FileIO+      Haskoin.Wallet.Main+      Haskoin.Wallet.Migration+      Haskoin.Wallet.Migration.SemVersion+      Haskoin.Wallet.Migration.V0_9_0+      Haskoin.Wallet.Parser+      Haskoin.Wallet.PrettyPrinter+      Haskoin.Wallet.Signing+      Haskoin.Wallet.TxInfo+      Haskoin.Wallet.Util+  other-modules:+      Paths_haskoin_wallet+  default-language: Haskell2010  executable hw-    if flag(library-only)-        Buildable: False-    main-is: Main.hs-    build-depends: base, haskoin-wallet-    hs-source-dirs: app-    ghc-options: -Wall-                 -O3-                 -threaded-                 -eventlog-                 -rtsopts-                 -with-rtsopts=-N4--test-suite test-haskoin-wallet-    type: exitcode-stdio-1.0-    main-is: Main.hs--    other-modules: Network.Haskoin.Wallet.Arbitrary-                 , Network.Haskoin.Wallet.Tests-                 , Network.Haskoin.Wallet.Units --    extensions: RecordWildCards-                OverloadedStrings--    build-depends: aeson                         >= 0.7       && < 1.1-                 , base                          >= 4.8       && < 5-                 , bytestring                    >= 0.10      && < 0.11-                 , containers                    >= 0.5       && < 0.6-                 , directory                     >= 1.2       && < 1.4-                 , haskoin-core                  >= 0.3       && < 0.5-                 , haskoin-node                  >= 0.3       && < 0.5-                 , haskoin-wallet-                 , monad-logger                  >= 0.3       && < 0.4-                 , mtl                           >= 2.1       && < 2.3-                 , persistent                    >= 2.2       && < 2.7-                 , persistent-sqlite             >= 2.2       && < 2.7-                 , resourcet                     >= 1.1       && < 1.2-                 , text                          >= 0.11      && < 1.3-                 , unordered-containers          >= 0.2       && < 0.3-                 , HUnit                         >= 1.2       && < 1.6-                 , QuickCheck                    >= 2.8       && < 2.10-                 , stm                           >= 2.4       && < 2.5-                 , stm-chans                     >= 3.0       && < 3.1-                 , string-conversions            >= 0.4       && < 0.5-                 , test-framework                >= 0.8       && < 0.9-                 , test-framework-quickcheck2    >= 0.3       && < 0.4-                 , test-framework-hunit          >= 0.3       && < 0.4--    hs-source-dirs: tests-    ghc-options: -Wall--executable example-inproc-wallet-server-    if flag(library-only)-        Buildable: False--    main-is:        Main.hs-    hs-source-dirs: examples/embedded-inproc-wallet-server/-    extensions:     OverloadedStrings--    ghc-options: -Wall-                 -O3-                 -threaded-                 -rtsopts-                 -with-rtsopts=-N4+  main-is: Main.hs+  hs-source-dirs:+      app+  ghc-options: -Wall+  build-depends:+      Decimal >=0.5.1+    , aeson >=1.4.6.0+    , aeson-pretty >=0.8.8+    , ansi-terminal >=1.0+    , base >=4.9 && <5+    , base16-bytestring >=1.0.0.0+    , base64-bytestring >=1.0.0.3+    , bytestring >=0.10.10.0+    , cereal >=0.5.8.1+    , conduit >=1.3.5+    , containers >=0.6.2.1+    , data-default >=0.7.1.1+    , directory >=1.3.6.0+    , entropy >=0.4.1.6+    , esqueleto >=3.5.10.1+    , haskeline >=0.7.5.0+    , haskoin-core >=1.0.4+    , haskoin-store-data >=1.2.2+    , haskoin-wallet+    , http-types >=0.12.3+    , lens >=4.18.1+    , lens-aeson >=1.1+    , monad-logger >=0.3.40+    , mtl >=2.2.2+    , optparse-applicative >=0.15.1.0+    , persistent >=2.14.5.1+    , persistent-sqlite >=2.13.1.1+    , pretty >=1.1.3.6+    , random >=1.1+    , raw-strings-qq >=1.1+    , secp256k1-haskell >=1.1.0+    , split >=0.2.3.5+    , string-conversions >=0.4.0.1+    , text >=1.2.4.0+    , time >=1.12.2+    , transformers >=0.5.6.2+    , unordered-containers >=0.2.10.0+    , wreq >=0.5.3.2+  other-modules:+      Paths_haskoin_wallet+  default-language: Haskell2010 -    build-depends:     base                          >= 4.8       && < 5-                     , aeson                         >= 0.7       && < 1.1-                     , aeson-pretty                  >= 0.7       && < 0.9-                     , haskoin-node                  >= 0.3       && < 0.5-                     , haskoin-wallet-                     , monad-logger                  >= 0.3       && < 0.4-                     , persistent-sqlite             >= 2.2       && < 2.7-                     , resourcet                     >= 1.1       && < 1.2-                     , unordered-containers          >= 0.2       && < 0.3-                     , string-conversions            >= 0.4       && < 0.5-                     , zeromq4-haskell               >= 0.6       && < 0.7+test-suite spec+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Haskoin.Wallet.AmountsSpec+      Haskoin.Wallet.CommandsSpec+      Haskoin.Wallet.EntropySpec+      Haskoin.Wallet.SigningSpec+      Haskoin.Wallet.TestUtils+      Haskoin.Wallet.VersionSpec+      Paths_haskoin_wallet+  hs-source-dirs:+      test+  ghc-options: -Wall+  build-depends:+      Decimal >=0.5.1+    , HUnit >=1.6.0.0+    , QuickCheck >=2.13.2+    , aeson >=1.4.6.0+    , aeson-pretty >=0.8.8+    , ansi-terminal >=1.0+    , base >=4.9 && <5+    , base16-bytestring >=1.0.0.0+    , base64-bytestring >=1.0.0.3+    , bytestring >=0.10.10.0+    , cereal >=0.5.8.1+    , conduit >=1.3.5+    , containers >=0.6.2.1+    , data-default >=0.7.1.1+    , directory >=1.3.6.0+    , entropy >=0.4.1.6+    , esqueleto >=3.5.10.1+    , haskeline >=0.7.5.0+    , haskoin-core >=1.0.4+    , haskoin-store-data >=1.2.2+    , haskoin-wallet+    , hspec >=2.7.1+    , http-types >=0.12.3+    , lens >=4.18.1+    , lens-aeson >=1.1+    , monad-logger >=0.3.40+    , mtl >=2.2.2+    , optparse-applicative >=0.15.1.0+    , persistent >=2.14.5.1+    , persistent-sqlite >=2.13.1.1+    , pretty >=1.1.3.6+    , random >=1.1+    , raw-strings-qq >=1.1+    , secp256k1-haskell >=1.1.0+    , split >=0.2.3.5+    , string-conversions >=0.4.0.1+    , text >=1.2.4.0+    , time >=1.12.2+    , transformers >=0.5.6.2+    , unordered-containers >=0.2.10.0+    , wreq >=0.5.3.2+  default-language: Haskell2010+  build-tool-depends: hspec-discover:hspec-discover
+ src/Haskoin/Wallet.hs view
@@ -0,0 +1,24 @@+module Haskoin.Wallet+  ( module Amounts,+    module Commands,+    module Config,+    module Database,+    module Entropy,+    module FileIO,+    module Parser,+    module Signing,+    module TxInfo,+    module Util,+  )+where++import Haskoin.Wallet.Amounts as Amounts+import Haskoin.Wallet.Commands as Commands+import Haskoin.Wallet.Config as Config+import Haskoin.Wallet.Database as Database+import Haskoin.Wallet.Entropy as Entropy+import Haskoin.Wallet.FileIO as FileIO+import Haskoin.Wallet.Parser as Parser+import Haskoin.Wallet.Signing as Signing+import Haskoin.Wallet.TxInfo as TxInfo+import Haskoin.Wallet.Util as Util
+ src/Haskoin/Wallet/Amounts.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Haskoin.Wallet.Amounts where++import Control.Arrow (second)+import Control.Monad (guard)+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Text.Read as Read+import Haskoin.Wallet.Util+import Numeric.Natural (Natural)++data AmountUnit+  = UnitBitcoin+  | UnitBit+  | UnitSatoshi+  deriving (Eq, Show)++-- Amount Parsing --++showUnit :: AmountUnit -> Integer -> Text+showUnit unit amnt+  | unit == UnitSatoshi = strUnit -- satoshi is always singular+  | abs amnt == 1 = strUnit+  | otherwise = strUnit <> "s" -- plural form bitcoins and bits+  where+    strUnit =+      case unit of+        UnitBitcoin -> "bitcoin"+        UnitBit -> "bit"+        UnitSatoshi -> "satoshi"++showAmount :: AmountUnit -> Natural -> Text+showAmount unit amnt =+  case unit of+    UnitBitcoin ->+      let (q, r) = amnt `divMod` 100000000+       in addSep (showT q) <> "." <> removeEnd (padStart 8 "0" (showT r))+    UnitBit ->+      let (q, r) = amnt `divMod` 100+       in addSep (showT q) <> "." <> padStart 2 "0" (showT r)+    UnitSatoshi -> addSep (showT amnt)+  where+    removeEnd = dropPatternEnd "0000" . dropPatternEnd "000000"+    addSep = Text.intercalate "'" . chunksOfEnd 3+    showT = Text.pack . show++readAmount :: AmountUnit -> Text -> Maybe Natural+readAmount unit amntStr =+  case unit of+    UnitBitcoin -> do+      guard $ Text.length r <= 8+      a <- readNatural q+      b <- readNatural $ padEnd 8 "0" r+      return $ a * 100000000 + b+    UnitBit -> do+      guard $ Text.length r <= 2+      a <- readNatural q+      b <- readNatural $ padEnd 2 "0" r+      return $ a * 100 + b+    UnitSatoshi -> readNatural str+  where+    str = dropAmountSep amntStr+    (q, r) = second (Text.drop 1) $ Text.breakOn "." str++readNatural :: Text -> Maybe Natural+readNatural txt =+  case Read.decimal txt of+    Right (res, "") -> Just res+    _ -> Nothing++dropAmountSep :: Text -> Text+dropAmountSep = Text.filter (`notElem` [' ', '_', '\''])++-- | Like 'showAmount' but will display a minus sign for negative amounts+showIntegerAmount :: AmountUnit -> Integer -> Text+showIntegerAmount unit i+  | i < 0 = "-" <> showAmount unit (fromIntegral $ abs i)+  | otherwise = showAmount unit $ fromIntegral i++-- | Like 'readAmount' but can parse a negative amount+readIntegerAmount :: AmountUnit -> Text -> Maybe Integer+readIntegerAmount unit txt =+  case Text.uncons txt of+    Just ('-', rest) -> negate . toInteger <$> readAmount unit rest+    _ -> toInteger <$> readAmount unit txt
+ src/Haskoin/Wallet/Backup.hs view
@@ -0,0 +1,358 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Haskoin.Wallet.Backup where++import Conduit (MonadUnliftIO)+import Control.Monad+import Control.Monad.Except+import Control.Monad.Reader (MonadIO (..), MonadTrans (lift))+import Data.Aeson (object, (.:), (.=))+import qualified Data.Aeson as Json+import Data.Default (def)+import Data.List (nub, sort, (\\))+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Maybe (fromJust)+import qualified Data.Serialize as S+import Data.Text (Text)+import Data.Time (UTCTime)+import Database.Esqueleto.Legacy as E+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Store.WebClient+import Haskoin.Wallet.Config+import Haskoin.Wallet.Database+import Haskoin.Wallet.TxInfo+import Haskoin.Wallet.Util+import Numeric.Natural (Natural)++newtype WalletBackup = WalletBackup [AccountBackup]+  deriving (Eq, Show)++instance MarshalJSON Ctx WalletBackup where+  marshalValue ctx (WalletBackup accs) =+    object ["accounts" .= (marshalValue ctx <$> accs)]+  unmarshalValue ctx =+    Json.withObject "WalletBackup" $ \o -> do+      accs <- mapM (unmarshalValue ctx) =<< o .: "accounts"+      return $ WalletBackup accs++data AccountBackup = AccountBackup+  { accountBackupName :: !Text,+    accountBackupWallet :: !Fingerprint,+    accountBackupPubKey :: !XPubKey,+    accountBackupNetwork :: !Network,+    accountBackupExternal :: !Natural,+    accountBackupInternal :: !Natural,+    accountBackupLabels :: Map Address Text,+    accountBackupFree :: [Address],+    accountBackupCreated :: !UTCTime+  }+  deriving (Eq, Show)++instance MarshalJSON Ctx AccountBackup where+  marshalValue ctx AccountBackup {..} =+    let net = accountBackupNetwork+        f = Map.mapKeys (fromJust . addrToText net)+     in object+          [ "name" .= accountBackupName,+            "wallet" .= accountBackupWallet,+            "xpubkey"+              .= marshalValue (accountBackupNetwork, ctx) accountBackupPubKey,+            "network" .= accountBackupNetwork.name,+            "external" .= accountBackupExternal,+            "internal" .= accountBackupInternal,+            "labels" .= f accountBackupLabels,+            "free" .= (fromJust . addrToText net <$> accountBackupFree),+            "created" .= accountBackupCreated+          ]+  unmarshalValue ctx =+    Json.withObject "AccountBackup" $ \o -> do+      net <- maybe mzero pure . netByName =<< o .: "network"+      let f = Map.mapKeys (fromJust . textToAddr net)+      AccountBackup+        <$> o .: "name"+        <*> o .: "wallet"+        <*> (unmarshalValue (net, ctx) =<< o .: "xpubkey")+        <*> pure net+        <*> o .: "external"+        <*> o .: "internal"+        <*> (f <$> o .: "labels")+        <*> ((fromJust . textToAddr net <$>) <$> o .: "free")+        <*> o .: "created"++createBackup :: (MonadUnliftIO m) => Ctx -> ExceptT String (DB m) WalletBackup+createBackup ctx = do+  accs <- lift getAccounts+  backs <-+    forM accs $ \(_, acc@DBAccount {..}) -> do+      let net = accountNetwork acc+          (DBWalletKey fpT) = dBAccountWallet+      fp <- liftEither $ textToFingerprint fpT+      labelVals <-+        lift . select . from $ \a -> do+          where_ $+            a ^. DBAddressAccountWallet ==. val dBAccountWallet+              &&. a ^. DBAddressAccountDerivation ==. val dBAccountDerivation+              &&. a ^. DBAddressInternal ==. val False+              &&. a ^. DBAddressLabel !=. val ""+          return (a ^. DBAddressAddress, a ^. DBAddressLabel)+      labels <- forM labelVals $ \(Value at, Value l) -> do+        a <- liftMaybe "Address" $ textToAddr net at+        return (a, l)+      freeVals <-+        lift . select . from $ \a -> do+          where_ $+            a ^. DBAddressAccountWallet ==. val dBAccountWallet+              &&. a ^. DBAddressAccountDerivation ==. val dBAccountDerivation+              &&. a ^. DBAddressInternal ==. val True+              &&. a ^. DBAddressFree ==. val True+          return $ a ^. DBAddressAddress+      free <- mapM (liftMaybe "Address" . textToAddr net . unValue) freeVals+      return+        AccountBackup+          { accountBackupName = dBAccountName,+            accountBackupWallet = fp,+            accountBackupPubKey = accountXPubKey ctx acc,+            accountBackupNetwork = accountNetwork acc,+            accountBackupExternal = fromIntegral dBAccountExternal,+            accountBackupInternal = fromIntegral dBAccountInternal,+            accountBackupLabels = Map.fromList labels,+            accountBackupFree = free,+            accountBackupCreated = dBAccountCreated+          }+  return $ WalletBackup backs++data SyncRes = SyncRes+  { syncResAccount :: !DBAccount,+    syncResBlockHash :: !BlockHash,+    syncResBlockHeight :: !BlockHeight,+    syncResTxUpdates :: !Natural,+    syncResCoinUpdates :: !Natural+  }+  deriving (Eq, Show)++restoreBackup ::+  (MonadUnliftIO m) =>+  Ctx ->+  Config ->+  WalletBackup ->+  ExceptT String (DB m) [SyncRes]+restoreBackup ctx cfg (WalletBackup accs) =+  mapM (restoreAccount ctx cfg) accs++restoreAccount ::+  (MonadUnliftIO m) =>+  Ctx ->+  Config ->+  AccountBackup ->+  ExceptT String (DB m) SyncRes+restoreAccount ctx cfg AccountBackup {..} = do+  -- Insert the account+  (accId, acc) <-+    insertAccount+      accountBackupNetwork+      ctx+      accountBackupWallet+      accountBackupName+      accountBackupPubKey+  let net = accountBackupNetwork+  -- Set the external and internal derivation indices+  (e, i) <- discoverAddrs net ctx cfg accountBackupPubKey+  let idxE = max e accountBackupExternal+      idxI = max i accountBackupInternal+  discoverAccGenAddrs ctx cfg accId AddrExternal $ fromIntegral idxE+  discoverAccGenAddrs ctx cfg accId AddrInternal $ fromIntegral idxI+  -- Perform an account sync+  syncRes <- sync ctx cfg net accId True+  -- Set address labels+  forM_ (Map.assocs accountBackupLabels) $ \(addr, l) -> do+    at <- liftMaybe "Address" $ addrToText net addr+    lift . update $ \a -> do+      set a [DBAddressLabel =. val l]+      where_ $ a ^. DBAddressAddress ==. val at+  -- Set Free internal addresses+  addrsT <- mapM (liftMaybe "Address" . addrToText net) accountBackupFree+  _ <- lift $ setAddrsFree AddrFree addrsT+  -- Set the creation time+  lift . update $ \a -> do+    set a [DBAccountCreated =. val accountBackupCreated]+    where_ $+      a ^. DBAccountWallet ==. val (dBAccountWallet acc)+        &&. a ^. DBAccountDerivation ==. val (dBAccountDerivation acc)+  acc' <- getAccountById accId+  return syncRes {syncResAccount = acc'}++discoverAddrs ::+  (MonadIO m) =>+  Network ->+  Ctx ->+  Config ->+  XPubKey ->+  ExceptT String m (Natural, Natural)+discoverAddrs net ctx cfg pub = do+  let recoveryGap = configRecoveryGap cfg+  e <- go extDeriv 0 (Page recoveryGap 0)+  i <- go intDeriv 0 (Page recoveryGap 0)+  return (fromIntegral e, fromIntegral i)+  where+    go path d page@(Page lim off) = do+      let addrs = addrsDerivPage ctx path page pub+          req = GetAddrsBalance $ fst <$> addrs+      let host = apiHost net cfg+      Store.SerialList bals <- liftExcept $ apiCall ctx host req+      let vBals = filter ((/= 0) . (.txs)) bals+      if null vBals+        then return d+        else do+          let dMax = findMax addrs $ (.address) <$> vBals+          go path (dMax + 1) (Page lim (off + lim))+    -- Find the largest ID amongst the addresses that have a positive balance+    findMax :: [(Address, SoftPath)] -> [Address] -> Int+    findMax addrs balAddrs =+      let fAddrs = filter ((`elem` balAddrs) . fst) addrs+       in fromIntegral $ maximum $ last . pathToList . snd <$> fAddrs++sync ::+  (MonadUnliftIO m) =>+  Ctx ->+  Config ->+  Network ->+  DBAccountId ->+  Bool ->+  ExceptT String (DB m) SyncRes+sync ctx cfg net accId full = do+  let host = apiHost net cfg+  -- Check API health+  checkHealth ctx net cfg+  -- Get the new best block before starting the sync+  best <- liftExcept $ apiCall ctx host (GetBlockBest def)+  -- Get the addresses from our local database+  (addrPathMap, addrBalMap) <- allAddressesMap net accId+  -- Fetch the address balances online+  Store.SerialList storeBals <-+    liftExcept . apiBatch ctx (configAddrBatch cfg) host $+      GetAddrsBalance (Map.keys addrBalMap)+  -- Filter only those addresses whose balances have changed+  balsToUpdate <-+    if full+      then return storeBals+      else liftEither $ filterAddresses storeBals addrBalMap+  let addrsToUpdate = (.address) <$> balsToUpdate+  -- Update balances+  updateAddressBalances net balsToUpdate+  newAcc <- lift $ updateAccountBalances accId+  -- Get a list of our confirmed txs in the local database+  -- Use an empty list when doing a full sync+  confirmedTxs <- if full then return [] else getConfirmedTxs accId True+  -- Fetch the txids of the addresses to update+  aTids <- searchAddrTxs net ctx cfg confirmedTxs addrsToUpdate+  -- We also want to check if there is any change in unconfirmed txs+  uTids <- getConfirmedTxs accId False+  let tids = nub $ uTids <> aTids+  -- Fetch the full transactions+  Store.SerialList txs <-+    liftExcept $ apiBatch ctx (configTxFullBatch cfg) host (GetTxs tids)+  -- Convert them to TxInfo and store them in the local database+  let txInfos = storeToTxInfo addrPathMap (fromIntegral best.height) <$> txs+  resTxInfo <- forM txInfos $ repsertTxInfo net ctx accId+  -- Fetch and update coins+  Store.SerialList storeCoins <-+    liftExcept . apiBatch ctx (configCoinBatch cfg) host $+      GetAddrsUnspent addrsToUpdate def+  (coinCount, newCoins) <- refreshCoins net accId addrsToUpdate storeCoins+  -- Get the dependent tranactions of the new coins+  depTxsHash <-+    if full+      then return $ (.outpoint.hash) <$> storeCoins+      else mapM (liftEither . coinToTxHash) newCoins+  Store.RawResultList rawTxs <-+    liftExcept+      . apiBatch ctx (configTxFullBatch cfg) host+      $ GetTxsRaw+      $ nub depTxsHash+  lift $ forM_ rawTxs insertRawTx+  -- Remove pending transactions if they are online+  pendingTids <- pendingTxHashes accId+  let toRemove = filter ((`elem` tids) . fst) pendingTids+  forM_ toRemove $ \(_, key) -> lift $ deletePendingTxOnline key+  -- Update the best block for this network+  lift $ updateBest net (headerHash best.header) best.height+  return $+    SyncRes+      newAcc+      (headerHash best.header)+      (fromIntegral best.height)+      (fromIntegral $ length $ filter id $ snd <$> resTxInfo)+      (fromIntegral coinCount)++-- Filter addresses that need to be updated+filterAddresses ::+  [Store.Balance] ->+  Map Address AddressBalance ->+  Either String [Store.Balance]+filterAddresses sBals aMap+  | sort ((.address) <$> sBals) /= sort (Map.keys aMap) =+      Left "Sync: addresses do not match"+  | otherwise =+      Right $ filter f sBals+  where+    f s =+      let b = fromJust $ s.address `Map.lookup` aMap+       in s.txs /= addrBalanceTxs b+            || s.confirmed /= addrBalanceConfirmed b+            || s.unconfirmed /= addrBalanceUnconfirmed b+            || s.utxo /= addrBalanceCoins b++searchAddrTxs ::+  (MonadIO m) =>+  Network ->+  Ctx ->+  Config ->+  [TxHash] ->+  [Address] ->+  ExceptT String m [TxHash]+searchAddrTxs _ _ _ _ [] = return []+searchAddrTxs net ctx cfg confirmedTxs as+  | length as > fromIntegral (configAddrBatch cfg) =+      nub . concat <$> mapM (go Nothing 0) (chunksOf (configAddrBatch cfg) as)+  | otherwise =+      nub <$> go Nothing 0 as+  where+    go hashM offset' xs = do+      Store.SerialList txRefs <-+        liftExcept $+          apiCall+            ctx+            (apiHost net cfg)+            ( GetAddrsTxs+                xs+                def+                  { limit = Just $ fromIntegral (configTxBatch cfg),+                    start = StartParamHash <$> hashM,+                    offset = offset'+                  }+            )+      -- Remove txs that we already have+      let tids = ((.txid) <$> txRefs) \\ confirmedTxs+      -- Either we have reached the end of the stream, or we have hit some+      -- txs in confirmedTxs. In both cases, we can stop the search.+      if length tids < fromIntegral (configTxBatch cfg)+        then return tids+        else do+          let lastId = (last tids).get+          rest <- go (Just lastId) 1 xs+          return $ tids <> rest++coinToTxHash :: DBCoin -> Either String TxHash+coinToTxHash coin =+  maybeToEither "coinToTxHash: Invalid outpoint" $ do+    bs <- decodeHex $ dBCoinOutpoint coin+    op <- eitherToMaybe (S.decode bs) :: Maybe OutPoint+    return op.hash
+ src/Haskoin/Wallet/Commands.hs view
@@ -0,0 +1,833 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}++module Haskoin.Wallet.Commands where++import Conduit (MonadUnliftIO)+import Control.Monad+import Control.Monad.Except+import Control.Monad.Reader (MonadIO (..), MonadTrans (lift))+import Data.Aeson (object, (.:), (.=))+import qualified Data.Aeson as Json+import qualified Data.ByteString as BS+import Data.Foldable (for_)+import Data.Maybe (fromMaybe, isJust)+import Data.String.Conversions (cs)+import Data.Text (Text)+import qualified Data.Text as Text+import Database.Persist.Sqlite (transactionUndo)+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Store.WebClient+import Haskoin.Wallet.Amounts+import Haskoin.Wallet.Backup+import Haskoin.Wallet.Config+import Haskoin.Wallet.Database+import Haskoin.Wallet.Entropy+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.Migration.SemVersion+import Haskoin.Wallet.Parser+import Haskoin.Wallet.Signing+import Haskoin.Wallet.TxInfo+import Haskoin.Wallet.Util+import Numeric.Natural (Natural)+import qualified System.Console.Haskeline as Haskeline+import qualified System.Directory as D+import System.Random (initStdGen)++data Response+  = ResponseError+      { responseError :: !Text+      }+  | ResponseMnemonic+      { responseEntropySource :: !Text,+        responseMnemonic :: ![Text],+        responseSplitMnemonic :: ![[Text]]+      }+  | ResponseAccount+      { responseAccount :: !DBAccount+      }+  | ResponseAccResult+      { responseAccount :: !DBAccount,+        responseResult :: !Bool,+        responseText :: !Text+      }+  | ResponseFile+      { responseTxFile :: !FilePath+      }+  | ResponseAccounts+      { responseAccounts :: ![DBAccount]+      }+  | ResponseAddress+      { responseAccount :: !DBAccount,+        responseAddress :: !DBAddress+      }+  | ResponseAddresses+      { responseAccount :: !DBAccount,+        responseAddresses :: ![DBAddress]+      }+  | ResponseTxs+      { responseAccount :: !DBAccount,+        responseTxs :: ![TxInfo]+      }+  | ResponseTx+      { responseAccount :: !DBAccount,+        responsePendingTx :: !TxInfo+      }+  | ResponseDeleteTx+      { responseNoSigHash :: !TxHash,+        responseFreedCoins :: !Natural,+        responseFreedAddrs :: !Natural+      }+  | ResponseCoins+      { responseAccount :: !DBAccount,+        responseCoins :: ![JsonCoin]+      }+  | ResponseSync {responseSync :: [SyncRes]}+  | ResponseRestore+      { responseRestore :: ![(DBAccount, Natural, Natural)]+      }+  | ResponseVersion+      { responseVersion :: !Text,+        responseDBVersion :: !Text+      }+  | ResponseRollDice+      { responseRollDice :: ![Natural],+        responseEntropySource :: !Text+      }+  deriving (Eq, Show)++jsonError :: String -> Json.Value+jsonError err = object ["type" .= Json.String "error", "error" .= err]++instance MarshalJSON Ctx Response where+  marshalValue ctx =+    \case+      ResponseError err -> jsonError $ cs err+      ResponseMnemonic e w ws ->+        object+          [ "type" .= Json.String "mnemonic",+            "entropysource" .= e,+            "mnemonic" .= w,+            "splitmnemonic" .= ws+          ]+      ResponseAccount a ->+        object+          [ "type" .= Json.String "account",+            "account" .= a+          ]+      ResponseAccResult a b t ->+        object+          [ "type" .= Json.String "accresult",+            "account" .= a,+            "result" .= b,+            "text" .= t+          ]+      ResponseFile f ->+        object+          [ "type" .= Json.String "file",+            "file" .= f+          ]+      ResponseAccounts as ->+        object+          [ "type" .= Json.String "accounts",+            "accounts" .= as+          ]+      ResponseAddress a addr ->+        object+          [ "type" .= Json.String "address",+            "account" .= a,+            "address" .= addr+          ]+      ResponseAddresses a adrs ->+        object+          [ "type" .= Json.String "addresses",+            "account" .= a,+            "addresses" .= adrs+          ]+      ResponseTxs a txs ->+        object+          [ "type" .= Json.String "txs",+            "account" .= a,+            "txs" .= (marshalValue (accountNetwork a, ctx) <$> txs)+          ]+      ResponseTx a t -> do+        let net = accountNetwork a+        object+          [ "type" .= Json.String "tx",+            "account" .= a,+            "tx" .= marshalValue (net, ctx) t+          ]+      ResponseDeleteTx h c a ->+        object+          [ "type" .= Json.String "deletetx",+            "nosighash" .= h,+            "freedcoins" .= c,+            "freedaddrs" .= a+          ]+      ResponseCoins a coins -> do+        let net = accountNetwork a+        object+          [ "type" .= Json.String "coins",+            "account" .= a,+            "coins" .= (marshalValue net <$> coins)+          ]+      ResponseSync xs -> do+        let f (SyncRes as bb bh tc cc) =+              object+                [ "account" .= as,+                  "bestblock" .= bb,+                  "bestheight" .= bh,+                  "txupdates" .= tc,+                  "coinupdates" .= cc+                ]+        object+          [ "type" .= Json.String "sync",+            "syncres" .= (f <$> xs)+          ]+      ResponseRestore xs ->+        let f (acc, t, c) =+              object+                [ "account" .= acc,+                  "txupdates" .= t,+                  "coinupdates" .= c+                ]+         in object+              [ "type" .= Json.String "restore",+                "restore" .= (f <$> xs)+              ]+      ResponseVersion v dbv ->+        object+          [ "type" .= Json.String "version",+            "version" .= v,+            "dbversion" .= dbv+          ]+      ResponseRollDice ns e ->+        object+          ["type" .= Json.String "rolldice", "entropysource" .= e, "dice" .= ns]+  unmarshalValue ctx =+    Json.withObject "response" $ \o -> do+      Json.String resType <- o .: "type"+      case resType of+        "error" -> ResponseError <$> o .: "error"+        "mnemonic" ->+          ResponseMnemonic+            <$> o .: "entropysource"+            <*> o .: "mnemonic"+            <*> o .: "splitmnemonic"+        "account" ->+          ResponseAccount+            <$> o .: "account"+        "accresult" ->+          ResponseAccResult+            <$> o .: "account"+            <*> o .: "result"+            <*> o .: "text"+        "file" ->+          ResponseFile+            <$> o .: "file"+        "accounts" ->+          ResponseAccounts+            <$> o .: "accounts"+        "address" ->+          ResponseAddress+            <$> o .: "account"+            <*> o .: "address"+        "addresses" ->+          ResponseAddresses+            <$> o .: "account"+            <*> o .: "addresses"+        "txs" -> do+          a <- o .: "account"+          let net = accountNetwork a+          txs <- mapM (unmarshalValue (net, ctx)) =<< o .: "txs"+          return $ ResponseTxs a txs+        "tx" -> do+          a <- o .: "account"+          let net = accountNetwork a+          t <- unmarshalValue (net, ctx) =<< o .: "tx"+          return $ ResponseTx a t+        "deletetx" ->+          ResponseDeleteTx+            <$> o .: "nosighash"+            <*> o .: "freedcoins"+            <*> o .: "freedaddrs"+        "coins" -> do+          a <- o .: "account"+          xs <- o .: "coins"+          coins <- mapM (unmarshalValue (accountNetwork a)) xs+          return $ ResponseCoins a coins+        "sync" -> do+          xs <- o .: "syncres"+          res <- forM xs $ \x ->+            SyncRes+              <$> x .: "account"+              <*> x .: "bestblock"+              <*> x .: "bestheight"+              <*> x .: "txupdates"+              <*> x .: "coinupdates"+          return $ ResponseSync res+        "restore" -> do+          let f =+                Json.withObject "account" $ \o' ->+                  (,,)+                    <$> o' .: "account"+                    <*> o' .: "txupdates"+                    <*> o' .: "coinupdates"+          ResponseRestore <$> (mapM f =<< o .: "restore")+        "version" ->+          ResponseVersion+            <$> o .: "version"+            <*> o .: "dbversion"+        "rolldice" ->+          ResponseRollDice+            <$> o .: "dice"+            <*> o .: "entropysource"+        _ -> fail "Invalid JSON response type"++runDBResponse ::+  (MonadUnliftIO m) => Config -> ExceptT String (DB m) Response -> m Response+runDBResponse cfg action = do+  runDB cfg $ do+    resE <- runExceptT action+    case resE of+      Left err -> do+        transactionUndo -- Roll back the current sqlite transaction+        return $ ResponseError $ cs err+      Right res -> return res++catchResponseError :: (Monad m) => ExceptT String m Response -> m Response+catchResponseError m = do+  resE <- runExceptT m+  case resE of+    Left err -> return $ ResponseError $ cs err+    Right res -> return res++commandResponse :: Ctx -> Config -> AmountUnit -> Command -> IO Response+commandResponse ctx cfg unit cmd =+  case cmd of+    -- Mnemonic and account management+    CommandMnemonic e d s -> cmdMnemonic e d s+    CommandCreateAcc t n dM s -> cmdCreateAcc ctx cfg t n dM s+    CommandTestAcc nameM s -> cmdTestAcc ctx cfg nameM s+    CommandRenameAcc old new -> cmdRenameAcc cfg old new+    CommandAccounts nameM -> cmdAccounts cfg nameM+    CommandSyncAcc nameM full -> cmdSyncAcc ctx cfg nameM full+    CommandDeleteAcc name net deriv -> cmdDeleteAcc cfg name net deriv+    -- Address management+    CommandReceive nameM labM -> cmdReceive ctx cfg nameM labM+    CommandAddrs nameM p -> cmdAddrs cfg nameM p+    CommandLabel nameM i l -> cmdLabel cfg nameM i l+    -- Transaction management+    CommandTxs nameM p -> cmdTxs ctx cfg nameM p+    CommandPrepareTx rcpts nameM fee dust rcptPay minConf o ->+      cmdPrepareTx ctx cfg rcpts nameM unit fee dust rcptPay minConf o+    CommandPendingTxs nameM p -> cmdPendingTxs ctx cfg nameM p+    CommandSignTx nameM h i o s -> cmdSignTx ctx cfg nameM h i o s+    CommandSendTx h -> cmdSendTx ctx cfg h+    CommandDeleteTx h -> cmdDeleteTx ctx cfg h+    CommandCoins nameM p -> cmdCoins cfg nameM p+    -- Import/export commands+    CommandExportAcc nameM f -> cmdExportAcc ctx cfg nameM f+    CommandImportAcc f -> cmdImportAcc ctx cfg f+    CommandReviewTx nameM file -> cmdReviewTx ctx cfg nameM file+    CommandExportTx h f -> cmdExportTx cfg h f+    CommandImportTx nameM file -> cmdImportTx ctx cfg nameM file+    -- Backup and Restore+    CommandBackup f -> cmdBackup ctx cfg f+    CommandRestore f -> cmdRestore ctx cfg f+    CommandDiscoverAcc nameM -> cmdDiscoverAccount ctx cfg nameM+    -- Utilities+    CommandVersion -> cmdVersion cfg+    CommandPrepareSweep nameM prvKey st outputM f d ->+      prepareSweep ctx cfg nameM prvKey st outputM f d+    CommandSignSweep nameM h i o k -> signSweep ctx cfg nameM h i o k+    CommandRollDice n -> rollDice n++-- runDBResponse Monad Stack:+-- ExceptT String (ReaderT SqlBackend (NoLoggingT (ResourceT (IO))))++liftEitherIO :: (MonadIO m) => IO (Either String a) -> ExceptT String m a+liftEitherIO = liftEither <=< liftIO++cmdMnemonic :: Natural -> Bool -> Natural -> IO Response+cmdMnemonic ent useDice splitMnemIn =+  catchResponseError $ do+    (orig, ms, splitMs) <- genMnemonic ent useDice splitMnemIn+    return $ ResponseMnemonic orig (Text.words ms) (Text.words <$> splitMs)++cmdCreateAcc ::+  Ctx -> Config -> Text -> Network -> Maybe Natural -> Natural -> IO Response+cmdCreateAcc ctx cfg name net derivM splitMnemIn = do+  runDBResponse cfg $ do+    mnem <- askMnemonicPass splitMnemIn+    walletFP <- liftEither $ walletFingerprint net ctx mnem+    d <- maybe (lift $ nextAccountDeriv walletFP net) return derivM+    prvKey <- liftEither $ signingKey net ctx mnem d+    let xpub = deriveXPubKey ctx prvKey+    (_, acc) <- insertAccount net ctx walletFP name xpub+    return $ ResponseAccount acc++cmdTestAcc :: Ctx -> Config -> Maybe Text -> Natural -> IO Response+cmdTestAcc ctx cfg nameM splitMnemIn =+  runDBResponse cfg $ do+    (_, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        xPubKey = accountXPubKey ctx acc+        d = accountIndex acc+    mnem <- askMnemonicPass splitMnemIn+    xPrvKey <- liftEither $ signingKey net ctx mnem d+    return $+      if deriveXPubKey ctx xPrvKey == xPubKey+        then+          ResponseAccResult+            { responseAccount = acc,+              responseResult = True,+              responseText =+                "The mnemonic and passphrase matched the account"+            }+        else+          ResponseAccResult+            { responseAccount = acc,+              responseResult = False,+              responseText =+                "The mnemonic and passphrase did not match the account"+            }++cmdImportAcc :: Ctx -> Config -> FilePath -> IO Response+cmdImportAcc ctx cfg fp =+  runDBResponse cfg $ do+    (PubKeyDoc xpub net name wallet) <- liftEitherIO $ readMarshalFile ctx fp+    (_, acc) <- insertAccount net ctx wallet name xpub+    return $ ResponseAccount acc++cmdExportAcc :: Ctx -> Config -> Maybe Text -> FilePath -> IO Response+cmdExportAcc ctx cfg nameM file =+  runDBResponse cfg $ do+    (_, acc) <- getAccountByName nameM+    checkPathFree file+    let xpub = accountXPubKey ctx acc+        net = accountNetwork acc+        name = dBAccountName acc+        wallet = accountWallet acc+        doc = PubKeyDoc xpub net name wallet+    liftIO $ writeMarshalFile ctx file doc+    return $ ResponseFile file++cmdRenameAcc :: Config -> Text -> Text -> IO Response+cmdRenameAcc cfg oldName newName =+  runDBResponse cfg $ do+    acc <- renameAccount oldName newName+    return $ ResponseAccount acc++cmdDeleteAcc :: Config -> Text -> Network -> HardPath -> IO Response+cmdDeleteAcc cfg name net path =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName $ Just name+    unless (accountNetwork acc == net) $+      throwError "The network of the account to delete did not match"+    accPath <- liftMaybe "HardPath" $ parseHard $ cs $ dBAccountDerivation acc+    unless (path == accPath) $+      throwError+        "The full derivation path of the account to delete did not match"+    lift $ deleteAccount accId+    return $+      ResponseAccResult acc True $+        "The account " <> name <> " has been deleted"++cmdAccounts :: Config -> Maybe Text -> IO Response+cmdAccounts cfg nameM =+  runDBResponse cfg $ do+    case nameM of+      Just _ -> do+        (_, acc) <- getAccountByName nameM+        return $ ResponseAccounts [acc]+      _ -> do+        accs <- lift getAccounts+        return $ ResponseAccounts $ snd <$> accs++cmdReceive :: Ctx -> Config -> Maybe Text -> Maybe Text -> IO Response+cmdReceive ctx cfg nameM labelM =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    addr <- genExtAddress ctx cfg accId $ fromMaybe "" labelM+    return $ ResponseAddress acc addr++cmdAddrs :: Config -> Maybe Text -> Page -> IO Response+cmdAddrs cfg nameM page =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    as <- lift $ addressPage accId page+    return $ ResponseAddresses acc as++cmdLabel :: Config -> Maybe Text -> Natural -> Text -> IO Response+cmdLabel cfg nameM idx lab =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    adr <- setAddrLabel accId (fromIntegral idx) lab+    return $ ResponseAddress acc adr++cmdTxs :: Ctx -> Config -> Maybe Text -> Page -> IO Response+cmdTxs ctx cfg nameM page =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    txInfos <- txsPage ctx accId page+    return $ ResponseTxs acc txInfos++cmdPrepareTx ::+  Ctx ->+  Config ->+  [(Text, Text)] ->+  Maybe Text ->+  AmountUnit ->+  Natural ->+  Natural ->+  Bool ->+  Natural ->+  Maybe FilePath ->+  IO Response+cmdPrepareTx ctx cfg rcpTxt nameM unit feeByte dust rcptPay minConf fileM =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        pub = accountXPubKey ctx acc+    rcpts <- liftEither $ mapM (toRecipient net) rcpTxt+    gen <- liftIO initStdGen+    signDat <- buildTxSignData net ctx cfg gen accId rcpts feeByte dust rcptPay minConf+    txInfo <- liftEither $ parseTxSignData net ctx pub signDat+    txInfoL <- lift $ fillTxInfoLabels net txInfo+    for_ fileM checkPathFree+    _ <- importPendingTx net ctx accId signDat+    for_ fileM $ \file -> liftIO $ writeJsonFile file $ Json.toJSON signDat+    newAcc <- getAccountById accId+    return $ ResponseTx newAcc txInfoL+  where+    toRecipient net (a, v) = do+      addr <- textToAddrE net a+      val <- maybeToEither (cs $ badAmnt v) (readAmount unit v)+      return (addr, val)+    badAmnt v =+      "Could not parse the amount " <> v <> " as " <> showUnit unit 1++cmdPendingTxs :: Ctx -> Config -> Maybe Text -> Page -> IO Response+cmdPendingTxs ctx cfg nameM page =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    txInfos <- pendingTxPage ctx accId page+    return $ ResponseTxs acc txInfos++cmdReviewTx :: Ctx -> Config -> Maybe Text -> FilePath -> IO Response+cmdReviewTx ctx cfg nameM fp =+  runDBResponse cfg $ do+    (_, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        pub = accountXPubKey ctx acc+    tsd <- liftEitherIO $ readJsonFile fp+    txInfo <- liftEither $ parseTxSignData net ctx pub tsd+    txInfoL <- lift $ fillTxInfoLabels net txInfo+    return $ ResponseTx acc txInfoL++cmdExportTx :: Config -> TxHash -> FilePath -> IO Response+cmdExportTx cfg nosigH fp =+  runDBResponse cfg $ do+    pendingTxM <- lift $ getPendingTx nosigH+    case pendingTxM of+      Just (_, tsd, _) -> do+        checkPathFree fp+        liftIO $ writeJsonFile fp $ Json.toJSON tsd+        return $ ResponseFile fp+      _ -> throwError "The pending transaction does not exist"++cmdImportTx :: Ctx -> Config -> Maybe Text -> FilePath -> IO Response+cmdImportTx ctx cfg nameM fp =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        pub = accountXPubKey ctx acc+    tsd <- liftEitherIO $ readJsonFile fp+    txInfo <- liftEither $ parseTxSignData net ctx pub tsd+    txInfoL <- lift $ fillTxInfoLabels net txInfo+    _ <- importPendingTx net ctx accId tsd+    return $ ResponseTx acc txInfoL++cmdDeleteTx :: Ctx -> Config -> TxHash -> IO Response+cmdDeleteTx ctx cfg nosigH =+  runDBResponse cfg $ do+    (coins, addrs) <- deletePendingTx ctx nosigH+    return $ ResponseDeleteTx nosigH coins addrs++cmdSignTx ::+  Ctx ->+  Config ->+  Maybe Text ->+  Maybe TxHash ->+  Maybe FilePath ->+  Maybe FilePath ->+  Natural ->+  IO Response+cmdSignTx ctx cfg nameM nosigHM inputM outputM splitMnemIn =+  runDBResponse cfg $ do+    (tsd, online) <- parseSignInput nosigHM inputM outputM+    when online $+      throwError "The transaction is already online"+    when (txSignDataSigned tsd) $+      throwError "The transaction is already signed"+    (accId, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        idx = fromIntegral $ dBAccountIndex acc+        accPub = accountXPubKey ctx acc+    for_ outputM checkPathFree+    mnem <- askMnemonicPass splitMnemIn+    prvKey <- liftEither $ signingKey net ctx mnem idx+    let xpub = deriveXPubKey ctx prvKey+    unless (accPub == xpub) $+      throwError "The mnemonic did not match the provided account"+    (newSignData, txInfo) <- liftEither $ signWalletTx net ctx tsd prvKey+    txInfoL <- lift $ fillTxInfoLabels net txInfo+    when (isJust nosigHM) $ void $ importPendingTx net ctx accId newSignData+    for_ outputM $ \o -> liftIO $ writeJsonFile o $ Json.toJSON newSignData+    return $ ResponseTx acc txInfoL++parseSignInput ::+  (MonadUnliftIO m) =>+  Maybe TxHash ->+  Maybe FilePath ->+  Maybe FilePath ->+  ExceptT String (DB m) (TxSignData, Bool)+parseSignInput nosigHM inputM outputM =+  case (nosigHM, inputM, outputM) of+    (Nothing, Nothing, _) ->+      throwError+        "Provide either a TXHASH or both a --input file and a --output file"+    (Just _, Just _, _) ->+      throwError "Can not specify both a TXHASH and a --input file"+    (_, Just _, Nothing) ->+      throwError "When using a --input file, also provide a --output file"+    (Just h, _, _) -> do+      resM <- lift $ getPendingTx h+      case resM of+        Just (_, t, TxInfoPending _ _ online) -> return (t, online)+        _ -> throwError "The nosigHash does not exist in the wallet"+    (_, Just i, _) -> do+      exist <- liftIO $ D.doesFileExist i+      unless exist $ throwError "Input file does not exist"+      tsd <- liftEitherIO (readJsonFile i)+      return (tsd, False)++cmdCoins :: Config -> Maybe Text -> Page -> IO Response+cmdCoins cfg nameM page =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    let net = accountNetwork acc+    coins <- coinPage net accId page+    return $ ResponseCoins acc coins++cmdSendTx :: Ctx -> Config -> TxHash -> IO Response+cmdSendTx ctx cfg nosigH =+  runDBResponse cfg $ do+    tsdM <- lift $ getPendingTx nosigH+    case tsdM of+      Just (accId, tsd@(TxSignData signedTx _ _ _ signed), _) -> do+        acc <- getAccountById accId+        let net = accountNetwork acc+            pub = accountXPubKey ctx acc+        txInfo <- liftEither $ parseTxSignData net ctx pub tsd+        txInfoL <- lift $ fillTxInfoLabels net txInfo+        let verify = verifyTxInfo net ctx signedTx txInfoL+        unless (signed && verify) $ throwError "The transaction is not signed"+        checkHealth ctx net cfg+        let host = apiHost net cfg+        Store.TxId netTxId <- liftExcept $ apiCall ctx host (PostTx signedTx)+        unless (netTxId == txHash signedTx) $+          throwError $+            "The server returned the wrong TxHash: "+              <> cs (txHashToHex netTxId)+        _ <- lift $ setPendingTxOnline nosigH+        lift $ insertRawTx signedTx+        return $ ResponseTx acc $ setTxInfoOnline txInfoL+      _ -> throwError "The nosigHash does not exist in the wallet"++setTxInfoOnline :: TxInfo -> TxInfo+setTxInfoOnline txInfo =+  case txInfoPending txInfo of+    Just p -> txInfo {txInfoPending = Just p {pendingOnline = True}}+    _ -> txInfo++cmdSyncAcc :: Ctx -> Config -> Maybe Text -> Bool -> IO Response+cmdSyncAcc ctx cfg nameM full =+  runDBResponse cfg $ do+    accs <-+      case nameM of+        Just _ -> (:[]) <$> getAccountByName nameM+        _ -> lift getAccounts+    when (null accs) $ throwError "There are no accounts in the wallet"+    res <- forM accs $ \(accId, acc) -> do+      let net = accountNetwork acc+      sync ctx cfg net accId full+    return $ ResponseSync res++cmdDiscoverAccount :: Ctx -> Config -> Maybe Text -> IO Response+cmdDiscoverAccount ctx cfg nameM = do+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        pub = accountXPubKey ctx acc+    checkHealth ctx net cfg+    (e, i) <- discoverAddrs net ctx cfg pub+    discoverAccGenAddrs ctx cfg accId AddrExternal $ fromIntegral e+    discoverAccGenAddrs ctx cfg accId AddrInternal $ fromIntegral i+    -- Perform a full sync after discovery+    res <- sync ctx cfg net accId True+    return $ ResponseSync [res]++cmdBackup :: Ctx -> Config -> FilePath -> IO Response+cmdBackup ctx cfg fp =+  runDBResponse cfg $ do+    backup <- createBackup ctx+    checkPathFree fp+    liftIO $ writeJsonFile fp $ marshalValue ctx backup+    return $ ResponseFile fp++cmdRestore :: Ctx -> Config -> FilePath -> IO Response+cmdRestore ctx cfg fp =+  runDBResponse cfg $ do+    backup <- liftEitherIO $ readMarshalFile ctx fp+    let f (SyncRes a _ _ t c) = (a, t, c)+    ResponseRestore . (f <$>) <$> restoreBackup ctx cfg backup++cmdVersion :: Config -> IO Response+cmdVersion cfg = do+  runDBResponse cfg $ do+    dbv <- lift getVersion+    return $ ResponseVersion currentVersionStr (cs $ verString dbv)++prepareSweep ::+  Ctx ->+  Config ->+  Maybe Text ->+  FilePath ->+  [Text] ->+  Maybe FilePath ->+  Natural ->+  Natural ->+  IO Response+prepareSweep ctx cfg nameM prvKeyFile sweepToT outputM feeByte dust =+  runDBResponse cfg $ do+    (accId, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        pub = accountXPubKey ctx acc+    secKeys <- parseSecKeysFile net <$> liftIO (readFileWords prvKeyFile)+    sweepTo <- liftEither $ mapM (textToAddrE net) sweepToT+    checkHealth ctx net cfg+    tsd <- buildSweepSignData net ctx cfg accId secKeys sweepTo feeByte dust+    txInfo <- liftEither $ parseTxSignData net ctx pub tsd+    txInfoL <- lift $ fillTxInfoLabels net txInfo+    for_ outputM checkPathFree+    _ <- importPendingTx net ctx accId tsd+    for_ outputM $ \file -> liftIO $ writeJsonFile file $ Json.toJSON tsd+    return $ ResponseTx acc txInfoL++signSweep ::+  Ctx ->+  Config ->+  Maybe Text ->+  Maybe TxHash ->+  Maybe FilePath ->+  Maybe FilePath ->+  FilePath ->+  IO Response+signSweep ctx cfg nameM nosigHM inputM outputM keyFile =+  runDBResponse cfg $ do+    (tsd, online) <- parseSignInput nosigHM inputM outputM+    when online $+      throwError "The transaction is already online"+    when (txSignDataSigned tsd) $+      throwError "The transaction is already signed"+    (accId, acc) <- getAccountByName nameM+    let net = accountNetwork acc+        pub = accountXPubKey ctx acc+    for_ outputM checkPathFree+    -- Read the file containing the private keys+    secKeys <- parseSecKeysFile net <$> liftIO (readFileWords keyFile)+    when (null secKeys) $ throwError "No private keys to sign"+    -- Sign the transactions+    (newTsd, txInfo) <- liftEither $ signTxWithKeys net ctx tsd pub secKeys+    txInfoL <- lift $ fillTxInfoLabels net txInfo+    when (isJust nosigHM) $ void $ importPendingTx net ctx accId newTsd+    for_ outputM $ \o -> liftIO $ writeJsonFile o $ Json.toJSON newTsd+    return $ ResponseTx acc txInfoL++rollDice :: Natural -> IO Response+rollDice n = do+  (res, origEnt) <- go [] ""+  return $ ResponseRollDice (take (fromIntegral n) res) origEnt+  where+    go acc orig+      | length acc >= fromIntegral n = return (acc, orig)+      | otherwise = do+          (origEnt, sysEnt) <- systemEntropy 1+          go (word8ToBase6 (head $ BS.unpack sysEnt) <> acc) origEnt++-- Haskeline Helpers --++askInputLineHidden :: String -> IO String+askInputLineHidden message = do+  inputM <-+    Haskeline.runInputT Haskeline.defaultSettings $+      Haskeline.getPassword (Just '*') message+  maybe+    (error "No action due to EOF")+    return+    inputM++askInputLine :: String -> IO String+askInputLine message = do+  inputM <-+    Haskeline.runInputT Haskeline.defaultSettings $+      Haskeline.getInputLine message+  maybe+    (error "No action due to EOF")+    return+    inputM++askMnemonicWords :: String -> IO Mnemonic+askMnemonicWords txt = do+  mnm <- askInputLineHidden txt+  case fromMnemonic (cs mnm) of -- validate the mnemonic+    Right _ -> return $ cs mnm+    Left _ -> do+      liftIO $ putStrLn "Invalid mnemonic"+      askMnemonicWords txt++askMnemonicPass :: (MonadError String m, MonadIO m) => Natural -> m MnemonicPass+askMnemonicPass splitMnemIn = do+  mnm <-+    if splitMnemIn == 1+      then liftIO $ askMnemonicWords "Enter your mnemonic words: "+      else do+        ms <- forM [1 .. splitMnemIn] $ \n ->+          liftIO $ askMnemonicWords $ "Split mnemonic part #" <> show n <> ": "+        liftEither $ mergeMnemonicParts ms+  passStr <- liftIO askPassword+  return+    MnemonicPass+      { mnemonicWords = mnm,+        mnemonicPass = cs passStr+      }++askPassword :: IO String+askPassword = do+  pass <- askInputLineHidden "Mnemonic passphrase or leave empty: "+  if null pass+    then return pass+    else do+      pass2 <- askInputLineHidden "Repeat your mnemonic passphrase: "+      if pass == pass2+        then return pass+        else do+          putStrLn "The passphrases did not match"+          askPassword
+ src/Haskoin/Wallet/Config.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}++module Haskoin.Wallet.Config where++import Control.Monad (unless)+import Control.Monad.Except+import Control.Monad.Reader (MonadIO (..))+import Data.Aeson+import Data.Default+import Data.String.Conversions (cs)+import Data.Text (Text)+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Store.WebClient+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.Util+import Numeric.Natural (Natural)+import qualified System.Directory as D++hwDataDirectory :: IO FilePath+hwDataDirectory = do+  dir <- D.getAppUserDataDirectory "hw"+  D.createDirectoryIfMissing True dir+  return dir++databaseFile :: Config -> IO FilePath+databaseFile cfg =+  case configDatabaseFile cfg of+    Just dbFile -> return dbFile+    _ -> do+      dir <- hwDataDirectory+      return $ dir </> "accounts.sqlite"++data Config = Config+  { configHost :: Text,+    configGap :: Natural,+    configRecoveryGap :: Natural,+    configAddrBatch :: Natural,+    configTxBatch :: Natural,+    configCoinBatch :: Natural,+    configTxFullBatch :: Natural,+    configDatabaseFile :: Maybe FilePath+  }+  deriving (Eq, Show)++instance FromJSON Config where+  parseJSON =+    withObject "config" $ \o ->+      Config+        <$> o .: "host"+        <*> o .: "gap"+        <*> o .: "recovery-gap"+        <*> o .: "addr-batch"+        <*> o .: "tx-batch"+        <*> o .: "coin-batch"+        <*> o .: "tx-full-batch"+        <*> o .:? "database-file"++instance ToJSON Config where+  toJSON cfg =+    object+      [ "host" .= configHost cfg,+        "gap" .= configGap cfg,+        "recovery-gap" .= configRecoveryGap cfg,+        "addr-batch" .= configAddrBatch cfg,+        "tx-batch" .= configTxBatch cfg,+        "coin-batch" .= configCoinBatch cfg,+        "tx-full-batch" .= configTxFullBatch cfg,+        "database-file" .= configDatabaseFile cfg+      ]++instance Default Config where+  def =+    Config+      { configHost = cs (def :: ApiConfig).host,+        configGap = 20,+        configRecoveryGap = 40,+        configAddrBatch = 100,+        configTxBatch = 100,+        configCoinBatch = 100,+        configTxFullBatch = 100,+        configDatabaseFile = Nothing+      }++initConfig :: IO Config+initConfig = do+  dir <- hwDataDirectory+  let configFile = dir </> "config.json"+  exists <- D.doesFileExist configFile+  unless exists $ writeJsonFile configFile $ toJSON (def :: Config)+  resE <- readJsonFile configFile+  either (error "Could not read config.json") return resE++apiHost :: Network -> Config -> ApiConfig+apiHost net = ApiConfig net . cs . configHost++-- Utilities --++checkHealth ::+  (MonadIO m) =>+  Ctx ->+  Network ->+  Config ->+  ExceptT String m ()+checkHealth ctx net cfg = do+  let host = apiHost net cfg+  health <- liftExcept $ apiCall ctx host GetHealth+  unless (Store.isOK health) $+    throwError "The indexer health check has failed"
+ src/Haskoin/Wallet/Database.hs view
@@ -0,0 +1,1424 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Haskoin.Wallet.Database where++import Conduit (MonadUnliftIO, ResourceT)+import Control.Arrow (Arrow (second))+import Control.Monad+import Control.Monad.Except+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad.Logger (NoLoggingT)+import Control.Monad.Reader (MonadTrans (lift), ReaderT)+import Control.Monad.Trans.Maybe+import Data.Aeson+import qualified Data.Aeson as Json+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.Either (fromRight)+import Data.List (find, nub, partition)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Maybe (fromJust, fromMaybe, mapMaybe)+import qualified Data.Serialize as S+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Time (UTCTime, getCurrentTime)+import Data.Word (Word64)+import Database.Esqueleto.Legacy as E+import qualified Database.Persist as P+import Database.Persist.Sqlite (runSqlite)+import Database.Persist.TH+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Wallet.Config+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.Migration.SemVersion+import Haskoin.Wallet.TxInfo+import Haskoin.Wallet.Util (Page (Page), textToAddrE)+import Numeric.Natural (Natural)++{- SQL Table Definitions -}++share+  [mkPersist sqlSettings, mkMigrate "migrateAll"]+  [persistLowerCase|++DBVersion+  version Text+  Primary version+  deriving Show++DBWallet+  fingerprint Text+  Primary fingerprint+  deriving Show++DBAccount+  name Text+  wallet DBWalletId+  index Int+  network Text+  derivation Text+  external Int+  internal Int+  xPubKey Text+  balanceConfirmed Word64+  balanceUnconfirmed Word64+  balanceCoins Word64+  created UTCTime default=CURRENT_TIME+  Primary wallet derivation+  UniqueName name+  UniqueXPubKey xPubKey+  UniqueNetworkId wallet network index+  deriving Show+  deriving Eq++DBAddress+  index Int+  accountWallet DBWalletId+  accountDerivation Text+  derivation Text+  address Text+  label Text+  balanceConfirmed Word64+  balanceUnconfirmed Word64+  balanceCoins Word64+  balanceTxs Word64+  balanceReceived Word64+  internal Bool+  free Bool+  created UTCTime default=CURRENT_TIME+  Primary accountWallet accountDerivation derivation+  UniqueAddress address+  Foreign DBAccount fk_wallet_derivation accountWallet accountDerivation+  deriving Show+  deriving Eq++DBTxInfo+  accountWallet DBWalletId+  accountDerivation Text+  txid Text+  blockRef ByteString+  confirmed Bool+  blob ByteString+  created UTCTime default=CURRENT_TIME+  Primary accountWallet accountDerivation txid+  Foreign DBAccount fk_wallet_derivation accountWallet accountDerivation+  deriving Show+  deriving Eq++DBCoin+  accountWallet DBWalletId+  accountDerivation Text+  outpoint Text+  address Text+  value Word64+  blockRef ByteString+  blob ByteString+  confirmed Bool+  locked Bool+  created UTCTime default=CURRENT_TIME+  Primary outpoint+  Foreign DBAccount fk_wallet_derivation accountWallet accountDerivation+  deriving Show+  deriving Eq++DBRawTx+  hash Text+  blob ByteString+  Primary hash+  deriving Show+  deriving Eq++DBPendingTx+  accountWallet DBWalletId+  accountDerivation Text+  nosigHash Text+  blob ByteString+  online Bool+  created UTCTime default=CURRENT_TIME+  Primary nosigHash+  Foreign DBAccount fk_wallet_derivation accountWallet accountDerivation+  deriving Show+  deriving Eq++DBBest+  network Text+  bestBlock Text+  bestHeight Int+  Primary network+  deriving Show+|]++type DB m = ReaderT SqlBackend (NoLoggingT (ResourceT m))++runDB :: (MonadUnliftIO m) => Config -> DB m a -> m a+runDB cfg action = do+  dbFile <- liftIO $ databaseFile cfg+  runSqlite (cs dbFile) action++globalMigration :: (MonadUnliftIO m) => DB m ()+globalMigration = void $ runMigrationQuiet migrateAll++{- Meta -}++-- Database versions use major.minor only. Patch versions are ignored.+setVersion :: (MonadUnliftIO m) => SemVersion -> DB m ()+setVersion semVer = do+  verM <- selectOne $ from return+  let verTxt = cs $ verString $ toMinor semVer+  case verM of+    Nothing -> P.insert_ $ DBVersion verTxt+    Just (Entity k _) -> P.update k [DBVersionVersion P.=. verTxt]++getVersion :: (MonadUnliftIO m) => DB m SemVersion+getVersion = do+  resM <- selectOne . from $ \v -> return $ v ^. DBVersionVersion+  return $ parseSemVersion . cs . unValue $ fromJust resM++updateBest ::+  (MonadUnliftIO m) => Network -> BlockHash -> BlockHeight -> DB m ()+updateBest net hash height = do+  let netT = cs net.name+      key = DBBestKey netT+  P.repsert key $ DBBest netT (blockHashToHex hash) (fromIntegral height)++getBest ::+  (MonadUnliftIO m) => Network -> DB m (Maybe (BlockHash, BlockHeight))+getBest net = do+  let netT = cs net.name+  resM <- P.get $ DBBestKey netT+  return $ do+    DBBest _ a b <- resM+    hash <- hexToBlockHash a+    return (hash, fromIntegral b)++{- Accounts -}++instance ToJSON DBAccount where+  toJSON acc =+    object+      [ "name" .= dBAccountName acc,+        "wallet" .= dBAccountWallet acc,+        "index" .= dBAccountIndex acc,+        "network" .= dBAccountNetwork acc,+        "derivation" .= dBAccountDerivation acc,+        "external" .= dBAccountExternal acc,+        "internal" .= dBAccountInternal acc,+        "xPubKey" .= dBAccountXPubKey acc,+        "balance" .= toJSON (AccountBalance confirm unconfirm coins),+        "created" .= dBAccountCreated acc+      ]+    where+      confirm = dBAccountBalanceConfirmed acc+      unconfirm = dBAccountBalanceUnconfirmed acc+      coins = dBAccountBalanceCoins acc++instance FromJSON DBAccount where+  parseJSON =+    withObject "DBAccount" $ \o -> do+      bal <- o .: "balance"+      DBAccount+        <$> o .: "name"+        <*> o .: "wallet"+        <*> o .: "index"+        <*> o .: "network"+        <*> o .: "derivation"+        <*> o .: "external"+        <*> o .: "internal"+        <*> o .: "xPubKey"+        <*> (bal .: "confirmed")+        <*> (bal .: "unconfirmed")+        <*> (bal .: "coins")+        <*> o .: "created"++data AccountBalance = AccountBalance+  { -- | confirmed balance+    accBalanceConfirmed :: !Word64,+    -- | unconfirmed balance+    accBalanceUnconfirmed :: !Word64,+    -- | number of unspent outputs+    accBalanceCoins :: !Word64+  }+  deriving (Show, Read, Eq, Ord)++instance ToJSON AccountBalance where+  toJSON b =+    object+      [ "confirmed" .= accBalanceConfirmed b,+        "unconfirmed" .= accBalanceUnconfirmed b,+        "coins" .= accBalanceCoins b+      ]++instance FromJSON AccountBalance where+  parseJSON =+    withObject "accountbalance" $ \o ->+      AccountBalance+        <$> o .: "confirmed"+        <*> o .: "unconfirmed"+        <*> o .: "coins"++accountIndex :: DBAccount -> Natural+accountIndex = fromIntegral . dBAccountIndex++accountNetwork :: DBAccount -> Network+accountNetwork =+  fromMaybe (error "Invalid Network in database")+    . netByName+    . cs+    . dBAccountNetwork++accountWallet :: DBAccount -> Fingerprint+accountWallet acc =+  let (DBWalletKey walletFP) = dBAccountWallet acc+   in fromRight (error "Invalid WalletId in database") $+        textToFingerprint walletFP++accountXPubKey :: Ctx -> DBAccount -> XPubKey+accountXPubKey ctx acc =+  fromMaybe (error "Invalid XPubKey in database") $+    xPubImport (accountNetwork acc) ctx (dBAccountXPubKey acc)++nextAccountDeriv :: (MonadUnliftIO m) => Fingerprint -> Network -> DB m Natural+nextAccountDeriv walletFP net = do+  let walletId = DBWalletKey $ fingerprintToText walletFP+  idxs <-+    select . from $ \a -> do+      where_ $+        a ^. DBAccountNetwork ==. val (cs net.name)+          &&. a ^. DBAccountWallet ==. val walletId+      return $ a ^. DBAccountIndex+  return $ smallestUnused $ fromIntegral . unValue <$> idxs++smallestUnused :: [Natural] -> Natural+smallestUnused xs = fromJust $ find (not . (`elem` xs)) [0 ..]++existsAccount :: (MonadUnliftIO m) => Text -> DB m Bool+existsAccount name = P.existsBy $ UniqueName name++existsXPubKey :: (MonadUnliftIO m) => Network -> Ctx -> XPubKey -> DB m Bool+existsXPubKey net ctx key = P.existsBy $ UniqueXPubKey $ xPubExport net ctx key++getWalletOrCreate :: (MonadUnliftIO m) => Fingerprint -> DB m DBWalletId+getWalletOrCreate fp = do+  let key = DBWalletKey $ fingerprintToText fp+  walletM <- P.get key+  case walletM of+    Just _ -> return key+    _ -> P.insert $ DBWallet $ fingerprintToText fp++insertAccount ::+  (MonadUnliftIO m) =>+  Network ->+  Ctx ->+  Fingerprint ->+  Text ->+  XPubKey ->+  ExceptT String (DB m) (DBAccountId, DBAccount)+insertAccount net ctx walletFP name xpub = do+  existsName <- lift $ existsAccount name+  if existsName+    then throwError $ "Account " <> cs name <> " already exists"+    else do+      existsKey <- lift $ existsXPubKey net ctx xpub+      if existsKey+        then throwError "The XPubKey already exists"+        else do+          time <- liftIO getCurrentTime+          walletId <- lift $ getWalletOrCreate walletFP+          let path = bip44Deriv net $ fromIntegral $ xPubChild xpub+              idx = fromIntegral $ xPubIndex xpub+              account =+                DBAccount+                  { dBAccountName = name,+                    dBAccountWallet = walletId,+                    dBAccountIndex = idx,+                    dBAccountNetwork = cs net.name,+                    dBAccountDerivation = cs $ pathToStr path,+                    dBAccountExternal = 0,+                    dBAccountInternal = 0,+                    dBAccountXPubKey = xPubExport net ctx xpub,+                    dBAccountBalanceConfirmed = 0,+                    dBAccountBalanceUnconfirmed = 0,+                    dBAccountBalanceCoins = 0,+                    dBAccountCreated = time+                  }+          key <- lift $ P.insert account+          return (key, account)++deleteAccount :: (MonadUnliftIO m) => DBAccountId -> DB m ()+deleteAccount accId@(DBAccountKey accWallet accDeriv) = do+  delete . from $ \a ->+    where_ $+      a ^. DBAddressAccountWallet ==. val accWallet+        &&. a ^. DBAddressAccountDerivation ==. val accDeriv+  delete . from $ \t ->+    where_ $+      t ^. DBTxInfoAccountWallet ==. val accWallet+        &&. t ^. DBTxInfoAccountDerivation ==. val accDeriv+  delete . from $ \c ->+    where_ $+      c ^. DBCoinAccountWallet ==. val accWallet+        &&. c ^. DBCoinAccountDerivation ==. val accDeriv+  delete . from $ \p ->+    where_ $+      p ^. DBPendingTxAccountWallet ==. val accWallet+        &&. p ^. DBPendingTxAccountDerivation ==. val accDeriv+  P.delete accId++-- When a name is provided, get that account or throw an error if it doesn't+-- exist. When no name is provided, return the account only if there is one+-- account.+getAccountByName ::+  (MonadUnliftIO m) =>+  Maybe Text ->+  ExceptT String (DB m) (DBAccountId, DBAccount)+getAccountByName (Just name) = do+  aM <- lift $ P.getBy $ UniqueName name+  case aM of+    Just a -> return (entityKey a, entityVal a)+    _ -> throwError $ "The account " <> cs name <> " does not exist"+getAccountByName Nothing = do+  as <- lift getAccounts+  case as of+    [a] -> return a+    [] -> throwError "There are no accounts in the wallet"+    _ -> throwError "Specify which account to use"++getAccountById ::+  (MonadUnliftIO m) => DBAccountId -> ExceptT String (DB m) DBAccount+getAccountById accId = liftMaybe "Invalid account" =<< lift (P.get accId)++getAccounts :: (MonadUnliftIO m) => DB m [(DBAccountId, DBAccount)]+getAccounts =+  (go <$>)+    <$> P.selectList+      []+      [P.Asc DBAccountWallet, P.Asc DBAccountNetwork, P.Asc DBAccountIndex]+  where+    go a = (entityKey a, entityVal a)++getAccountNames :: (MonadUnliftIO m) => DB m [Text]+getAccountNames = do+  res <- select . from $ \a -> do+    orderBy [asc $ a ^. DBAccountCreated]+    return $ a ^. DBAccountName+  return $ unValue <$> res++renameAccount ::+  (MonadUnliftIO m) => Text -> Text -> ExceptT String (DB m) DBAccount+renameAccount oldName newName+  | oldName == newName = throwError "Old and new names are the same"+  | otherwise = do+      e <- lift $ existsAccount newName+      if e+        then throwError $ "The account " <> cs newName <> " already exists"+        else do+          c <-+            lift . updateCount $ \a -> do+              set a [DBAccountName =. val newName]+              where_ $ a ^. DBAccountName ==. val oldName+          if c == 0+            then throwError $ "The account " <> cs oldName <> " does not exist"+            else snd <$> getAccountByName (Just newName)++updateAccountBalances :: (MonadUnliftIO m) => DBAccountId -> DB m DBAccount+updateAccountBalances accId@(DBAccountKey wallet accDeriv) = do+  confirm <- selectSum DBAddressBalanceConfirmed+  unconfirm <- selectSum DBAddressBalanceUnconfirmed+  coins <- selectSum DBAddressBalanceCoins+  update $ \a -> do+    set+      a+      [ DBAccountBalanceConfirmed =. val (unpack confirm),+        DBAccountBalanceUnconfirmed =. val (unpack unconfirm),+        DBAccountBalanceCoins =. val (unpack coins)+      ]+    where_ $ a ^. DBAccountId ==. val accId+  fromJust <$> P.get accId+  where+    unpack m = fromMaybe 0 $ unValue $ fromMaybe (Value $ Just 0) m+    selectSum field =+      selectOne . from $ \a -> do+        where_ $+          a ^. DBAddressAccountWallet ==. val wallet+            &&. a ^. DBAddressAccountDerivation ==. val accDeriv+        return $ sum_ $ a ^. field++{- Addresses -}++data AddrType = AddrInternal | AddrExternal+  deriving (Eq, Show)++isInternal :: AddrType -> Bool+isInternal AddrInternal = True+isInternal AddrExternal = False++dBAccountCount :: AddrType -> DBAccount -> Int+dBAccountCount AddrInternal = dBAccountInternal+dBAccountCount AddrExternal = dBAccountExternal++dBAccountField :: AddrType -> EntityField DBAccount Int+dBAccountField AddrInternal = DBAccountInternal+dBAccountField AddrExternal = DBAccountExternal++addrDeriv :: AddrType -> SoftPath+addrDeriv AddrInternal = intDeriv+addrDeriv AddrExternal = extDeriv++data AddrFree = AddrFree | AddrBusy+  deriving (Eq, Show)++isAddrFree :: AddrFree -> Bool+isAddrFree AddrFree = True+isAddrFree AddrBusy = False++instance ToJSON DBAddress where+  toJSON addr =+    object+      [ "index" .= dBAddressIndex addr,+        "wallet" .= dBAddressAccountWallet addr,+        "account" .= dBAddressAccountDerivation addr,+        "derivation" .= dBAddressDerivation addr,+        "address" .= dBAddressAddress addr,+        "label" .= dBAddressLabel addr,+        "balance"+          .= AddressBalance+            (dBAddressBalanceConfirmed addr)+            (dBAddressBalanceUnconfirmed addr)+            (dBAddressBalanceCoins addr)+            (dBAddressBalanceTxs addr)+            (dBAddressBalanceReceived addr),+        "internal" .= dBAddressInternal addr,+        "free" .= dBAddressFree addr,+        "created" .= dBAddressCreated addr+      ]++instance FromJSON DBAddress where+  parseJSON =+    withObject "DBAddress" $ \o -> do+      bal <- o .: "balance"+      DBAddress+        <$> o .: "index"+        <*> o .: "wallet"+        <*> o .: "account"+        <*> o .: "derivation"+        <*> o .: "address"+        <*> o .: "label"+        <*> bal .: "confirmed"+        <*> bal .: "unconfirmed"+        <*> bal .: "coins"+        <*> bal .: "txs"+        <*> bal .: "received"+        <*> o .: "internal"+        <*> o .: "free"+        <*> o .: "created"++data AddressBalance = AddressBalance+  { -- | confirmed balance+    addrBalanceConfirmed :: !Word64,+    -- | unconfirmed balance+    addrBalanceUnconfirmed :: !Word64,+    -- | number of unspent outputs+    addrBalanceCoins :: !Word64,+    -- | number of transactions+    addrBalanceTxs :: !Word64,+    -- | total amount from all outputs in this address+    addrBalanceReceived :: !Word64+  }+  deriving (Show, Read, Eq, Ord)++instance ToJSON AddressBalance where+  toJSON b =+    object+      [ "confirmed" .= addrBalanceConfirmed b,+        "unconfirmed" .= addrBalanceUnconfirmed b,+        "coins" .= addrBalanceCoins b,+        "txs" .= addrBalanceTxs b,+        "received" .= addrBalanceReceived b+      ]++instance FromJSON AddressBalance where+  parseJSON =+    withObject "accountbalance" $ \o ->+      AddressBalance+        <$> o .: "confirmed"+        <*> o .: "unconfirmed"+        <*> o .: "coins"+        <*> o .: "txs"+        <*> o .: "received"++updateAddressBalances ::+  (MonadUnliftIO m) =>+  Network ->+  [Store.Balance] ->+  ExceptT String (DB m) ()+updateAddressBalances net storeBals =+  forM_ storeBals $ \s -> do+    addrT <-+      liftEither $ maybeToEither "Invalid Address" (addrToText net s.address)+    lift . update $ \a -> do+      set+        a+        [ DBAddressBalanceConfirmed =. val s.confirmed,+          DBAddressBalanceUnconfirmed =. val s.unconfirmed,+          DBAddressBalanceCoins =. val s.utxo,+          DBAddressBalanceTxs =. val s.txs,+          DBAddressBalanceReceived =. val s.received+        ]+      where_ $ a ^. DBAddressAddress ==. val addrT++insertAddress ::+  (MonadUnliftIO m) =>+  Network ->+  DBAccountId ->+  SoftPath ->+  Address ->+  AddrFree ->+  ExceptT String (DB m) DBAddress+insertAddress net (DBAccountKey wallet accDeriv) deriv addr free = do+  time <- liftIO getCurrentTime+  addrT <- liftEither $ maybeToEither "Invalid Address" (addrToText net addr)+  let label = if isIntPath deriv then "Internal Address" else ""+      derivS = cs $ pathToStr deriv+      dbAddr =+        DBAddress+          (fromIntegral $ pathIndex deriv)+          wallet+          accDeriv+          derivS+          addrT+          label+          0+          0+          0+          0+          0+          (isIntPath deriv)+          (isAddrFree free)+          time+  lift $ P.insert_ dbAddr+  return dbAddr++-- This is an internal function+genNextAddress ::+  (MonadUnliftIO m) =>+  Ctx ->+  Config ->+  DBAccountId ->+  AddrType ->+  AddrFree ->+  Bool ->+  ExceptT String (DB m) DBAddress+genNextAddress ctx cfg accId addrType addrFree testGap = do+  acc <- getAccountById accId+  let net = accountNetwork acc+      pub = accountXPubKey ctx acc+      nextIdx = dBAccountCount addrType acc+      deriv = addrDeriv addrType+  when testGap $ checkGap cfg accId nextIdx addrType+  let (addr, _) = derivePathAddr ctx pub deriv (fromIntegral nextIdx)+  dbAddr <-+    insertAddress net accId (deriv :/ fromIntegral nextIdx) addr addrFree+  lift $ P.update accId [dBAccountField addrType P.=. nextIdx + 1]+  return dbAddr++checkGap ::+  (MonadUnliftIO m) =>+  Config ->+  DBAccountId ->+  Int ->+  AddrType ->+  ExceptT String (DB m) ()+checkGap cfg accId addrIdx addrType = do+  let gap = configGap cfg+  usedIdxM <- lift $ bestAddrWithFunds accId addrType+  let usedIdx = maybe 0 (+ 1) usedIdxM+  when (addrIdx >= usedIdx + fromIntegral gap) $+    throwError $+      "Can not generate addresses beyond the gap of " <> show gap++-- Highest address with a positive transaction count+bestAddrWithFunds ::+  (MonadUnliftIO m) => DBAccountId -> AddrType -> DB m (Maybe Int)+bestAddrWithFunds (DBAccountKey wallet accDeriv) addrType = do+  resM <- (flatMaybe <$>) . selectOne . from $ \a -> do+    where_ $+      a ^. DBAddressAccountWallet ==. val wallet+        &&. a ^. DBAddressAccountDerivation ==. val accDeriv+        &&. a ^. DBAddressInternal ==. val (isInternal addrType)+        &&. a ^. DBAddressBalanceTxs >. val 0+    return $ max_ $ a ^. DBAddressIndex+  return $ fromIntegral <$> resM++-- Generate the discovered external and internal addresses+discoverAccGenAddrs ::+  (MonadUnliftIO m) =>+  Ctx ->+  Config ->+  DBAccountId ->+  AddrType ->+  Int ->+  ExceptT String (DB m) ()+discoverAccGenAddrs ctx cfg accId addrType newAddrCnt = do+  acc <- getAccountById accId+  let oldAddrCnt = dBAccountCount addrType acc+      cnt = max 0 $ newAddrCnt - oldAddrCnt+  -- False: Don't check the gap while discovering+  replicateM_ cnt $ genNextAddress ctx cfg accId addrType AddrBusy False++genExtAddress ::+  (MonadUnliftIO m) =>+  Ctx ->+  Config ->+  DBAccountId ->+  Text ->+  ExceptT String (DB m) DBAddress+genExtAddress ctx cfg accId label = do+  -- True: check the gap+  addr <- genNextAddress ctx cfg accId AddrExternal AddrBusy True+  setAddrLabel accId (dBAddressIndex addr) label++setAddrLabel ::+  (MonadUnliftIO m) =>+  DBAccountId ->+  Int ->+  Text ->+  ExceptT String (DB m) DBAddress+setAddrLabel accId@(DBAccountKey wallet accDeriv) idx label = do+  acc <- getAccountById accId+  let path = cs $ pathToStr $ extDeriv :/ fromIntegral idx+      aKey = DBAddressKey wallet accDeriv path+  unless (fromIntegral idx < dBAccountExternal acc) $+    throwError $+      "Address " <> show idx <> " does not exist"+  lift $ P.updateGet aKey [DBAddressLabel P.=. label]++nextFreeIntAddr ::+  (MonadUnliftIO m) =>+  Ctx ->+  Config ->+  DBAccountId ->+  ExceptT String (DB m) DBAddress+nextFreeIntAddr ctx cfg accId@(DBAccountKey wallet accDeriv) = do+  resM <- lift . selectOne . from $ \a -> do+    where_ $+      a ^. DBAddressAccountWallet ==. val wallet+        &&. a ^. DBAddressAccountDerivation ==. val accDeriv+        &&. a ^. DBAddressInternal ==. val True+        &&. a ^. DBAddressFree ==. val True+    orderBy [asc $ a ^. DBAddressIndex]+    limit 1+    return a+  case resM of+    Just (Entity _ a) -> return a+    -- True: Check the gap+    Nothing -> genNextAddress ctx cfg accId AddrInternal AddrFree True++fromDBAddr :: Network -> DBAddress -> Either String (Address, SoftPath)+fromDBAddr net addrDB = do+  let addrT = dBAddressAddress addrDB+      derivT = dBAddressDerivation addrDB+  deriv <- maybeToEither "fromDBAddress deriv" $ parseSoft $ cs derivT+  addr <- maybeToEither "fromDBAddress addr" $ textToAddr net addrT+  return (addr, deriv)++setAddrsFree :: (MonadUnliftIO m) => AddrFree -> [Text] -> DB m Natural+setAddrsFree free addrs = do+  (fromIntegral <$>) . updateCount $ \a -> do+    set a [DBAddressFree =. val (isAddrFree free)]+    where_ $+      a ^. DBAddressAddress `in_` valList addrs+        &&. (a ^. DBAddressBalanceTxs ==. val 0)++addressPage :: (MonadUnliftIO m) => DBAccountId -> Page -> DB m [DBAddress]+addressPage (DBAccountKey wallet accDeriv) (Page lim off) = do+  as <-+    select $+      from $ \a -> do+        where_ $+          a ^. DBAddressAccountWallet ==. val wallet+            &&. a ^. DBAddressAccountDerivation ==. val accDeriv+            &&. a ^. DBAddressInternal ==. val False+        orderBy [desc (a ^. DBAddressIndex)]+        limit $ fromIntegral lim+        offset $ fromIntegral off+        return a+  return $ entityVal <$> as++allAddressesMap ::+  (MonadUnliftIO m) =>+  Network ->+  DBAccountId ->+  ExceptT String (DB m) (Map Address SoftPath, Map Address AddressBalance)+allAddressesMap net (DBAccountKey wallet accDeriv) = do+  dbRes <-+    lift . select $+      from $ \a -> do+        where_ $+          a ^. DBAddressAccountWallet ==. val wallet+            &&. a ^. DBAddressAccountDerivation ==. val accDeriv+        return a+  res <-+    forM dbRes $ \(Entity _ dbAddr) -> do+      a <- liftEither $ textToAddrE net $ dBAddressAddress dbAddr+      d <-+        liftEither . maybeToEither "parsePath failed" $+          parseSoft . cs $+            dBAddressDerivation dbAddr+      let b =+            AddressBalance+              (dBAddressBalanceConfirmed dbAddr)+              (dBAddressBalanceUnconfirmed dbAddr)+              (dBAddressBalanceCoins dbAddr)+              (dBAddressBalanceTxs dbAddr)+              (dBAddressBalanceReceived dbAddr)+      return ((a, d), (a, b))+  return (Map.fromList $ fst <$> res, Map.fromList $ snd <$> res)++getCoinDeriv ::+  (MonadUnliftIO m) =>+  Network ->+  DBAccountId ->+  Store.Unspent ->+  DB m (Either String SoftPath)+getCoinDeriv net accId unspent =+  runExceptT $ do+    addr <-+      liftEither . maybeToEither "getCoinDeriv: no address" $ unspent.address+    liftEither <=< lift $ getAddrDeriv net accId addr++getAddrDeriv ::+  (MonadUnliftIO m) =>+  Network ->+  DBAccountId ->+  Address ->+  DB m (Either String SoftPath)+getAddrDeriv net (DBAccountKey wallet accDeriv) addr =+  runExceptT $ do+    addrT <-+      liftEither . maybeToEither "getAddrDeriv: no address" $ addrToText net addr+    derivM <-+      lift . selectOne . from $ \a -> do+        where_ $+          a ^. DBAddressAddress ==. val addrT+            &&. a ^. DBAddressAccountWallet ==. val wallet+            &&. a ^. DBAddressAccountDerivation ==. val accDeriv+        return $ a ^. DBAddressDerivation+    liftEither . maybeToEither "getAddrDeriv: no derivation" $+      parseSoft . cs . unValue =<< derivM++{- Transactions -}++getConfirmedTxs ::+  (MonadUnliftIO m) => DBAccountId -> Bool -> ExceptT String (DB m) [TxHash]+getConfirmedTxs (DBAccountKey wallet accDeriv) confirm = do+  ts <-+    lift . select $+      from $ \t -> do+        where_ $+          t ^. DBTxInfoAccountWallet ==. val wallet+            &&. t ^. DBTxInfoAccountDerivation ==. val accDeriv+            &&. t ^. DBTxInfoConfirmed ==. val confirm+        orderBy [asc (t ^. DBTxInfoBlockRef)]+        return $ t ^. DBTxInfoTxid+  forM ts $ \(Value t) ->+    liftEither $ maybeToEither "getConfirmedTxs invalid TxHash" $ hexToTxHash t++-- Insert a new transaction or replace it, if it already exists+-- Returns True if there was a change or an insert+repsertTxInfo ::+  (MonadUnliftIO m) =>+  Network ->+  Ctx ->+  DBAccountId ->+  TxInfo ->+  ExceptT String (DB m) (DBTxInfo, Bool)+repsertTxInfo net ctx accId txInfo = do+  time <- liftIO getCurrentTime+  tid <- liftEither $ maybeToEither "TxId" $ txInfoHash txInfo+  let confirmed' = Store.confirmed $ txInfoBlockRef txInfo+      tidT = txHashToHex tid+      bRef = S.encode $ txInfoBlockRef txInfo+      -- Confirmations will get updated when retrieving them+      blob =+        BS.toStrict $+          marshalJSON+            (net, ctx)+            txInfo+              { txInfoConfirmations = 0,+                txInfoPending = Nothing+              }+      (DBAccountKey wallet accDeriv) = accId+      key = DBTxInfoKey wallet accDeriv tidT+      dbInfo =+        DBTxInfo+          { dBTxInfoAccountWallet = wallet,+            dBTxInfoAccountDerivation = accDeriv,+            dBTxInfoTxid = tidT,+            dBTxInfoBlockRef = bRef,+            dBTxInfoConfirmed = confirmed',+            dBTxInfoBlob = blob,+            dBTxInfoCreated = time+          }+  prevM <- lift $ P.get key+  case prevM of+    Just prev -> do+      let newDBInfo =+            prev+              { dBTxInfoBlockRef = bRef,+                dBTxInfoConfirmed = confirmed',+                dBTxInfoBlob = blob+              }+      lift $ P.replace key newDBInfo+      return (newDBInfo, prev /= newDBInfo)+    Nothing -> do+      lift $ P.insert_ dbInfo+      return (dbInfo, True)++txsPage ::+  (MonadUnliftIO m) =>+  Ctx ->+  DBAccountId ->+  Page ->+  ExceptT String (DB m) [TxInfo]+txsPage ctx accId@(DBAccountKey wallet accDeriv) (Page lim off) = do+  acc <- getAccountById accId+  let net = accountNetwork acc+  dbTxs <-+    lift . select . from $ \t -> do+      where_ $+        t ^. DBTxInfoAccountWallet ==. val wallet+          &&. t ^. DBTxInfoAccountDerivation ==. val accDeriv+      orderBy [asc (t ^. DBTxInfoBlockRef)]+      limit $ fromIntegral lim+      offset $ fromIntegral off+      return $ t ^. DBTxInfoBlob+  res <-+    forM dbTxs $ \(Value dbTx) -> do+      liftMaybe "TxInfo unmarshalJSON Failed" $+        unmarshalJSON (net, ctx) $+          BS.fromStrict dbTx+  resLabels <- lift $ mapM (fillTxInfoLabels net) res+  bestM <- lift $ getBest net+  return $ updateConfirmations (snd <$> bestM) <$> resLabels+  where+    updateConfirmations bestM tif =+      tif {txInfoConfirmations = getConfirmations bestM (txInfoBlockRef tif)}++getConfirmations :: Maybe BlockHeight -> Store.BlockRef -> Natural+getConfirmations _ (Store.MemRef _) = 0+getConfirmations Nothing (Store.BlockRef _ _) = 1+getConfirmations (Just best) (Store.BlockRef height _)+  | best < height = 1+  | otherwise = fromIntegral $ best - height + 1++fillTxInfoLabels :: (MonadUnliftIO m) => Network -> TxInfo -> DB m TxInfo+fillTxInfoLabels net txInfo = do+  o <- mapM fillOutput $ Map.assocs $ txInfoMyOutputs txInfo+  i <- mapM fillInput $ Map.assocs $ txInfoMyInputs txInfo+  return $+    txInfo+      { txInfoMyOutputs = Map.fromList o,+        txInfoMyInputs = Map.fromList i+      }+  where+    fillOutput (a, o) = do+      resM <- runMaybeT $ do+        addrT <- hoistMaybe $ addrToText net a+        (Entity _ dbAddr) <- hoistMaybe =<< lift (P.getBy $ UniqueAddress addrT)+        return (a, o {myOutputsLabel = dBAddressLabel dbAddr})+      return $ fromMaybe (a, o) resM+    fillInput (a, i) = do+      resM <- runMaybeT $ do+        addrT <- hoistMaybe $ addrToText net a+        (Entity _ dbAddr) <- hoistMaybe =<< lift (P.getBy $ UniqueAddress addrT)+        return (a, i {myInputsLabel = dBAddressLabel dbAddr})+      return $ fromMaybe (a, i) resM++{- Coins -}++data JsonCoin = JsonCoin+  { jsonCoinOutpoint :: !OutPoint,+    jsonCoinAddress :: !Address,+    jsonCoinValue :: !Word64,+    jsonCoinBlock :: !Store.BlockRef,+    jsonCoinConfirmations :: !Natural,+    jsonCoinLocked :: !Bool+  }+  deriving (Eq, Show)++instance MarshalJSON Network JsonCoin where+  marshalValue net c =+    object+      [ "outpoint" .= jsonCoinOutpoint c,+        "address" .= marshalValue net (jsonCoinAddress c),+        "value" .= jsonCoinValue c,+        "block" .= jsonCoinBlock c,+        "confirmations" .= jsonCoinConfirmations c,+        "locked" .= jsonCoinLocked c+      ]+  unmarshalValue net =+    withObject "JsonCoin" $ \o ->+      JsonCoin+        <$> o .: "outpoint"+        <*> (unmarshalValue net =<< o .: "address")+        <*> o .: "value"+        <*> o .: "block"+        <*> o .: "confirmations"+        <*> o .: "locked"++toJsonCoin :: Network -> Maybe BlockHeight -> DBCoin -> Either String JsonCoin+toJsonCoin net bestM dbCoin = do+  op <- textToOutpoint $ dBCoinOutpoint dbCoin+  ad <-+    maybeToEither "toJsonCoin: Invalid address" $+      textToAddr net $+        dBCoinAddress dbCoin+  br <- S.decode $ dBCoinBlockRef dbCoin+  let confirmations = getConfirmations bestM br+  return $+    JsonCoin+      { jsonCoinOutpoint = op,+        jsonCoinAddress = ad,+        jsonCoinValue = dBCoinValue dbCoin,+        jsonCoinBlock = br,+        jsonCoinConfirmations = confirmations,+        jsonCoinLocked = dBCoinLocked dbCoin+      }++outpointText :: OutPoint -> Text+outpointText = encodeHex . S.encode++textToOutpoint :: Text -> Either String OutPoint+textToOutpoint t = do+  bs <- maybeToEither "textToOutpoint: invalid input" $ decodeHex t+  S.decode bs++-- Get all coins in an account, spendable or not+coinPage ::+  (MonadUnliftIO m) =>+  Network ->+  DBAccountId ->+  Page ->+  ExceptT String (DB m) [JsonCoin]+coinPage net (DBAccountKey wallet accDeriv) (Page lim off) = do+  coins <-+    lift . select . from $ \c -> do+      where_ $ do+        c ^. DBCoinAccountWallet ==. val wallet+          &&. c ^. DBCoinAccountDerivation ==. val accDeriv+      orderBy [asc (c ^. DBCoinBlockRef), desc (c ^. DBCoinCreated)]+      limit $ fromIntegral lim+      offset $ fromIntegral off+      return c+  bestM <- lift $ getBest net+  mapM (liftEither . toJsonCoin net (snd <$> bestM) . entityVal) coins++-- Spendable coins must be confirmed and not locked+getSpendableCoins ::+  (MonadUnliftIO m) =>+  Network ->+  DBAccountId ->+  Natural ->+  ExceptT String (DB m) [Store.Unspent]+getSpendableCoins net (DBAccountKey wallet accDeriv) minConf = do+  bestM <- lift $ getBest net+  coins <- lift . select . from $ \c -> do+    where_ $+      c ^. DBCoinAccountWallet ==. val wallet+        &&. c ^. DBCoinAccountDerivation ==. val accDeriv+        &&. c ^. DBCoinLocked ==. val False+    return c+  let f c = do+        ref <- liftEither $ S.decode $ dBCoinBlockRef c+        return $ getConfirmations (snd <$> bestM) ref >= minConf+  spendableCoins <- filterM (f . entityVal) coins+  let bss = dBCoinBlob . entityVal <$> spendableCoins+  mapM (liftEither . S.decode) bss++insertCoin ::+  (MonadUnliftIO m) =>+  DBAccountId ->+  Text ->+  Store.Unspent ->+  DB m DBCoin+insertCoin (DBAccountKey wallet accDeriv) addr unspent = do+  time <- liftIO getCurrentTime+  let newCoin =+        DBCoin+          { dBCoinAccountWallet = wallet,+            dBCoinAccountDerivation = accDeriv,+            dBCoinOutpoint = outpointText unspent.outpoint,+            dBCoinAddress = addr,+            dBCoinValue = unspent.value,+            dBCoinBlockRef = S.encode unspent.block,+            dBCoinBlob = S.encode unspent,+            dBCoinConfirmed = Store.confirmed unspent.block,+            dBCoinLocked = False,+            dBCoinCreated = time+          }+  P.insert_ newCoin+  return newCoin++updateCoin :: (MonadUnliftIO m) => Store.Unspent -> DB m ()+updateCoin unspent = do+  let key = DBCoinKey $ outpointText unspent.outpoint+  P.update+    key+    [ DBCoinBlob P.=. S.encode unspent,+      DBCoinConfirmed P.=. Store.confirmed unspent.block,+      DBCoinBlockRef P.=. S.encode unspent.block+    ]++deleteCoin :: (MonadUnliftIO m) => DBCoin -> DB m ()+deleteCoin coin =+  delete . from $ \c ->+    where_ $+      c ^. DBCoinOutpoint ==. val (dBCoinOutpoint coin)++getCoinsByAddr :: (MonadUnliftIO m) => Text -> DB m [DBCoin]+getCoinsByAddr addr = do+  coins <- select . from $ \c -> do+    where_ $ c ^. DBCoinAddress ==. val addr+    return c+  return $ entityVal <$> coins++-- This is the main coin function+-- Either insert, update or delete coins as required. Returns the number of+-- coins that have either been inserted, updated or deleted.+refreshCoins ::+  (MonadUnliftIO m) =>+  Network ->+  DBAccountId ->+  [Address] ->+  [Store.Unspent] ->+  ExceptT String (DB m) (Int, [DBCoin])+refreshCoins net accId addrsToUpdate allUnspent = do+  let storeMap = groupCoins net allUnspent+  addrsToUpdateE <-+    mapM (liftEither . maybeToEither "Addr" . addrToText net) addrsToUpdate+  res <- forM addrsToUpdateE $ \addr -> do+    let storeCoins = fromMaybe [] $ Map.lookup addr storeMap+    localCoins <- lift $ getCoinsByAddr addr+    let storeOps = outpointText . (.outpoint) <$> storeCoins+        localOps = dBCoinOutpoint <$> localCoins+        toDelete = filter ((`notElem` storeOps) . dBCoinOutpoint) localCoins+        toInsert =+          filter ((`notElem` localOps) . outpointText . (.outpoint)) storeCoins+        toUpdate = filter (f localCoins) storeCoins+    lift $ forM_ toDelete deleteCoin+    lift $ forM_ toUpdate updateCoin+    newCoins <- lift $ forM toInsert $ insertCoin accId addr+    return (length toDelete + length toUpdate + length toInsert, newCoins)+  return (sum $ fst <$> res, concatMap snd res)+  where+    f localCoins s =+      let cM = find ((== outpointText s.outpoint) . dBCoinOutpoint) localCoins+       in case cM of+            Just c -> dBCoinBlockRef c /= S.encode s.block+            _ -> False++groupCoins :: Network -> [Store.Unspent] -> Map Text [Store.Unspent]+groupCoins net =+  Map.fromListWith (<>) . mapMaybe f+  where+    f x =+      case x.address of+        Just a -> (,[x]) <$> addrToText net a+        _ -> Nothing++setLockCoin :: (MonadUnliftIO m) => OutPoint -> Bool -> DB m Natural+setLockCoin op locked = do+  cnt <- updateCount $ \c -> do+    set c [DBCoinLocked =. val locked]+    where_ $+      c ^. DBCoinOutpoint ==. val (outpointText op)+        &&. c ^. DBCoinLocked ==. val (not locked)+  return $ fromIntegral cnt++{- Raw Transactions -}++insertRawTx :: (MonadUnliftIO m) => Tx -> DB m ()+insertRawTx tx = do+  let hash = txHashToHex $ txHash tx+      key = DBRawTxKey hash+  P.repsert key $ DBRawTx hash (S.encode tx)++getRawTx :: (MonadUnliftIO m) => TxHash -> ExceptT String (DB m) Tx+getRawTx hash = do+  let key = DBRawTxKey $ txHashToHex hash+  txM <- lift $ P.get key+  case txM of+    Just tx -> liftEither . S.decode $ dBRawTxBlob tx+    Nothing -> throwError "getRawTx: missing transaction"++{- Pending Transactions -}++data TxOnline = TxOnline | TxOffline+  deriving (Eq, Show)++-- Returns (TxSignData, isOnline)+getPendingTx ::+  (MonadUnliftIO m) =>+  TxHash ->+  DB m (Maybe (DBAccountId, TxSignData, TxInfoPending))+getPendingTx nosigHash = do+  let hashT = txHashToHex nosigHash+      key = DBPendingTxKey hashT+  resM <- P.get key+  case resM of+    Just (DBPendingTx wallet accDeriv _ blob online _) -> do+      let accId = DBAccountKey wallet accDeriv+          tsdM = Json.decode $ BS.fromStrict blob+      return $ do+        tsd <- tsdM+        let pending =+              TxInfoPending+                (nosigTxHash $ txSignDataTx tsd)+                (txSignDataSigned tsd)+                online+        return (accId, tsd, pending)+    _ -> return Nothing++pendingTxPage ::+  (MonadUnliftIO m) =>+  Ctx ->+  DBAccountId ->+  Page ->+  ExceptT String (DB m) [TxInfo]+pendingTxPage ctx accId@(DBAccountKey wallet accDeriv) (Page lim off) = do+  acc <- getAccountById accId+  let pub = accountXPubKey ctx acc+      net = accountNetwork acc+  tsds <-+    lift . select . from $ \p -> do+      where_ $ do+        p ^. DBPendingTxAccountWallet ==. val wallet+          &&. p ^. DBPendingTxAccountDerivation ==. val accDeriv+      orderBy [desc (p ^. DBPendingTxCreated)]+      limit $ fromIntegral lim+      offset $ fromIntegral off+      return p+  forM tsds $ \(Entity _ res) -> do+    tsd <- liftEither . Json.eitherDecode . BS.fromStrict $ dBPendingTxBlob res+    txInfo <- liftEither $ parseTxSignData net ctx pub tsd+    let nosigHash = nosigTxHash $ txSignDataTx tsd+        pending =+          TxInfoPending nosigHash (txSignDataSigned tsd) (dBPendingTxOnline res)+    let txInfoP = txInfo {txInfoPending = Just pending}+    lift $ fillTxInfoLabels net txInfoP++-- Returns the TxHash and NoSigHash of pending transactions. They are compared+-- during a sync in order to delete pending transactions that are now online.+pendingTxHashes ::+  (MonadUnliftIO m) =>+  DBAccountId ->+  ExceptT String (DB m) [(TxHash, DBPendingTxId)]+pendingTxHashes (DBAccountKey wallet accDeriv) = do+  blobs <-+    lift . select . from $ \p -> do+      where_ $ do+        p ^. DBPendingTxAccountWallet ==. val wallet+          &&. p ^. DBPendingTxAccountDerivation ==. val accDeriv+      return (p ^. DBPendingTxBlob, p ^. DBPendingTxId)+  res <- forM blobs $ \(Value blob, Value key) -> do+    tsd <- liftEither . Json.eitherDecode $ BS.fromStrict blob+    if txSignDataSigned tsd+      then return [(txHash $ txSignDataTx tsd, key)]+      else return []+  return $ concat res++-- Imports a pending transaction, locks coins and locks internal addresses+importPendingTx ::+  (MonadUnliftIO m) =>+  Network ->+  Ctx ->+  DBAccountId ->+  TxSignData ->+  ExceptT String (DB m) TxHash+importPendingTx net ctx accId tsd@(TxSignData tx _ _ _ signed) = do+  acc <- getAccountById accId+  let pub = accountXPubKey ctx acc+      nosigHash = nosigTxHash $ txSignDataTx tsd+      bs = BS.toStrict $ Json.encode tsd+  prevM <- lift $ getPendingTx nosigHash+  case prevM of+    Just (_, TxSignData prevTx _ _ _ prevSigned, pending) -> do+      when (pendingOnline pending) $+        throwError "The transaction is already online"+      when (prevSigned && not signed) $+        throwError "Can not replace a signed transaction with an unsigned one"+      when (prevTx == tx) $+        throwError "The transaction already exists"+      when (not prevSigned && signed) $ do+        let key = DBPendingTxKey $ txHashToHex nosigHash+        lift $ P.update key [DBPendingTxBlob P.=. bs]+      return nosigHash+    Nothing -> do+      txInfoU <- liftEither $ parseTxSignData net ctx pub tsd+      let (outpoints, outIntAddrs, restAddrs) = parseTxInfoU txInfoU+      -- Verify coins and lock them+      forM_ outpoints $ \outpoint -> do+        coinM <- lift $ P.get $ DBCoinKey $ outpointText outpoint+        case coinM of+          Just coin -> do+            when (dBCoinLocked coin) $+              throwError "A coin referenced by the transaction is locked"+            lift $ setLockCoin outpoint True+          _ -> throwError "A coin referenced by the transaction does not exist"+      -- Verify addresses and set internal output addresses to busy+      outIntAddrsT <- mapM (liftMaybe "Addrs" . addrToText net . fst) outIntAddrs+      restAddrsT <- mapM (liftMaybe "Addrs" . addrToText net) restAddrs+      outIntAddrsE <- lift . select . from $ \a -> do+        where_ $ a ^. DBAddressAddress `in_` valList outIntAddrsT+        return a+      restAddrsE <- lift . select . from $ \a -> do+        where_ $ a ^. DBAddressAddress `in_` valList restAddrsT+        return a+      when+        ( length (outIntAddrsT <> restAddrsT)+            /= length (outIntAddrsE <> restAddrsE)+        )+        $ throwError "Some referenced addresses do not exist"+      unless (all (dBAddressFree . entityVal) outIntAddrsE) $+        throwError "Some of the internal output addresses are not free"+      -- Set the output internal addresses to not free+      _ <- lift $ setAddrsFree AddrBusy outIntAddrsT+      -- Insert the pending transaction+      time <- liftIO getCurrentTime+      let ptx =+            DBPendingTx+              (dBAccountWallet acc)+              (dBAccountDerivation acc)+              (txHashToHex nosigHash)+              bs+              False+              time+      lift $ P.insert_ ptx+      return nosigHash++-- (Outpoints, external addrs, internal addrs)+parseTxInfoU :: TxInfo -> ([OutPoint], [(Address, KeyIndex)], [Address])+parseTxInfoU TxInfo {..} =+  (outpoints, nub $ f <$> outIntAddrs, nub $ fst <$> restAddrs)+  where+    outpoints = (.outpoint) <$> concatMap myInputsSigInput (Map.elems txInfoMyInputs)+    outAddrs = second myOutputsPath <$> Map.assocs txInfoMyOutputs+    inAddrs = second myInputsPath <$> Map.assocs txInfoMyInputs+    (outIntAddrs, outExtAddrs) = partition (isIntPath . snd) outAddrs+    restAddrs = outExtAddrs <> inAddrs+    f (a, p) =+      case pathToList p of+        (_ : i : _) -> (a, i)+        _ -> error "parseTxInfoU"++-- Delete a pending transaction, unlocks coins and frees internal addresses+deletePendingTx ::+  (MonadUnliftIO m) =>+  Ctx ->+  TxHash ->+  ExceptT String (DB m) (Natural, Natural)+deletePendingTx ctx nosigHash = do+  let key = DBPendingTxKey $ txHashToHex nosigHash+  tsdM <- lift $ getPendingTx nosigHash+  case tsdM of+    Just (_, _, TxInfoPending _ _ True) -> do+      throwError+        "This pending transaction has been sent to the network.\+        \ Run syncacc to refresh your database."+    -- We only free coins and addresses if the transaction is offline+    Just (accId, tsd, _) -> do+      acc <- getAccountById accId+      let net = accountNetwork acc+          pub = accountXPubKey ctx acc+      txInfoU <- liftEither $ parseTxSignData net ctx pub tsd+      let (outpoints, outIntAddrs, _) = parseTxInfoU txInfoU+      outIntAddrsT <-+        mapM (liftMaybe "Address" . addrToText net . fst) outIntAddrs+      freedCoins <- forM outpoints $ \op -> lift $ setLockCoin op False+      freedAddresses <- lift $ setAddrsFree AddrFree outIntAddrsT+      lift $ P.delete key+      return (sum freedCoins, fromIntegral freedAddresses)+    _ -> throwError "The pending transaction does not exist"++-- When the pending transaction is online, we just delete it+deletePendingTxOnline :: (MonadUnliftIO m) => DBPendingTxId -> DB m ()+deletePendingTxOnline = P.delete++setPendingTxOnline :: (MonadUnliftIO m) => TxHash -> DB m Natural+setPendingTxOnline nosigH = do+  let nosigHT = txHashToHex nosigH+  cnt <- updateCount $ \p -> do+    set p [DBPendingTxOnline =. val True]+    where_ $ p ^. DBPendingTxNosigHash ==. val nosigHT+  return $ fromIntegral cnt++{- Helpers -}++extDeriv :: SoftPath+extDeriv = Deriv :/ 0++intDeriv :: SoftPath+intDeriv = Deriv :/ 1++isExtPath :: SoftPath -> Bool+isExtPath p =+  case pathToList p of+    [0, _] -> True+    _ -> False++isIntPath :: SoftPath -> Bool+isIntPath p =+  case pathToList p of+    [1, _] -> True+    _ -> False++pathIndex :: SoftPath -> KeyIndex+pathIndex p =+  case pathToList p of+    [_, i] -> i+    _ -> error "Invalid pathIndex"++bip44Deriv :: Network -> Natural -> HardPath+bip44Deriv net a = Deriv :| 44 :| net.bip44Coin :| fromIntegral a++xPubIndex :: XPubKey -> Natural+xPubIndex = fromIntegral . xPubChild++addrsDerivPage :: Ctx -> SoftPath -> Page -> XPubKey -> [(Address, SoftPath)]+addrsDerivPage ctx deriv (Page lim off) xpub =+  fmap (\(a, _, i) -> (a, deriv :/ i)) addrs+  where+    addrs =+      take (fromIntegral lim) $+        derivePathAddrs ctx xpub deriv (fromIntegral off)++-- like `maybe` but for a maybe/value sandwich+joinMaybe :: b -> (a -> b) -> Maybe (E.Value (Maybe a)) -> b+joinMaybe d f m =+  case m of+    Just (Value m') -> maybe d f m'+    Nothing -> d++flatMaybe :: Maybe (E.Value (Maybe a)) -> Maybe a+flatMaybe = joinMaybe Nothing Just
+ src/Haskoin/Wallet/Entropy.hs view
@@ -0,0 +1,221 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}++module Haskoin.Wallet.Entropy where++import Control.Monad.IO.Class ( MonadIO, liftIO )+import Control.Monad (replicateM, unless, when)+import Control.Monad.Except+import Data.Bits (Bits (setBit, xor))+import qualified Data.ByteString as BS+import Data.List (foldl', nub)+import Data.Text (Text)+import Data.Word (Word8)+import Haskoin.Crypto (Mnemonic, toMnemonic, fromMnemonic)+import Haskoin.Util (bsToInteger)+import Haskoin.Wallet.Util (chunksOf)+import Numeric (showIntAtBase)+import Numeric.Natural (Natural)+import qualified System.Console.Haskeline as Haskeline+import qualified System.Console.Haskeline as Haskline+import qualified System.Directory as D+import System.Entropy (getEntropy)+import System.IO (IOMode (ReadMode), withBinaryFile)+import Text.Read (readMaybe)++-- Produces uniformily distributed dice rolls from a random byte+-- 0,   0x00 -> [6,6,6]+-- 1,   0x01 -> [6,6,1]+-- 215, 0xd7 -> [5,5,5]+-- 216, 0xd8 -> [6,6]+-- 217, 0xd9 -> [6,1]+-- 251, 0xfb -> [5,5]+-- Anything above 0xfb is dropped+word8ToBase6 :: Word8 -> [Natural]+word8ToBase6 w+  | w > 0xfb = [] -- Drop otherwise we don't have uniform distribution+  | w > 0xd7 =+      let res = f <$> encodeBase6 (BS.pack [w - 0xd8])+       in replicate (2 - length res) 6 <> res+  | otherwise =+      let res = f <$> encodeBase6 (BS.pack [w])+       in replicate (3 - length res) 6 <> res+  where+    f :: Char -> Natural+    f = read . (: [])+    b6Data :: String+    b6Data = "612345"+    b6 :: Int -> Char+    b6 = (b6Data !!)+    encodeBase6 :: BS.ByteString -> String+    encodeBase6 bs+      | BS.null bs = mempty+      | otherwise = showIntAtBase 6 b6 (bsToInteger bs) ""++-- Produces Bools using base6ToBool. Once 8 Bools have been+-- accumulated, it is transformed to a Word8 using boolsToWord8.+-- Any unused Bools are returned along with the Word8s.+base6ToWord8 :: [Natural] -> [Bool] -> ([Word8], [Bool])+base6ToWord8 ns obs+  | length bs < 8 = ([], bs)+  | otherwise =+      let (ws, rest) = f bs []+       in (boolsToWord8 <$> ws, rest)+  where+    bs = concatMap base6ToBool ns <> obs+    f :: [Bool] -> [[Bool]] -> ([[Bool]], [Bool])+    f xs acc+      | length xs < 8 = (acc, xs)+      | otherwise =+          let (l, r) = splitAt (length xs - 8) xs+           in f l ([r] <> acc)++-- Produce uniformily distributed bits from dice rolls+-- 6 -> [0,0]+-- 1 -> [0,1]+-- 2 -> [1,0]+-- 3 -> [1,1]+-- 4 -> [0]+-- 5 -> [1]+base6ToBool :: Natural -> [Bool]+base6ToBool =+  \case+    6 -> [False, False]+    1 -> [False, True]+    2 -> [True, False]+    3 -> [True, True]+    4 -> [False]+    5 -> [True]+    _ -> error "Invalid base6ToBool"++-- Big Endian+-- [T,F,F,F,F,F,F,F] -> 128+-- [F,F,F,F,F,F,F,T] -> 1+boolsToWord8 :: [Bool] -> Word8+boolsToWord8 xs+  | length xs /= 8 = error "Invalid boolsToWord8"+  | otherwise =+      foldl' setBit 0 ys+  where+    ys = fmap fst $ filter snd $ zip [0 ..] $ reverse xs++-- Packs bools into a ByteString+packBools :: [Bool] -> BS.ByteString+packBools bs+  | length bs `mod` 8 /= 0 = error "Invalid packBools length"+  | otherwise = BS.pack $ boolsToWord8 <$> chunksOf 8 bs++-- Mix entropy of same length by xoring them+xorBytes :: BS.ByteString -> BS.ByteString -> BS.ByteString+xorBytes ent1 ent2+  | BS.length ent1 == BS.length ent2 =+      BS.pack $ BS.zipWith xor ent1 ent2+  | otherwise = error "Entropy is not of the same length"++-- Split secret s into n parts+-- generate k1 .. k(n-1) randomly+-- compute kn = s xor k1 xor .. xor k(n-1)+-- return k1 .. kn+-- We have the property that s = k1 xor ... xor kn+splitEntropy :: Natural -> BS.ByteString -> IO [BS.ByteString]+splitEntropy n secret+  | n < 2 = error "splitEntropy invalid number"+  | otherwise = do+      ks <- replicateM (fromIntegral n - 1) (snd <$> systemEntropy len)+      return $ splitEntropyWith secret ks+  where+    len = fromIntegral $ BS.length secret++splitEntropyWith :: BS.ByteString -> [BS.ByteString] -> [BS.ByteString]+splitEntropyWith secret ks = foldl' xorBytes secret ks : ks++mergeMnemonicParts :: [Text] -> Either String Mnemonic+mergeMnemonicParts mnems+  | length mnems < 2 = Left "Only one mnemonic provided"+  | length (nub mnems) /= length mnems = Left "Two mnemonics are identical"+  | otherwise = do+      bs <- mapM fromMnemonic mnems+      let ent = foldl' xorBytes (head bs) (tail bs)+      toMnemonic ent++askInputDice :: IO Natural+askInputDice = do+  inputM <-+    Haskeline.runInputT Haskline.defaultSettings $+      Haskeline.getInputLine "Dice roll: "+  case readMaybe =<< inputM of+    Just n ->+      if n `elem` [1 .. 6]+        then return n+        else badInput+    _ -> badInput+  where+    badInput = do+      putStrLn "Invalid dice roll. Must be in range [1..6]"+      askInputDice++-- TODO: Ask the dice rolls in sequences of 5 or so+getDiceEntropy :: Natural -> IO BS.ByteString+getDiceEntropy ent = do+  putStrLn "Enter your dice rolls (from 1 to 6)"+  f [] []+  where+    f :: [Word8] -> [Bool] -> IO BS.ByteString+    f res acc+      | length res == fromIntegral ent =+          return $ BS.pack res+      | otherwise = do+          d <- askInputDice+          let (ws, bs) = base6ToWord8 [d] acc+          unless (null ws) $ do+            putStrLn $+              show (length res + length ws)+                <> "/"+                <> show ent+                <> " bytes collected"+          f (ws <> res) bs++-- Generate a mnemonic with optional dice entropy and key splitting+genMnemonic ::+  (MonadIO m) =>+  Natural ->+  Bool ->+  Natural ->+  ExceptT String m (Text, Mnemonic, [Mnemonic])+genMnemonic reqEnt reqDice splitIn+  | splitIn == 0 = throwError "Invalid split option"+  | reqEnt `elem` [16, 20 .. 32] = do+      (origEnt, sysEnt) <- liftIO $ systemEntropy reqEnt+      ent <-+        if reqDice+          then do+            diceEnt <- liftIO $ getDiceEntropy reqEnt+            return $ sysEnt `xorBytes` diceEnt+          else return sysEnt+      when (BS.length ent /= fromIntegral reqEnt) $+        throwError "Something went wrong with the entropy size"+      mnem <- liftEither $ toMnemonic ent+      if splitIn > 1+        then do+          ks <- liftIO $ splitEntropy splitIn ent+          unless (ent == foldl' xorBytes (head ks) (tail ks)) $+            throwError "Something went wrong while splitting the keys"+          splitMnems <- liftEither $ mapM toMnemonic ks+          unsplitMnem <- liftEither $ mergeMnemonicParts splitMnems+          unless (mnem == unsplitMnem) $+            throwError "Something went wrong while reconstructing the mnemonic"+          return (origEnt, mnem, splitMnems)+        else return (origEnt, mnem, [])+  | otherwise = throwError "The entropy value can only be in [16,20..32]"++systemEntropy :: Natural -> IO (Text, BS.ByteString)+systemEntropy bytes = do+  exists <- D.doesFileExist "/dev/random"+  if exists+    then ("/dev/random",) <$> devRandom bytes+    else ("System.Entropy.getEntropy",) <$> getEntropy (fromIntegral bytes)++devRandom :: Natural -> IO BS.ByteString+devRandom bytes =+  withBinaryFile "/dev/random" ReadMode (`BS.hGet` fromIntegral bytes)
+ src/Haskoin/Wallet/FileIO.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}++module Haskoin.Wallet.FileIO where++import Control.Applicative ((<|>))+import Control.Monad+import Control.Monad.Except+import Control.Monad.Reader (MonadIO (..))+import Data.Aeson+import Data.Aeson.Types (parseEither)+import qualified Data.ByteString.Char8 as C8+import Data.Maybe (mapMaybe)+import qualified Data.Serialize as S+import Data.String.Conversions (cs)+import Data.Text (Text)+import qualified Data.Text as Text+import Haskoin+import Haskoin.Wallet.Util+import qualified System.Directory as D+import qualified System.IO as IO++data PubKeyDoc = PubKeyDoc+  { documentPubKey :: !XPubKey,+    documentNetwork :: !Network,+    documentName :: !Text,+    documentWallet :: !Fingerprint+  }+  deriving (Eq, Show)++instance MarshalJSON Ctx PubKeyDoc where+  unmarshalValue ctx =+    withObject "pubkeydocument" $ \o -> do+      net <- maybe mzero return . netByName =<< o .: "network"+      PubKeyDoc+        <$> (unmarshalValue (net, ctx) =<< o .: "xpubkey")+        <*> return net+        <*> (o .: "name")+        <*> (o .: "wallet")++  marshalValue ctx (PubKeyDoc k net name wallet) =+    object+      [ "xpubkey" .= marshalValue (net, ctx) k,+        "network" .= net.name,+        "name" .= name,+        "wallet" .= wallet+      ]++data TxSignData = TxSignData+  { txSignDataTx :: !Tx,+    txSignDataInputs :: ![Tx],+    txSignDataInputPaths :: ![SoftPath],+    txSignDataOutputPaths :: ![SoftPath],+    txSignDataSigned :: !Bool+  }+  deriving (Eq, Show)++instance FromJSON TxSignData where+  parseJSON =+    withObject "txsigndata" $ \o -> do+      let f = eitherToMaybe . S.decode <=< decodeHex+      t <- maybe mzero return . f =<< o .: "tx"+      i <- maybe mzero return . mapM f =<< o .: "txinputs"+      TxSignData t i+        <$> o .: "inputpaths"+        <*> o .: "outputpaths"+        <*> o .: "signed"++instance ToJSON TxSignData where+  toJSON (TxSignData t i oi op s) =+    object+      [ "tx" .= encodeHex (S.encode t),+        "txinputs" .= (encodeHex . S.encode <$> i),+        "inputpaths" .= oi,+        "outputpaths" .= op,+        "signed" .= s+      ]++instance MarshalJSON Ctx TxSignData where+  marshalValue _ = toJSON+  unmarshalValue _ = parseJSON++checkPathFree :: (MonadIO m) => FilePath -> ExceptT String m ()+checkPathFree path = do+  exist <- liftIO $ D.doesPathExist path+  when exist $ throwError $ "Path '" <> path <> "' already exists"++-- JSON IO Helpers--++writeJsonFile :: FilePath -> Value -> IO ()+writeJsonFile filePath doc = C8.writeFile filePath $ encodeJsonPrettyLn doc++readJsonFile :: (FromJSON a) => FilePath -> IO (Either String a)+readJsonFile = eitherDecodeFileStrict'++writeMarshalFile :: (MarshalJSON s a) => s -> FilePath -> a -> IO ()+writeMarshalFile s filePath a = writeJsonFile filePath $ marshalValue s a++readMarshalFile :: (MarshalJSON s a) => s -> FilePath -> IO (Either String a)+readMarshalFile s filePath = do+  vE <- readJsonFile filePath+  return $ parseEither (unmarshalValue s) =<< vE++-- Parse wallet dump files for sweeping --++readFileWords :: FilePath -> IO [[Text]]+readFileWords fp = do+  strContents <- IO.readFile fp+  return $ removeComments $ Text.words <$> Text.lines (cs strContents)++parseSecKeysFile :: Network -> [[Text]] -> [SecKey]+parseSecKeysFile net =+  withParser $ \w -> (.key) <$> (fromWif net w <|> fromMiniKey (cs w))++withParser :: (Text -> Maybe a) -> [[Text]] -> [a]+withParser parser =+  mapMaybe go+  where+    go [] = Nothing+    go (w : ws) = parser w <|> go ws++removeComments :: [[Text]] -> [[Text]]+removeComments =+  mapMaybe go+  where+    go [] = Nothing+    go ws@(w : _)+      | "#" `Text.isPrefixOf` w = Nothing+      | otherwise = Just ws
+ src/Haskoin/Wallet/Main.hs view
@@ -0,0 +1,25 @@+module Haskoin.Wallet.Main where++import qualified Data.ByteString.Char8 as C8+import Haskoin (Ctx, MarshalJSON (..), withContext)+import Haskoin.Wallet.Commands (Response, commandResponse)+import Haskoin.Wallet.Config (initConfig)+import Haskoin.Wallet.Database (runDB)+import Haskoin.Wallet.Migration (migrateDB)+import Haskoin.Wallet.Parser (parserMain)+import Haskoin.Wallet.PrettyPrinter (prettyPrinter)+import Haskoin.Wallet.Util (encodeJsonPretty)++clientMain :: IO ()+clientMain =+  withContext $ \ctx -> do+    cfg <- initConfig+    runDB cfg $ migrateDB ctx cfg+    (cmd, unit, json) <- parserMain+    res <- commandResponse ctx cfg unit cmd+    if json+      then jsonPrinter ctx res+      else prettyPrinter unit res++jsonPrinter :: Ctx -> Response -> IO ()+jsonPrinter ctx = C8.putStrLn . encodeJsonPretty . marshalValue ctx
+ src/Haskoin/Wallet/Migration.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}++module Haskoin.Wallet.Migration where++import Conduit (MonadUnliftIO)+import Control.Monad+import Control.Monad.Except+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad.Reader (MonadTrans (lift))+import Data.Text (Text)+import Database.Esqueleto.Legacy as E+import Haskoin+import Haskoin.Wallet.Config+import Haskoin.Wallet.Database+import qualified Haskoin.Wallet.Migration.V0_9_0 as V0_9_0+import Haskoin.Wallet.Migration.SemVersion++getDatabaseTables :: (MonadUnliftIO m) => DB m [Text]+getDatabaseTables = do+  s <- rawSql "select name from sqlite_master where type='table'" []+  return $ unSingle <$> s++databaseVersion :: (MonadUnliftIO m) => DB m (Maybe SemVersion)+databaseVersion = do+  tables <- getDatabaseTables+  if null tables+    then return Nothing+    else+      if "d_b_version" `elem` tables -- Version table was created at v0.9.0+        then Just <$> getVersion+        else return $ Just V0_9_0.migrateFrom++migrateDB :: (MonadUnliftIO m) => Ctx -> Config -> DB m ()+migrateDB ctx cfg = do+  resE <- runExceptT $ do+    verM <- lift databaseVersion+    case verM of+      Nothing -> do+        lift  $ do+          globalMigration+          setVersion currentVersion+      Just ver -> migrateDB_ ctx ver+    return verM+  case resE of+    Left err -> do+      transactionUndo -- Roll back the migration and exit+      error err+    Right verM -> unless (verM == Just currentVersion) $ migrateDB ctx cfg++migrateDB_ ::+  (MonadUnliftIO m) => Ctx -> SemVersion -> ExceptT String (DB m) ()+migrateDB_ ctx dbVer+  | dbVer == V0_9_0.migrateFrom = do+      liftIO $ printDBMigration V0_9_0.migrateFrom V0_9_0.migrateTo+      V0_9_0.migrateDB ctx+  | dbVer == currentVersion = return ()+  -- In case we have a patch version in the database+  | toMinor dbVer == currentVersion = lift $ setVersion currentVersion+  | otherwise = error $ "Invalid migration version: " <> verString dbVer++printDBMigration :: SemVersion -> SemVersion -> IO ()+printDBMigration fromV toV =+  putStrLn $ "Migrating from " <> verString fromV <> " to " <> verString toV
+ src/Haskoin/Wallet/Migration/SemVersion.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}++module Haskoin.Wallet.Migration.SemVersion where++import Data.List.Split+import Data.String (IsString)+import Numeric.Natural++-- | Version of Haskoin Wallet package.+currentVersionStr :: (IsString a) => a+#ifdef CURRENT_PACKAGE_VERSION+currentVersionStr = CURRENT_PACKAGE_VERSION+#else+currentVersionStr = error "No version string"+#endif++data SemVersion+  = VerMajor !Natural+  | VerMinor !Natural !Natural+  | VerPatch !Natural Natural Natural+  deriving (Show)++-- 0 == 0.9 == 0.9.0+instance Eq SemVersion where+  (VerMajor j1) == (VerMajor j2) = j1 == j2+  (VerMajor j1) == (VerMinor j2 _) = j1 == j2+  (VerMajor j1) == (VerPatch j2 _ _) = j1 == j2+  (VerMinor j1 m1) == (VerMinor j2 m2) = j1 == j2 && m1 == m2+  (VerMinor j1 m1) == (VerPatch j2 m2 _) = j1 == j2 && m1 == m2+  (VerPatch j1 m1 p1) == (VerPatch j2 m2 p2) = j1 == j2 && m1 == m2 && p1 == p2+  a == b = b == a++toMajor :: SemVersion -> SemVersion+toMajor (VerMajor a) = VerMajor a+toMajor (VerMinor a _) = VerMajor a+toMajor (VerPatch a _ _) = VerMajor a++toMinor :: SemVersion -> SemVersion+toMinor (VerMajor _) = error "VerMajor in toMinor"+toMinor (VerMinor a b) = VerMinor a b+toMinor (VerPatch a b _) = VerMinor a b++parseSemVersion :: String -> SemVersion+parseSemVersion str =+  case read <$> splitOn "." str of+    [a] -> VerMajor a+    [a, b] -> VerMinor a b+    [a, b, c] -> VerPatch a b c+    _ -> error $ "Bad version: " <> str++verString :: SemVersion -> String+verString (VerMajor a) = show a+verString (VerMinor a b) = show a <> "." <> show b+verString (VerPatch a b c) = show a <> "." <> show b <> "." <> show c++currentVersion :: SemVersion+currentVersion =+  case parseSemVersion currentVersionStr of+    res@(VerPatch {}) -> res+    _ -> error $ "Bad version: " <> currentVersionStr
+ src/Haskoin/Wallet/Migration/V0_9_0.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Haskoin.Wallet.Migration.V0_9_0 where++import Conduit (MonadUnliftIO)+import Control.Monad+import Control.Monad.Except+import Control.Monad.Reader (MonadTrans (lift))+import Data.Aeson+import qualified Data.Aeson as Json+import qualified Data.ByteString as BS+import Data.Map.Strict (Map)+import Data.Maybe (fromMaybe)+import Database.Esqueleto.Legacy as E+import qualified Database.Persist as P+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Wallet.Database+import Haskoin.Wallet.TxInfo+import Numeric.Natural (Natural)+import Haskoin.Wallet.Migration.SemVersion++{- Migration from 0.8 to 0.9 -}++migrateFrom :: SemVersion+migrateFrom = VerMinor 0 8++migrateTo :: SemVersion+migrateTo = VerMinor 0 9++data OldTxInfo = OldTxInfo+  { oldTxInfoHash :: !TxHash,+    oldTxInfoType :: !TxType,+    oldTxInfoAmount :: !Integer,+    oldTxInfoMyOutputs :: !(Map Address OldMyOutputs),+    oldTxInfoOtherOutputs :: !(Map Address Natural),+    oldTxInfoNonStdOutputs :: ![Store.StoreOutput],+    oldTxInfoMyInputs :: !(Map Address OldMyInputs),+    oldTxInfoOtherInputs :: !(Map Address OtherInputs),+    oldTxInfoNonStdInputs :: ![Store.StoreInput],+    oldTxInfoSize :: !Natural,+    oldTxInfoFee :: !Natural,+    oldTxInfoFeeByte :: !Natural,+    oldTxInfoBlockRef :: !Store.BlockRef,+    oldTxInfoConfirmations :: !Natural+  }+  deriving (Eq, Show)++data OldMyOutputs = OldMyOutputs+  { oldMyOutputsValue :: !Natural,+    oldMyOutputsPath :: !SoftPath+  }+  deriving (Eq, Show)++instance Json.ToJSON OldMyOutputs where+  toJSON (OldMyOutputs i p) =+    object+      [ "value" .= i,+        "path" .= p+      ]++instance Json.FromJSON OldMyOutputs where+  parseJSON =+    withObject "MyOutputs" $ \o -> do+      i <- o .: "value"+      p <- o .: "path"+      return $ OldMyOutputs i p++data OldMyInputs = OldMyInputs+  { oldMyInputsValue :: !Natural,+    oldMyInputsPath :: !SoftPath,+    oldMyInputsSigInput :: [SigInput]+  }+  deriving (Eq, Show)++instance MarshalJSON Ctx OldMyInputs where+  marshalValue ctx (OldMyInputs i p s) =+    object $+      [ "value" .= i,+        "path" .= p+      ]+        ++ ["siginput" .= (marshalValue ctx <$> s) | not (null s)]++  unmarshalValue ctx =+    withObject "MyInputs" $ \o -> do+      i <- o .: "value"+      p <- o .: "path"+      sM <- o .:? "siginput"+      s <- unmarshalValue ctx `mapM` fromMaybe [] sM+      return $ OldMyInputs i p s++instance MarshalJSON (Network, Ctx) OldTxInfo where+  marshalValue (net, ctx) tx =+    object+      [ "txid" .= oldTxInfoHash tx,+        "type" .= oldTxInfoType tx,+        "amount" .= oldTxInfoAmount tx,+        "myoutputs" .= mapAddrText net (oldTxInfoMyOutputs tx),+        "otheroutputs" .= mapAddrText net (oldTxInfoOtherOutputs tx),+        "nonstdoutputs" .= (marshalValue net <$> oldTxInfoNonStdOutputs tx),+        "myinputs" .= marshalMap net ctx (oldTxInfoMyInputs tx),+        "otherinputs" .= marshalMap net ctx (oldTxInfoOtherInputs tx),+        "nonstdinputs" .= (marshalValue net <$> oldTxInfoNonStdInputs tx),+        "size" .= oldTxInfoSize tx,+        "fee" .= oldTxInfoFee tx,+        "feebyte" .= oldTxInfoFeeByte tx,+        "block" .= oldTxInfoBlockRef tx,+        "confirmations" .= oldTxInfoConfirmations tx+      ]+  unmarshalValue (net, ctx) =+    Json.withObject "TxInfo" $ \o ->+      OldTxInfo+        <$> o .: "txid"+        <*> o .: "type"+        <*> o .: "amount"+        <*> (mapTextAddr net <$> o .: "myoutputs")+        <*> (mapTextAddr net <$> o .: "otheroutputs")+        <*> (mapM (unmarshalValue net) =<< o .: "nonstdoutputs")+        <*> (unmarshalMap net ctx =<< o .: "myinputs")+        <*> (unmarshalMap net ctx =<< o .: "otherinputs")+        <*> (mapM (unmarshalValue net) =<< o .: "nonstdinputs")+        <*> o .: "size"+        <*> o .: "fee"+        <*> o .: "feebyte"+        <*> o .: "block"+        <*> o .: "confirmations"++migrateMyOutputs :: OldMyOutputs -> MyOutputs+migrateMyOutputs OldMyOutputs {..} =+  MyOutputs+    { myOutputsValue = oldMyOutputsValue,+      myOutputsPath = oldMyOutputsPath,+      myOutputsLabel = ""+    }++migrateMyInputs :: OldMyInputs -> MyInputs+migrateMyInputs OldMyInputs {..} =+  MyInputs+    { myInputsValue = oldMyInputsValue,+      myInputsPath = oldMyInputsPath,+      myInputsLabel = "",+      myInputsSigInput = oldMyInputsSigInput+    }++migrateTxInfo :: OldTxInfo -> TxInfo+migrateTxInfo OldTxInfo {..} =+  TxInfo+    { txInfoHash = Just oldTxInfoHash,+      txInfoType = oldTxInfoType,+      txInfoAmount = oldTxInfoAmount,+      txInfoMyOutputs = migrateMyOutputs <$> oldTxInfoMyOutputs,+      txInfoOtherOutputs = oldTxInfoOtherOutputs,+      txInfoNonStdOutputs = oldTxInfoNonStdOutputs,+      txInfoMyInputs = migrateMyInputs <$> oldTxInfoMyInputs,+      txInfoOtherInputs = oldTxInfoOtherInputs,+      txInfoNonStdInputs = oldTxInfoNonStdInputs,+      txInfoSize = oldTxInfoSize,+      txInfoFee = oldTxInfoFee,+      txInfoFeeByte = oldTxInfoFeeByte,+      txInfoBlockRef = oldTxInfoBlockRef,+      txInfoConfirmations = oldTxInfoConfirmations,+      txInfoPending = Nothing+    }++migrateDB :: (MonadUnliftIO m) => Ctx -> ExceptT String (DB m) ()+migrateDB ctx = do+  accs <- lift . select $ from return+  forM_ accs $ \(Entity _ acc) -> do+    let net = accountNetwork acc+    res <- lift . select . from $ \t -> do+      where_ $+        t ^. DBTxInfoAccountWallet ==. val (dBAccountWallet acc)+          &&. t ^. DBTxInfoAccountDerivation ==. val (dBAccountDerivation acc)+      return $ t ^. DBTxInfoId+    forM_ res $ \(Value tKey) -> do+      oldDBTx <- liftMaybe "OldTxInfo" =<< lift (P.get tKey)+      oldTxInfo <-+        liftMaybe "OldTxInfo" $+          unmarshalJSON (net, ctx) $+            BS.fromStrict $+              dBTxInfoBlob oldDBTx+      let newTxInfo = migrateTxInfo oldTxInfo+          newBlob = BS.toStrict $ marshalJSON (net, ctx) newTxInfo+      lift $ P.update tKey [DBTxInfoBlob P.=. newBlob]+  -- This will create the missing version table+  lift $ do+    globalMigration+    setVersion migrateTo
+ src/Haskoin/Wallet/Parser.hs view
@@ -0,0 +1,1083 @@+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++module Haskoin.Wallet.Parser where++import Data.List (intercalate)+import Data.String.Conversions (cs)+import Data.Text (Text)+import Haskoin+import Haskoin.Wallet.Amounts (AmountUnit (..), readNatural)+import Haskoin.Wallet.Util (Page (Page))+import Numeric.Natural (Natural)+import Options.Applicative+import Options.Applicative.Help.Pretty+import Text.RawString.QQ (r)++{- Command Parsers -}++data Command+  = CommandMnemonic+      { commandEntropy :: !Natural,+        commandUseDice :: !Bool,+        commandSplitIn :: !Natural+      }+  | CommandCreateAcc+      { commandName :: !Text,+        commandNetwork :: !Network,+        commandDerivationMaybe :: !(Maybe Natural),+        commandSplitIn :: !Natural+      }+  | CommandTestAcc+      { commandMaybeAcc :: !(Maybe Text),+        commandSplitIn :: !Natural+      }+  | CommandImportAcc+      { commandFilePath :: !FilePath+      }+  | CommandExportAcc+      { commandMaybeAcc :: !(Maybe Text),+        commandFilePath :: !FilePath+      }+  | CommandRenameAcc+      { commandOldName :: !Text,+        commandNewName :: !Text+      }+  | CommandAccounts+      { commandMaybeAcc :: !(Maybe Text)+      }+  | CommandDeleteAcc+      { commandName :: !Text,+        commandNetwork :: !Network,+        commandDerivation :: !HardPath+      }+  | CommandReceive+      { commandMaybeAcc :: !(Maybe Text),+        commandMaybeLabel :: !(Maybe Text)+      }+  | CommandAddrs+      { commandMaybeAcc :: !(Maybe Text),+        commandPage :: !Page+      }+  | CommandLabel+      { commandMaybeAcc :: !(Maybe Text),+        commandAddrIndex :: !Natural,+        commandLabel :: !Text+      }+  | CommandTxs+      { commandMaybeAcc :: !(Maybe Text),+        commandPage :: !Page+      }+  | CommandPrepareTx+      { commandRecipients :: ![(Text, Text)],+        commandMaybeAcc :: !(Maybe Text),+        commandFeeByte :: !Natural,+        commandDust :: !Natural,+        commandRcptPay :: !Bool,+        commandMinConf :: !Natural,+        commandOutputFileMaybe :: !(Maybe FilePath)+      }+  | CommandPendingTxs+      { commandMaybeAcc :: !(Maybe Text),+        commandPage :: !Page+      }+  | CommandReviewTx+      { commandMaybeAcc :: !(Maybe Text),+        commandFilePath :: !FilePath+      }+  | CommandImportTx+      { commandMaybeAcc :: !(Maybe Text),+        commandFilePath :: !FilePath+      }+  | CommandExportTx+      { commandNoSigHash :: !TxHash,+        commandFilePath :: !FilePath+      }+  | CommandDeleteTx+      {commandNoSigHash :: !TxHash}+  | CommandSignTx+      { commandMaybeAcc :: !(Maybe Text),+        commandNoSigHashMaybe :: !(Maybe TxHash),+        commandInputFileMaybe :: !(Maybe FilePath),+        commandOutputFileMaybe :: !(Maybe FilePath),+        commandSplitIn :: !Natural+      }+  | CommandCoins+      { commandMaybeAcc :: !(Maybe Text),+        commandPage :: !Page+      }+  | CommandSendTx+      {commandNoSigHash :: !TxHash}+  | CommandSyncAcc+      { commandMaybeAcc :: !(Maybe Text),+        commandFull :: !Bool+      }+  | CommandDiscoverAcc+      { commandMaybeAcc :: !(Maybe Text)+      }+  | CommandBackup+      {commandFilePath :: !FilePath}+  | CommandRestore+      {commandFilePath :: !FilePath}+  | CommandVersion+  | CommandPrepareSweep+      { commandMaybeAcc :: !(Maybe Text),+        commandSecKeyPath :: !FilePath,+        commandSweepTo :: ![Text],+        commandOutputFileMaybe :: !(Maybe FilePath),+        commandFeeByte :: !Natural,+        commandDust :: !Natural+      }+  | CommandSignSweep+      { commandMaybeAcc :: !(Maybe Text),+        commandNoSigHashMaybe :: !(Maybe TxHash),+        commandInputFileMaybe :: !(Maybe FilePath),+        commandOutputFileMaybe :: !(Maybe FilePath),+        commandSecKeyPath :: !FilePath+      }+  | CommandRollDice+      { commandCount :: !Natural+      }+  deriving (Eq, Show)++parserMain :: IO (Command, AmountUnit, Bool)+parserMain =+  customExecParser+    (prefs $ showHelpOnEmpty <> helpIndent 25)+    programParser++programParser :: ParserInfo (Command, AmountUnit, Bool)+programParser = do+  let cmd = (,,) <$> (commandParser <**> helper) <*> unitOption <*> jsonOption+  info cmd $+    fullDesc+      <> progDesc+        [r|+hw is a BIP-44 command-line wallet for bitcoin and bitcoin-cash. It allows+sensitive commands (!) to be run on a separate offline computer. For more+information on a command, type "hw COMMAND --help".+|]++jsonOption :: Parser Bool+jsonOption =+  switch $+    short 'j'+      <> long "json"+      <> help+        [r|+Display the result as JSON. Specify this option at the end of your command.+|]++unitOption :: Parser AmountUnit+unitOption = satoshiOption <|> bitOption++satoshiOption :: Parser AmountUnit+satoshiOption =+  flag UnitBitcoin UnitSatoshi $+    short 's'+      <> long "satoshi"+      <> help+        [r|+Use satoshis for parsing and displaying amounts (default: bitcoin). Specify this+option at the end of your command.+|]++bitOption :: Parser AmountUnit+bitOption =+  flag UnitBitcoin UnitBit $+    short 'b'+      <> long "bit"+      <> help+        [r|+Use bits for parsing and displaying amounts (default: bitcoin). Specify this+option at the end of your command.+|]++commandParser :: Parser Command+commandParser =+  asum+    [ hsubparser $+        mconcat+          [ commandGroup "Mnemonic and account management",+            command "mnemonic" mnemonicParser,+            command "createacc" createAccParser,+            command "testacc" testAccParser,+            command "renameacc" renameAccParser,+            command "accounts" accountsParser,+            command "syncacc" syncAccParser,+            command "deleteacc" deleteAccParser,+            metavar "COMMAND",+            style (const "COMMAND --help")+          ],+      hsubparser $+        mconcat+          [ commandGroup "Address management",+            command "receive" receiveParser,+            command "addrs" addrsParser,+            command "label" labelParser,+            hidden+          ],+      hsubparser $+        mconcat+          [ commandGroup "Transaction management",+            command "txs" txsParser,+            command "preparetx" prepareTxParser,+            command "pendingtxs" pendingTxsParser,+            command "signtx" signTxParser,+            command "sendtx" sendTxParser,+            command "deletetx" deleteTxParser,+            command "coins" coinsParser,+            hidden+          ],+      hsubparser $+        mconcat+          [ commandGroup "Import/export commands",+            command "exportacc" exportAccParser,+            command "importacc" importAccParser,+            command "reviewtx" reviewTxParser,+            command "exporttx" exportTxParser,+            command "importtx" importTxParser,+            hidden+          ],+      hsubparser $+        mconcat+          [ commandGroup "Backup/restore commands",+            command "backup" backupParser,+            command "restore" restoreParser,+            command "discoveracc" discoverAccParser,+            hidden+          ],+      hsubparser $+        mconcat+          [ commandGroup "Utilities",+            command "version" versionParser,+            command "preparesweep" prepareSweepParser,+            command "signsweep" signSweepParser,+            command "rolldice" rollDiceParser,+            hidden+          ]+    ]++offline :: Doc -> Maybe Doc+offline s = Just $ annotate (color Red) "! " <> s++{- Mnemonic Parser -}++mnemonicParser :: ParserInfo Command+mnemonicParser = do+  let cmd =+        CommandMnemonic+          <$> entropyOption+          <*> diceOption+          <*> splitInOption+  info cmd $+    progDescDoc (offline "Generate a mnemonic")+      <> footer+        [r|+Generate a mnemonic using the systems entropy pool. By default it should be+/dev/random on linux machines. If you use the --dice option, the additional dice+entropy will be mixed with the system entropy. You should ideally run this+command on an offline (air-gapped) computer. The mnemonic will NOT be stored on+disk. It will only be printed to the screen and you should write it down and+keep it in a secure location. Make multiple copies of your mnemonic. Do not+store your mnemonic on a digital medium. If you lose your mnemonic, you lose+your funds. If you choose to use the --split option, the mnemonic will be split+into different pieces such that ALL the pieces will be required for signing.+|]++entropyOption :: Parser Natural+entropyOption =+  option (maybeReader f) $+    short 'e'+      <> long "entropy"+      <> metavar "BYTES"+      <> value 16+      <> showDefault+      <> completeWith valid+      <> help+        [r|+Amount of entropy to use in bytes. Valid values are 16, 20, 24, 28 or 32.+|]+  where+    valid = ["16", "20", "24", "28", "32"]+    f s+      | s `elem` valid = fromIntegral <$> readNatural (cs s)+      | otherwise = Nothing++diceOption :: Parser Bool+diceOption =+  switch $+    short 'd'+      <> long "dice"+      <> help+        [r|+Provide additional entropy using 6-sided dice. The entropy will be mixed with+the system entropy.+|]++splitInOption :: Parser Natural+splitInOption =+  option (eitherReader $ f . cs) $+    short 's'+      <> long "split"+      <> metavar "INT"+      <> value 1+      <> help+        [r|+The mnemonic is split in different pieces and reconstructed using bitwise xor.+All the pieces are required for signing. The ordering of the pieces is not+important.+|]+  where+    f s =+      case readNatural s of+        Just n ->+          if n >= 2 && n <= 12+            then Right n+            else Left "The --split value has to be between 2 and 12"+        Nothing -> Left "Could not parse the --split option"++{- CreateAcc Parser -}++createAccParser :: ParserInfo Command+createAccParser = do+  let cmd =+        CommandCreateAcc+          <$> textArg "Name of the new account"+          <*> networkOption+          <*> optional derivationOption+          <*> splitInOption+  info cmd $+    progDescDoc (offline "Create a new account")+      <> footer+        [r|+An account corresponds to the BIP-32 extended key derivation+m/44'/coin'/account'. For example, the bitcoin account 0 would be m/44'/0'/0'.+An account is tied to a private key and thus to a mnemonic. The derived account+m/44'/0'/0' would be different given two different mnemonics. To help manage+your accounts in hw, they are identified by a name. The command `createacc` will+ask for a mnemonic and save the derived public key M/44'/coin'/account' on disk.+The private keys are never stored anywhere while using hw. If you are using an+offline computer, you can then export your account and import it on an online+computer.+|]++networkOption :: Parser Network+networkOption =+  option (eitherReader (f . netByName)) $+    short 'n'+      <> long "network"+      <> metavar "TEXT"+      <> value btc+      <> showDefaultWith (.name)+      <> completeWith ((.name) <$> allNets)+      <> help ("Specify one of the following networks to use: " <> nets)+  where+    nets = intercalate ", " ((.name) <$> allNets)+    f :: Maybe Network -> Either String Network+    f Nothing =+      Left $ "Invalid network name. Select one of the following: " <> nets+    f (Just res) = Right res++derivationOption :: Parser Natural+derivationOption =+  option (maybeReader $ readNatural . cs) $+    short 'd'+      <> long "derivation"+      <> metavar "INT"+      <> help+        [r|+Specify a different account derivation to use (the last part of+m/44'/coin'/account'). By default, account derivations are chosen sequentially+starting from 0.+|]++{- DeleteAcc Parser -}++deleteAccParser :: ParserInfo Command+deleteAccParser = do+  let cmd =+        CommandDeleteAcc+          <$> textArg "Name of the account to delete"+          <*> networkArg+          <*> fullDerivationOption+  info cmd $+    progDesc ("Delete an account")+      <> footer+        [r|+Delete an account from the wallet. You must specify the name, the network and+the full derivation path as confirmation to delete an account.+|]++fullDerivationOption :: Parser HardPath+fullDerivationOption =+  argument (maybeReader parseHard) $+    metavar "DERIVPATH"+      <> help+        [r|+Specify the full derivation path of the account to delete. For example:+/44'/0'/3'.+|]++networkArg :: Parser Network+networkArg =+  argument (eitherReader (f . netByName)) $+    metavar "NETWORK"+      <> completeWith ((.name) <$> allNets)+      <> help ("Specify the network of the account to delete: " <> nets)+  where+    nets = intercalate ", " ((.name) <$> allNets)+    f :: Maybe Network -> Either String Network+    f Nothing =+      Left $ "Invalid network name. Select one of the following: " <> nets+    f (Just res) = Right res++{- TestAcc Parser -}++testAccParser :: ParserInfo Command+testAccParser = do+  let cmd =+        CommandTestAcc+          <$> accountOption+          <*> splitInOption+  info cmd $+    progDescDoc (offline "Check the validity of your mnemonic/passphrase")+      <> footer+        [r|+`testacc` will prompt for your mnemonic/passphrase and check that the derived+public key M/44'/coin'/account' matches with the account stored on disk.+|]++accountOption :: Parser (Maybe Text)+accountOption =+  optional . strOption $+    short 'a'+      <> long "account"+      <> metavar "TEXT"+      <> completer (mkCompleter accountCompleter)+      <> help+        [r|+Specify the account name if the wallet has more than one account.+|]++{- ImportAcc Parser -}++importAccParser :: ParserInfo Command+importAccParser = do+  let cmd =+        CommandImportAcc+          <$> fileArgument "Path to the account file"+  info cmd $+    progDesc "Import an account file"+      <> footer importExportAccFooter++importExportAccFooter :: String+importExportAccFooter =+  [r|+When working in an online/offline environment, you can `importacc` a public key+file (on an online computer) that was exported with `exportacc` (on an offline+computer). This will allow the online computer to monitor transactions without+requiring access to the private keys. The file contains the public key+derivation M/44'/coin'/account' along with the account name, the network and a+wallet identifier. The wallet identifier is a fingerprint of the first public+account derivation M/44'/coin'/0'.+|]++{- ExportAcc Parser -}++exportAccParser :: ParserInfo Command+exportAccParser = do+  let cmd =+        CommandExportAcc+          <$> accountOption+          <*> fileArgument "File where the account data will be saved"+  info cmd $+    progDesc "Export account data to a file"+      <> footer importExportAccFooter++{- RenameAcc Parser -}++renameAccParser :: ParserInfo Command+renameAccParser = do+  let cmd =+        CommandRenameAcc+          <$> accountArg "Old account name"+          <*> textArg "New account name"+  info cmd $ progDesc "Rename an account"++accountArg :: String -> Parser Text+accountArg desc =+  argument str $+    metavar "TEXT"+      <> completer (mkCompleter accountCompleter)+      <> help desc++accountCompleter :: String -> IO [String]+accountCompleter _ = return []++{- TODO: Fix this+accountCompleter :: String -> IO [String]+accountCompleter pref = do+  names <- runDB getAccountNames+  return $ sort $ nub $ filter (pref `isPrefixOf`) (cs <$> names)+-}++{- Accounts Parser -}++accountsParser :: ParserInfo Command+accountsParser = do+  let cmd = CommandAccounts <$> accountOption+  info cmd $ progDesc "Display account information"++{- Receive Parser -}++receiveParser :: ParserInfo Command+receiveParser = do+  let cmd =+        CommandReceive <$> accountOption <*> labelOption+  info cmd $+    progDesc "Get a new address for receiving a payment"+      <> footer+        [r|+There are two types of addresses in a BIP-44 wallet: internal and external addresses.+Internal addresses are not exposed to the user and are managed internally by the+wallet (mostly for producing change outputs in transactions). The `receive` command+produces external addresses that are meant for receiving payments. Internal addresses+are derived under the BIP-32 path M/44'/coin'/account'/1/address. External addresses+use the path M/44'/coin'/account'/0/address.+|]++labelOption :: Parser (Maybe Text)+labelOption =+  optional . strOption $+    short 'l'+      <> long "label"+      <> metavar "TEXT"+      <> help "Specify a label for the address"++{- Addrs Parser -}++addrsParser :: ParserInfo Command+addrsParser = do+  let cmd =+        CommandAddrs+          <$> accountOption+          <*> (Page <$> limitOption <*> offsetOption)+  info cmd $+    progDesc "List the receiving addresses of an account"++offsetOption :: Parser Natural+offsetOption =+  option (maybeReader $ readNatural . cs) $+    short 'o'+      <> long "offset"+      <> metavar "INT"+      <> value 0+      <> showDefault+      <> help "Offset the result set"++limitOption :: Parser Natural+limitOption =+  option (maybeReader $ readNatural . cs) $+    short 'l'+      <> long "limit"+      <> metavar "INT"+      <> value 5+      <> showDefault+      <> help+        [r|+Limit the result set. If the result set is very large, you can specify the+--limit and --offset options to view the required data. For example, to skip the+first 20 values and then display the following 10, use --limit=10 and+--offset=20.+|]++{- Label Parser-}++labelParser :: ParserInfo Command+labelParser = do+  let cmd =+        CommandLabel+          <$> accountOption+          <*> addrIndexArg+          <*> textArg "The new label for the address"+  info cmd $ progDesc "Set the label of an address"++addrIndexArg :: Parser Natural+addrIndexArg =+  argument (maybeReader $ readNatural . cs) $+    metavar "INT"+      <> help+        [r|+The index of the external address to update. The first address has an index of+0.+|]++{- Txs Parser -}++txsParser :: ParserInfo Command+txsParser = do+  let cmd =+        CommandTxs+          <$> accountOption+          <*> (Page <$> limitOption <*> offsetOption)+  info cmd $ progDesc "Display the transactions of an account"++{- PrepareTx Parser -}++prepareTxParser :: ParserInfo Command+prepareTxParser = do+  let cmd =+        CommandPrepareTx+          <$> some recipientArg+          <*> accountOption+          <*> feeOption+          <*> dustOption+          <*> rcptPayOption+          <*> minConfOption+          <*> outputFileMaybeOption+  info cmd $+    progDesc "Prepare an unsigned transaction for making a payment"+      <> footer+        [r|+In hw, sending a transaction happens in separate steps. First, an unsigned+transaction is prepared using `preparetx`. No mnemonic or private keys are+required for this step and `preparetx` can be run on an online computer. The new+transaction will be stored in the local wallet database as a "pending"+transaction but it can also be exported as a file using the --output option. The+pending transactions can be inspected either with `reviewtx` or `pendingtxs`.+The new transaction is not signed. The next step will be to sign it. Any coins+spent by it will be locked to prevent spending them twice. If you change your+mind about sending this transaction, you can call `deletetx` to remove it and+free up the locked coins. All the pending transactions are referenced by their+noSigHash (a hash of the transaction without its signatures).+|]++recipientArg :: Parser (Text, Text)+recipientArg = (,) <$> addressArg <*> amountArg++addressArg :: Parser Text+addressArg =+  strArgument $+    metavar "ADDRESS"+      <> help+        [r|+Recipient address. You can provide multiple "ADDRESS AMOUNT" pairs. +|]++amountArg :: Parser Text+amountArg =+  strArgument $+    metavar "AMOUNT"+      <> help+        [r|+Recipient amount. By default, amounts are parsed as bitcoins. You can also use the+--satoshi or --bit option to specify amounts in satoshis or bits. For example,+"0.00001" bitcoins is equivalent to "10.00" bits or "1000" satoshi. Bitcoins can+have up to 8 decimal places, bits up to 2 and satoshi are whole numbers.+|]++feeOption :: Parser Natural+feeOption =+  option (maybeReader $ readNatural . cs) $+    short 'f'+      <> long "fee"+      <> metavar "INT"+      <> value 200+      <> showDefault+      <> help "Fee to pay in satoshi/bytes"++dustOption :: Parser Natural+dustOption =+  option (maybeReader $ readNatural . cs) $+    short 'd'+      <> long "dust"+      <> metavar "INT"+      <> value 5430+      <> showDefault+      <> help "Amount (in satoshi) below which an output is considered dust"++rcptPayOption :: Parser Bool+rcptPayOption =+  switch $+    short 'r'+      <> long "recipientpay"+      <> showDefault+      <> help "The transaction fee will be deducted from the recipient amounts"++minConfOption :: Parser Natural+minConfOption =+  option (maybeReader $ readNatural . cs) $+    short 'c'+      <> long "minconf"+      <> metavar "INT"+      <> value 1+      <> showDefault+      <> help "Minimum number of confirmations required to spend a coin"++outputFileMaybeOption :: Parser (Maybe FilePath)+outputFileMaybeOption =+  optional . strOption $+    short 'o'+      <> long "output"+      <> metavar "FILENAME"+      <> action "file"+      <> help "Write the result to this file"++{- PendingTxs Parser -}++pendingTxsParser :: ParserInfo Command+pendingTxsParser = do+  let cmd =+        CommandPendingTxs+          <$> accountOption+          <*> (Page <$> limitOption <*> offsetOption)+  info cmd $+    progDesc "Display the pending transactions of an account"+      <> footer+        [r|+Pending transactions have been created with the `preparetx` command. They are+either unsigned and waiting to be signed, or signed and waiting to be sent to+the network.+|]++{- ReviewTx Parser -}++reviewTxParser :: ParserInfo Command+reviewTxParser = do+  let cmd =+        CommandReviewTx+          <$> accountOption+          <*> fileArgument "Path to the transaction file to review"+  info cmd $+    progDesc "Review a transaction file"+      <> footer+        [r|+Review a pending transaction file that has been created with the --output option+of the `preparetx` command or the `exporttx` command. You can review a+transaction file this way before signing it on an offline computer for example.+|]++{- ExportTx Parser -}++exportTxParser :: ParserInfo Command+exportTxParser = do+  let cmd =+        CommandExportTx+          <$> nosigHashArg+          <*> fileArgument "File where the transaction data will be saved"+  info cmd $+    progDesc "Export pending transaction data to a file"+      <> footer importExportTxFooter++importExportTxFooter :: String+importExportTxFooter =+  [r|+A transaction that has been prepared with `preparetx` can be exported to a file+so that it can be signed on an offline computer. The transaction is identified+by its nosigHash (a hash of the transaction without its signatures) as this+identifier doesn't change when the transaction is unsigned or signed. Once+signed, you can `importtx` the transaction back into the computer that+prepared the transaction. +|]++nosigHashArg :: Parser TxHash+nosigHashArg =+  argument (maybeReader $ hexToTxHash . cs) $+    metavar "NOSIGHASH"+      <> help "The nosigHash of the transaction"++{- ImportTx Parser -}++importTxParser :: ParserInfo Command+importTxParser = do+  let cmd =+        CommandImportTx+          <$> accountOption+          <*> fileArgument "Path to the transaction file to import"+  info cmd $+    progDesc "Import a pending transaction"+      <> footer importExportTxFooter++{- DeleteTx Parser -}++deleteTxParser :: ParserInfo Command+deleteTxParser = do+  let cmd = CommandDeleteTx <$> nosigHashArg+  info cmd $+    progDesc "Delete a pending transaction"+      <> footer+        [r|+A transaction that has been prepared with `preparetx` will lock the coins that+it has spent to prevent double-spending them. A pending transaction can be+deleted with `deletetx` to permanently remove it from the database. This will+also free any coins that have been locked by it. This will only be possible as+long as the transaction hasn't been signed and sent to the network.+|]++{- SignTx Parser -}++signTxParser :: ParserInfo Command+signTxParser = do+  let cmd =+        CommandSignTx+          <$> accountOption+          <*> optional nosigHashArg+          <*> inputFileMaybeOption+          <*> outputFileMaybeOption+          <*> splitInOption+  info cmd $+    progDescDoc (offline "Sign a transaction")+      <> footer+        [r|+The next step after preparing a transaction is to sign it. This step can happen+on an offline computer if you export the unsigned transaction to a file. If the+transaction is stored in the wallet, it can be signed by using its nosigHash.+This will sign and replace the transaction in-place. Otherwise, you must specify+the --input file of the unsigned transaction and the --output file where the+signed transaction will be saved. The transaction still hasn't been uploaded to+the network. You may inspect it using either the `reviewtx` or the `pendingtxs`+command. The transaction can then be sent to the network with `sendtx` or+deleted with `deletetx`.+|]++inputFileMaybeOption :: Parser (Maybe FilePath)+inputFileMaybeOption =+  optional . strOption $+    short 'i'+      <> long "input"+      <> metavar "FILENAME"+      <> action "file"+      <> help "Read the input from this file"++{- Coins Parser -}++coinsParser :: ParserInfo Command+coinsParser = do+  let cmd =+        CommandCoins+          <$> accountOption+          <*> (Page <$> limitOption <*> offsetOption)+  info cmd $+    progDesc "List the account coins (unspent outputs)"+      <> footer+        [r|+These are all the coins in the account. Coins can be locked when preparing new+transactions. When sending those transactions to the network, the locked coins+will be spent and removed. Calling `deletetx` will remove a pending transaction+and free up any locked coins.+|]++{- SendTx Parser -}++sendTxParser :: ParserInfo Command+sendTxParser = do+  let cmd = CommandSendTx <$> nosigHashArg+  info cmd $+    progDesc "Send (upload) a signed transaction to the network"+      <> footer+        [r|+Once a transaction is signed, it can be sent to the network. This step is+irreversible and the transaction will become effective. Make sure everything is+correct with your transaction before sending it. You must `importtx` the+transaction before sending it if you have it in a file.+|]++{- SyncAcc Parser -}++syncAccParser :: ParserInfo Command+syncAccParser = do+  let cmd = CommandSyncAcc <$> accountOption <*> fullOption+  info cmd $+    progDesc "Download account data from the network"+      <> footer+        [r|+The `syncacc` command will download the latest address balances, transactions+and coins from the network. It will also update the account balances.+|]++fullOption :: Parser Bool+fullOption =+  switch $+    long "full"+      <> showDefault+      <> help+        [r|+Force the syncacc command to re-download all the account data instead of only+the deltas (the data that has changed). This should normally not be required+unless some information is wrong, for example, when a previous syncacc was+called on a partially discovered account (the account was missing some+addresses).+|]++{- DiscoverAcc Parser -}++discoverAccParser :: ParserInfo Command+discoverAccParser = do+  let cmd = CommandDiscoverAcc <$> accountOption+  info cmd $+    progDesc "Scan the blockchain to generate missing addresses"+      <> footer+        [r|+`discoveracc` is typically called when restoring an account in a new wallet.+Initially, there will be no addresses stored in the account. `discoveracc` will+scan the blockchain and generate all the addresses (both external and internal)+that have received coins at some point. The search is stopped when a gap+(default = 20) of empty addresses is found. A full sync (syncacc --full) is+run automatically at the end of the `discoveracc` command. When restoring an+old wallet, it is important to discover it first before generating addresses+and receiving payments. Otherwise some addresses might be reused.+|]++{- Backup & Restore Parsers -}++backupParser :: ParserInfo Command+backupParser = do+  let cmd =+        CommandBackup+          <$> fileArgument "File where the backup will be saved"+  info cmd $+    progDesc "Create a wallet backup file"+      <> footer+        [r|+This command will backup your wallet accounts, your address labels and the+internal free addresses to an external file. This is enough to restore your+entire wallet state with some network help. It will, however, not backup any+pending transactions you might have so you should save those separately.+|]++restoreParser :: ParserInfo Command+restoreParser = do+  let cmd =+        CommandRestore+          <$> fileArgument "Path to the backup file"+  info cmd $+    progDesc "Restore a wallet backup file"+      <> footer+        [r|+Restore a backup file containing your accounts, address labels and internal+free addresses. This command will connect to the network to download everything+else that is not included in the backup file like your transactions and coins.+Pending transactions are not included in the backup file.+|]++{- Version Parser -}++versionParser :: ParserInfo Command+versionParser = do+  let cmd = pure CommandVersion+  info cmd $ progDesc "Display the version of hw"++{- PrepareSweep Parser -}++prepareSweepParser :: ParserInfo Command+prepareSweepParser = do+  let cmd =+        CommandPrepareSweep+          <$> accountOption+          <*> prvKeyFileOption+          <*> some sweepToOption -- some: optional+          <*> outputFileMaybeOption+          <*> feeOption+          <*> dustOption+  info cmd $+    progDesc "Prepare a transaction to sweep funds"+      <> footer+        [r|+`preparesweep` will prepare a transaction that sweeps the funds of the given+private keys. The private keys are passed in a file and can be in WIF or minikey+format. The typical use case for this command is to migrate an old wallet to a+new mnemonic. You can send the funds to 1 or multiple addresses. You can use the+shorthand format -t ADDR1 -t ADDR2 to specify the --sweepto addresses.+|]++sweepFromOption :: Parser Text+sweepFromOption =+  strOption $+    short 'w'+      <> long "sweepfrom"+      <> metavar "ADDRESS"+      <> help "Addresses to sweep from"++addressFileOption :: Parser (Maybe FilePath)+addressFileOption =+  optional . strOption $+    long "addrfile"+      <> metavar "FILENAME"+      <> action "file"+      <> help "File containing addresses to sweep from"++sweepToOption :: Parser Text+sweepToOption =+  strOption $+    short 't'+      <> long "sweepto"+      <> metavar "ADDRESS"+      <> help "Addresses to sweep to"++{- SignSweep Parser -}++signSweepParser :: ParserInfo Command+signSweepParser = do+  let cmd =+        CommandSignSweep+          <$> accountOption+          <*> optional nosigHashArg+          <*> inputFileMaybeOption+          <*> outputFileMaybeOption+          <*> prvKeyFileOption+  info cmd $+    progDesc "Sign a sweep transaction"+      <> footer+        [r|+Sign a transaction that was prepared with the `preparesweep` command. As the+coins of this transaction are not from this wallet, the private keys for signing+must be passed in a separate --prvkeys file. The sweep transaction can be+referenced by its nosigHash if it exists in the wallet or you can pass it as a+file --input.+|]++prvKeyFileOption :: Parser FilePath+prvKeyFileOption =+  strOption $+    short 'k'+      <> long "prvkeys"+      <> metavar "FILENAME"+      <> action "file"+      <> help "File containing the private keys in WIF or MiniKey format"++{- RollDice Parser -}++rollDiceParser :: ParserInfo Command+rollDiceParser = do+  let cmd = CommandRollDice <$> diceCountArg+  info cmd $+    progDesc "Roll 6-sided dice using the systems internal entropy"+      <> footer+        [r|+By default, /dev/random is used on linux machines. The mapping from a byte to+dice rolls ensures that all dice rolls have the same probability of occuring.+Bytes 0x00 to 0xfb are used and anything above is dropped.+|]++diceCountArg :: Parser Natural+diceCountArg =+  argument (maybeReader $ readNatural . cs) $+    metavar "INT"+      <> help "Number of 6-sided dice to roll"++{- Argument parser helpers -}++textArg :: String -> Parser Text+textArg desc =+  argument str $+    metavar "TEXT"+      <> help desc++fileArgument :: String -> Parser FilePath+fileArgument desc =+  strArgument $+    metavar "FILENAME"+      <> action "file"+      <> help desc
+ src/Haskoin/Wallet/PrettyPrinter.hs view
@@ -0,0 +1,547 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}++module Haskoin.Wallet.PrettyPrinter where++import Control.Monad+import Data.List (intercalate, intersperse)+import qualified Data.Map.Strict as Map+import Data.Maybe (fromMaybe)+import Data.String.Conversions (cs)+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Time.Format+import Data.Word+import Haskoin+import Haskoin.Wallet.Amounts+import Haskoin.Wallet.Backup+import Haskoin.Wallet.Commands+import Haskoin.Wallet.Database+import Haskoin.Wallet.TxInfo+import Haskoin.Wallet.Util+import Numeric.Natural (Natural)+import System.Console.ANSI+import System.Exit+import System.IO++data Printer+  = PConcat !Printer !Printer+  | PNewline !Printer+  | PNest !Natural !Printer+  | PText [SGR] !String+  | PEmpty+  | PNonEmpty++instance Semigroup Printer where+  a <> PEmpty = a+  PEmpty <> b = b+  a <> b = PConcat a b++instance Monoid Printer where+  mempty = PEmpty++isEmptyPrinter :: Printer -> Bool+isEmptyPrinter prt =+  case prt of+    PEmpty -> True+    (PText _ s) -> null s+    (PNest _ n) -> isEmptyPrinter n+    (PNewline n) -> isEmptyPrinter n+    _ -> False++text :: String -> Printer+text = PText []++(<+>) :: Printer -> Printer -> Printer+p1 <+> p2+  | isEmptyPrinter p1 = p2+  | isEmptyPrinter p2 = p1+  | otherwise = p1 <> text " " <> p2++vcat :: [Printer] -> Printer+vcat = go . filter (not . isEmptyPrinter)+  where+    go [] = PEmpty+    go [x] = x+    go (x : xs) = x <> PNewline (go xs)++hsep :: [Printer] -> Printer+hsep = go . filter (not . isEmptyPrinter)+  where+    go [] = PEmpty+    go [x] = x+    go (x : xs) = x <+> go xs++nest :: Natural -> Printer -> Printer+nest = PNest++block :: Natural -> String -> String+block n str =+  case n `safeSubtract` fromIntegral (length str) of+    Just missing -> str <> replicate (fromIntegral missing) ' '+    _ -> str++rblock :: Natural -> String -> String+rblock n str =+  case n `safeSubtract` fromIntegral (length str) of+    Just missing -> replicate (fromIntegral missing) ' ' <> str+    _ -> str++renderIO :: Printer -> IO ()+renderIO cp = go 0 0 cp >> putStrLn ""+  where+    go :: Natural -> Natural -> Printer -> IO Natural+    go l n p+      | isEmptyPrinter p = return l+      | otherwise =+          case p of+            PConcat p1 p2 -> do+              l2 <- go l n p1+              go l2 n p2+            PNewline p1 -> do+              putStrLn ""+              putStr $ replicate (fromIntegral n) ' '+              go n n p1+            PNest i p1 -> do+              putStr $ replicate (fromIntegral i) ' '+              go (l + i) (n + i) p1+            PText xs s -> do+              printFormat xs s+              return $ l + fromIntegral (length s)+            PEmpty -> return l+            PNonEmpty -> return l++formatTitle :: String -> Printer+formatTitle =+  PText+    [ SetConsoleIntensity BoldIntensity,+      SetColor Foreground Vivid White+    ]++formatAccount :: String -> Printer+formatAccount =+  PText+    [ SetConsoleIntensity BoldIntensity,+      SetColor Foreground Vivid Cyan+    ]++formatFilePath :: String -> Printer+formatFilePath =+  PText+    [ SetItalicized True,+      SetColor Foreground Vivid White+    ]++formatKey :: String -> Printer+formatKey = PText [SetColor Foreground Dull White]++formatValue :: String -> Printer+formatValue = PText [SetColor Foreground Vivid White]++formatLabel :: String -> Printer+formatLabel =+  PText+    [ SetItalicized True,+      SetColor Foreground Vivid White+    ]++formatDeriv :: String -> Printer+formatDeriv =+  PText+    [ SetItalicized True,+      SetColor Foreground Vivid Yellow+    ]++formatMnemonic :: Bool -> String -> Printer+formatMnemonic split =+  PText+    [ SetConsoleIntensity BoldIntensity,+      SetColor Foreground Vivid (if split then Yellow else Cyan)+    ]++formatAddress :: Bool -> String -> Printer+formatAddress False = PText [SetColor Foreground Dull White]+formatAddress True = PText [SetColor Foreground Vivid White]++formatTxHash :: String -> Printer+formatTxHash = PText [SetColor Foreground Dull White]++formatNoSigTxHash :: String -> Printer+formatNoSigTxHash = PText [SetColor Foreground Vivid Magenta]++formatBlockHash :: String -> Printer+formatBlockHash = PText [SetColor Foreground Dull White]++formatPosAmount :: String -> Printer+formatPosAmount = PText [SetColor Foreground Vivid Green]++formatNegAmount :: String -> Printer+formatNegAmount = PText [SetColor Foreground Vivid Red]++formatZeroAmount :: String -> Printer+formatZeroAmount = PText [SetColor Foreground Vivid White]++formatDice :: String -> Printer+formatDice =+  PText+    [ SetConsoleIntensity BoldIntensity,+      SetColor Foreground Vivid Yellow+    ]++formatTrue :: String -> Printer+formatTrue = PText [SetColor Foreground Vivid Green]++formatFalse :: String -> Printer+formatFalse = PText [SetColor Foreground Vivid Red]++formatError :: String -> Printer+formatError = PText [SetColor Foreground Vivid Red]++printFormat :: [SGR] -> String -> IO ()+printFormat sgr str = do+  support <- hSupportsANSI stdout+  when support $ setSGR sgr+  putStr str+  when support $ setSGR []++{- Pretty Response -}++mnemonicPrinter :: Bool -> [Text] -> Printer+mnemonicPrinter split ws =+  vcat $+    fmap (mconcat . fmap formatWord) $+      chunksOf 4 $+        zip ([1 ..] :: [Natural]) ws+  where+    formatWord (i, w) =+      mconcat+        [ formatKey $ block 4 $ show i <> ".",+          formatMnemonic split $ block 10 $ cs w+        ]++partsPrinter :: [[Text]] -> [Printer]+partsPrinter [] = mempty+partsPrinter xs =+  concatMap formatPart $ zip ([1 ..] :: [Natural]) xs+  where+    formatPart (i, ws) =+      [ formatTitle $ "Private Mnemonic Part #" <> show i,+        nest 2 $ mnemonicPrinter True ws+      ]++keyPrinter :: Natural -> Text -> Printer+keyPrinter n txt = formatKey (block n (cs txt)) <> text ": "++amountPrinter :: AmountUnit -> Word64 -> Printer+amountPrinter unit = integerAmountPrinter unit . fromIntegral++integerAmountPrinter :: AmountUnit -> Integer -> Printer+integerAmountPrinter unit amnt+  | amnt == 0 = integerAmountPrinterWith formatZeroAmount unit amnt+  | amnt > 0 = integerAmountPrinterWith formatPosAmount unit amnt+  | otherwise = integerAmountPrinterWith formatNegAmount unit amnt++integerAmountPrinterWith ::+  (String -> Printer) -> AmountUnit -> Integer -> Printer+integerAmountPrinterWith f unit amnt =+  f (cs $ showIntegerAmount unit amnt) <+> unitPrinter unit amnt++unitPrinter :: AmountUnit -> Integer -> Printer+unitPrinter unit = text . cs . showUnit unit++naturalPrinter :: Natural -> Printer+naturalPrinter nat+  | nat == 0 = formatZeroAmount $ show nat+  | otherwise = formatPosAmount $ show nat++accountPrinter :: AmountUnit -> DBAccount -> Printer+accountPrinter unit acc =+  vcat+    [ keyPrinter 8 "Account" <> formatAccount (cs $ dBAccountName acc),+      mconcat [keyPrinter 8 "Wallet", formatValue $ cs fp],+      mconcat [keyPrinter 8 "Deriv", formatDeriv deriv],+      mconcat [keyPrinter 8 "Network", formatValue net],+      mconcat+        [keyPrinter 8 "External", formatValue $ show $ dBAccountExternal acc],+      mconcat+        [keyPrinter 8 "Internal", formatValue $ show $ dBAccountInternal acc],+      mconcat [keyPrinter 8 "Created", formatValue created],+      formatKey "Balances:",+      nest 2 $+        mconcat+          [ keyPrinter 11 "Confirmed",+            amountPrinter unit $ dBAccountBalanceConfirmed acc+          ],+      nest 2 $+        mconcat+          [ keyPrinter 11 "Unconfirmed",+            amountPrinter unit $ dBAccountBalanceUnconfirmed acc+          ],+      nest 2 $+        mconcat+          [ keyPrinter 11 "Coins",+            naturalPrinter $ fromIntegral $ dBAccountBalanceCoins acc+          ]+    ]+  where+    DBWalletKey fp = dBAccountWallet acc+    deriv = cs $ dBAccountDerivation acc+    net = (accountNetwork acc).name+    utctime = dBAccountCreated acc+    created = formatTime defaultTimeLocale "%d %b %Y" utctime++addressPrinter :: AmountUnit -> Natural -> DBAddress -> Printer+addressPrinter unit pad addr =+  vcat+    [ mconcat+        [ keyPrinter pad $ cs $ show (dBAddressIndex addr),+          formatAddress True $ cs $ dBAddressAddress addr+        ],+      if Text.null $ dBAddressLabel addr+        then PEmpty+        else+          nest 2 $+            mconcat+              [ keyPrinter 8 "Label",+                formatLabel $ cs $ dBAddressLabel addr+              ],+      nest 2 $+        mconcat+          [ keyPrinter 8 "Txs",+            naturalPrinter $ fromIntegral $ dBAddressBalanceTxs addr+          ],+      nest 2 $+        mconcat+          [ keyPrinter 8 "Received",+            amountPrinter unit $ dBAddressBalanceReceived addr+          ]+    ]++txInfoPrinter ::+  Network ->+  AmountUnit ->+  TxInfo ->+  Printer+txInfoPrinter net unit TxInfo {..} =+  vcat+    [ formatTitle (block 9 title) <> text ": " <> total,+      txid,+      pending,+      fee,+      internal,+      debit,+      credit+    ]+  where+    title =+      case txInfoType of+        TxDebit -> "Debit"+        TxInternal -> "Internal"+        TxCredit -> "Credit"+    total = integerAmountPrinter unit txInfoAmount+    fee = keyPrinter 9 "Fee" <> feePrinter unit txInfoFee txInfoFeeByte+    txid =+      case txInfoHash of+        Just tid ->+          keyPrinter 9 "TxHash" <> formatTxHash (cs $ txHashToHex tid)+        _ -> mempty+    pending =+      case txInfoPending of+        Just (TxInfoPending nosigH signed online) ->+          vcat+            [ keyPrinter 9 "NoSigHash"+                <> formatNoSigTxHash (cs $ txHashToHex nosigH),+              keyPrinter 9 "Signed"+                <> text "This pending transaction is"+                  <+> if signed+                    then formatTrue "signed"+                    else formatFalse "not signed",+              if online+                then+                  keyPrinter 9 "Online"+                    <> text "This transaction is"+                      <+> formatTrue "online"+                else mempty+            ]+        _ -> keyPrinter 9 "Confs" <> formatValue (show txInfoConfirmations)+    internal+      | txInfoType /= TxInternal = mempty+      | otherwise =+          vcat $+            [formatKey "From addresses:"]+              <> ( nest 2 . addrPrinter True+                     <$> Map.assocs (Map.map g txInfoMyInputs)+                 )+              <> [formatKey "To addresses:"]+              <> ( nest 2+                     . addrPrinter True+                     <$> Map.assocs (Map.map f txInfoMyOutputs)+                 )+    debit+      | txInfoType /= TxDebit = mempty+      | otherwise =+          vcat $+            [formatKey "Sending to addresses:"]+              <> ( nest 2 . addrPrinter False+                     <$> Map.assocs+                       ( Map.map+                           (,Nothing,"")+                           txInfoOtherOutputs+                       )+                 )+    credit+      | txInfoType /= TxCredit = mempty+      | otherwise =+          vcat $+            [formatKey "My credited addresses:"]+              <> ( nest 2 . addrPrinter True+                     <$> Map.assocs (Map.map f txInfoMyOutputs)+                 )+    f (MyOutputs v p l) = (v, Just p, cs l)+    g (MyInputs v p l _) = (v, Just p, cs l)+    addrPrinter isMine (a, (v, pathM, label)) =+      vcat+        [ formatAddress isMine (parseAddr net a)+            <> text ":"+              <+> if isMine+                then integerAmountPrinterWith formatPosAmount unit (fromIntegral v)+                else integerAmountPrinterWith formatZeroAmount unit (fromIntegral v),+          case pathM of+            Just p ->+              nest 2 $+                formatKey "Path:" <+> formatDeriv (pathToStr p)+            _ -> mempty,+          if null label+            then mempty+            else nest 2 $ formatKey "Label:" <+> formatLabel label+        ]++parseAddr :: Network -> Address -> String+parseAddr net = cs . fromMaybe "Invalid Address" . addrToText net++feePrinter :: AmountUnit -> Natural -> Natural -> Printer+feePrinter unit fee feeBytes =+  integerAmountPrinterWith formatZeroAmount unit (fromIntegral fee)+    <+> text "("+    <> formatValue (show feeBytes)+      <+> text "sat/bytes"+    <> text ")"++coinPrinter :: Network -> AmountUnit -> JsonCoin -> Printer+coinPrinter net unit JsonCoin {..} =+  vcat+    [ formatKey (block 7 "TxHash")+        <> text ":"+          <+> formatTxHash (cs $ txHashToHex jsonCoinOutpoint.hash),+      formatKey (block 7 "Index")+        <> text ":"+          <+> formatValue (show jsonCoinOutpoint.index),+      formatKey (block 7 "Value")+        <> text ":"+          <+> amountPrinter unit jsonCoinValue,+      formatKey (block 7 "Address")+        <> text ":"+          <+> formatAddress True (parseAddr net jsonCoinAddress),+      formatKey (block 7 "Confs")+        <> text ":"+          <+> formatValue (show jsonCoinConfirmations),+      formatKey (block 7 "Status")+        <> text ":"+          <+> if jsonCoinLocked+            then formatFalse "Locked"+            else formatTrue "Free"+    ]++prettyPrinter :: AmountUnit -> Response -> IO ()+prettyPrinter unit =+  \case+    ResponseError err -> do+      renderIO . mconcat $ [formatError "Error: ", text $ cs err]+      exitFailure+    ResponseMnemonic orig mnem parts ->+      renderIO . vcat $+        [ formatTitle "System Entropy Source",+          nest 2 $ formatFilePath $ cs orig,+          formatTitle "Private Mnemonic",+          nest 2 $ mnemonicPrinter False mnem+        ]+          <> partsPrinter parts+    ResponseAccount acc -> renderIO $ accountPrinter unit acc+    ResponseAccounts [] -> renderIO $ text "There are no accounts to display"+    ResponseAccounts accs ->+      renderIO . vcat $ intersperse (text " ") $ accountPrinter unit <$> accs+    ResponseAccResult _ res txt -> do+      let p =+            if res+              then formatTrue "Success"+              else formatFalse "Failure"+      renderIO . vcat $ [formatKey "Result:" <+> p, text $ cs txt]+    ResponseFile f -> renderIO $ formatKey "File:" <+> formatFilePath f+    ResponseAddress _ addr -> do+      let l = length $ show $ dBAddressIndex addr+      renderIO $ addressPrinter unit (fromIntegral l) addr+    ResponseAddresses _ [] ->+      renderIO $ text "There are no addresses to display"+    ResponseAddresses _ addrs -> do+      let l = length $ show $ maximum $ dBAddressIndex <$> addrs+      renderIO $ vcat $ addressPrinter unit (fromIntegral l) <$> addrs+    ResponseTxs _ [] ->+      renderIO $ text "There are no transactions to display"+    ResponseTxs acc txs -> do+      let net = accountNetwork acc+      renderIO $ vcat $ intersperse (text " ") $ txInfoPrinter net unit <$> txs+    ResponseTx acc txInfo -> do+      let net = accountNetwork acc+      renderIO $ txInfoPrinter net unit txInfo+    ResponseDeleteTx h c a ->+      renderIO $+        vcat+          [ formatKey "Deleted:" <+> formatNoSigTxHash (cs $ txHashToHex h),+            nest 2 $ formatKey "Freed coins:" <+> formatValue (show c),+            nest 2 $+              formatKey "Freed internal addresses:" <+> formatValue (show a)+          ]+    ResponseCoins _ [] ->+      renderIO $ text "There are no coins to display"+    ResponseCoins acc coins -> do+      let net = accountNetwork acc+      renderIO $ vcat $ intersperse (text " ") $ coinPrinter net unit <$> coins+    ResponseSync xs -> do+      let f (SyncRes acc bh h t c) =+            [ accountPrinter unit acc,+              formatTitle "Sync Results:",+              nest 2 $+                keyPrinter 12 "Best Block"+                  <> formatBlockHash (cs $ blockHashToHex bh),+              nest 2 $ keyPrinter 12 "Best Height" <> formatValue (show h),+              nest 2 $ keyPrinter 12 "Tx updates" <> formatValue (show t),+              nest 2 $ keyPrinter 12 "Coin updates" <> formatValue (show c)+            ]+      renderIO $ vcat $ intercalate [text " "] (f <$> xs)+    ResponseRestore as -> do+      let f (acc, t, c) =+            [ accountPrinter unit acc,+              formatTitle "Restore Results:",+              nest 2 $ keyPrinter 12 "Tx updates" <> formatValue (show t),+              nest 2 $ keyPrinter 12 "Coin updates" <> formatValue (show c)+            ]+      renderIO $ vcat $ intercalate [text " "] (f <$> as)+    ResponseVersion v dbv ->+      renderIO $+        vcat+          [ formatKey "Software version:" <+> formatValue (cs v),+            formatKey "Database version:" <+> formatValue (cs dbv)+          ]+    ResponseRollDice ds e ->+      renderIO . vcat $+        [ formatKey "System Entropy Source:" <+> formatFilePath (cs e),+          formatKey "Dice rolls:"+            <+> mconcat (intersperse (text ", ") (formatDice . show <$> ds))+        ]
+ src/Haskoin/Wallet/Signing.hs view
@@ -0,0 +1,310 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}++module Haskoin.Wallet.Signing where++import Conduit (MonadUnliftIO)+import Control.Arrow (second)+import Control.Monad (unless, when, (<=<))+import Control.Monad.Except+import Control.Monad.State+import qualified Data.ByteString as BS+import Data.Default (def)+import Data.Either (rights)+import Data.List (nub)+import qualified Data.Map.Strict as Map+import Data.Text (Text)+import Data.Word (Word64)+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Store.WebClient+import Haskoin.Wallet.Config+import Haskoin.Wallet.Database+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.TxInfo+import Haskoin.Wallet.Util+import Numeric.Natural (Natural)+import System.Random (Random (randomR), StdGen, initStdGen)++{- Building Transactions -}++buildTxSignData ::+  (MonadUnliftIO m) =>+  Network ->+  Ctx ->+  Config ->+  StdGen ->+  DBAccountId ->+  [(Address, Natural)] ->+  Natural ->+  Natural ->+  Bool ->+  Natural ->+  ExceptT String (DB m) TxSignData+buildTxSignData net ctx cfg gen accId rcpts feeByte dust rcptPay minConf+  | null rcpts = throwError "No recipients provided"+  | otherwise = do+      -- Get all spendable coins in the account+      allCoins <- getSpendableCoins net accId minConf+      -- Get a change address+      dbAddr <- nextFreeIntAddr ctx cfg accId+      (change, changeDeriv) <- liftEither $ fromDBAddr net dbAddr+      -- Build a transaction and pick the coins+      (tx, pickedCoins) <-+        liftEither $+          buildWalletTx net ctx gen rcpts change allCoins feeByte dust rcptPay+      -- Get the derivations of our recipients and picked coins+      rcptsDerivsE <- mapM (lift . getAddrDeriv net accId) $ fst <$> rcpts+      let rcptsDerivs = rights rcptsDerivsE -- It's OK to fail here+      inDerivs <- mapM (liftEither <=< lift . getCoinDeriv net accId) pickedCoins+      -- Check if we have a change output+      let noChange = length tx.outputs == length rcpts+          outDerivs =+            if noChange+              then rcptsDerivs+              else changeDeriv : rcptsDerivs+      -- Get the dependent transactions+      let depTxHash = (.hash) . (.outpoint) <$> tx.inputs+      depTxs <- mapM getRawTx depTxHash+      -- Return the result+      return $ TxSignData tx depTxs (nub inDerivs) (nub outDerivs) False++buildWalletTx ::+  Network ->+  Ctx ->+  StdGen -> -- Randomness for coin selection and change address order+  [(Address, Natural)] -> -- recipients+  Address -> -- change+  [Store.Unspent] -> -- Coins to choose from+  Natural -> -- Fee per byte+  Natural -> -- Dust+  Bool -> -- Recipients Pay for Fee+  Either String (Tx, [Store.Unspent])+buildWalletTx net ctx gen rcptsN change coins feeByteN dustN rcptPay =+  flip evalStateT gen $ do+    rdmCoins <- randomShuffle coins+    (pickedCoins, changeAmnt) <-+      lift $ chooseCoins tot feeCoinSel (length rcptsN + 1) False rdmCoins+    let nOuts =+          if changeAmnt <= dust+            then length rcptsN+            else length rcptsN + 1+        totFee =+          guessTxFee (fromIntegral feeByteN) nOuts (length pickedCoins)+    rcptsPayN <-+      if rcptPay+        then lift $ makeRcptsPay (fromIntegral totFee) rcptsN+        else return rcptsN+    let rcpts = second fromIntegral <$> rcptsPayN+        allRcpts+          | changeAmnt <= dust = rcpts+          | otherwise = (change, changeAmnt) : rcpts+        ops = (.outpoint) <$> pickedCoins+    when (any ((<= dust) . snd) allRcpts) $+      lift $+        Left "Recipient output is smaller than the dust value"+    rdmRcpts <- randomShuffle allRcpts+    tx <- lift $ buildAddrTx net ctx ops =<< mapM (addrToText2 net) rdmRcpts+    return (tx, pickedCoins)+  where+    feeCoinSel =+      if rcptPay+        then 0+        else fromIntegral feeByteN :: Word64+    dust = fromIntegral dustN :: Word64+    tot = fromIntegral $ sum $ snd <$> rcptsN :: Word64++makeRcptsPay ::+  Natural -> [(Address, Natural)] -> Either String [(Address, Natural)]+makeRcptsPay fee rcpts =+  mapM f rcpts+  where+    f (a, v) = (a,) <$> maybeToEither err (v `safeSubtract` toPay)+    err = "Recipients can't pay for the fee"+    (q, r) = fee `quotRem` fromIntegral (length rcpts)+    toPay = if r == 0 then q else q + 1++{- Signing Transactions -}++data MnemonicPass = MnemonicPass+  { mnemonicWords :: !Text,+    mnemonicPass :: !Text+  }+  deriving (Eq, Show)++-- Compute an account signing key+signingKey :: Network -> Ctx -> MnemonicPass -> Natural -> Either String XPrvKey+signingKey net ctx mnem acc = do+  seed <- mnemonicToSeed (mnemonicPass mnem) (mnemonicWords mnem)+  return $ derivePath ctx (bip44Deriv net acc) (makeXPrvKey seed)++-- Compute the unique wallet fingerprint given a mnemonic+walletFingerprint :: Network -> Ctx -> MnemonicPass -> Either String Fingerprint+walletFingerprint net ctx mnem = do+  xPrvKey <- signingKey net ctx mnem 0+  return $ xPubFP ctx $ deriveXPubKey ctx xPrvKey++signWalletTx ::+  Network ->+  Ctx ->+  TxSignData ->+  XPrvKey ->+  Either String (TxSignData, TxInfo)+signWalletTx net ctx tsd@TxSignData {txSignDataInputPaths = inPaths} signKey =+  signTxWithKeys net ctx tsd publicKey prvKeys+  where+    publicKey = deriveXPubKey ctx signKey+    prvKeys = (.key) . (\p -> derivePath ctx p signKey) <$> inPaths++signTxWithKeys ::+  Network ->+  Ctx ->+  TxSignData ->+  XPubKey ->+  [SecKey] ->+  Either String (TxSignData, TxInfo)+signTxWithKeys net ctx tsd@(TxSignData tx _ _ _ signed) publicKey secKeys = do+  when signed $ Left "The transaction is already signed"+  when (null secKeys) $ Left "There are no private keys to sign"+  txInfo <- parseTxSignData net ctx publicKey tsd+  -- signing+  let myInputs = txInfoMyInputs txInfo+      othInputs = txInfoOtherInputs txInfo+      mySigInputs = mconcat $ myInputsSigInput <$> Map.elems myInputs+      othSigInputs = mconcat $ otherInputsSigInput <$> Map.elems othInputs+      sigInputs = mySigInputs <> othSigInputs+  signedTx <- signTx net ctx tx sigInputs secKeys+  let txInfoS = signTxInfo signedTx txInfo+      isSigned = verifyTxInfo net ctx signedTx txInfoS+  unless isSigned $ Left "The transaction could not be signed"+  return+    ( tsd {txSignDataTx = signedTx, txSignDataSigned = True},+      txInfoS+    )++signTxInfo :: Tx -> TxInfo -> TxInfo+signTxInfo tx txInfo =+  txInfo+    { txInfoHash = Just $ txHash tx,+      txInfoPending = Just $ TxInfoPending (nosigTxHash tx) True False+    }++verifyTxInfo :: Network -> Ctx -> Tx -> TxInfo -> Bool+verifyTxInfo net ctx tx txInfo =+  let myInputs = txInfoMyInputs txInfo+      othInputs = txInfoOtherInputs txInfo+      mySigInputs = mconcat $ myInputsSigInput <$> Map.elems myInputs+      othSigInputs = mconcat $ otherInputsSigInput <$> Map.elems othInputs+      sigInputs = mySigInputs <> othSigInputs+      f i = (i.script, i.value, i.outpoint)+      vDat = f <$> sigInputs+   in Just (txHash tx) == txInfoHash txInfo+        && noEmptyInputs tx+        && verifyStdTx net ctx tx vDat++noEmptyInputs :: Tx -> Bool+noEmptyInputs = (not . any BS.null) . fmap (.script) . (.inputs)++{- Transaction Sweeping -}++buildSweepSignData ::+  (MonadUnliftIO m) =>+  Network ->+  Ctx ->+  Config ->+  DBAccountId ->+  [SecKey] ->+  [Address] ->+  Natural ->+  Natural ->+  ExceptT String (DB m) TxSignData+buildSweepSignData net ctx cfg accId prvKeys sweepTo feeByte dust+  | null prvKeys = throwError "No private keys to sweep from"+  | null sweepTo = throwError "No addresses to sweep to"+  | otherwise = do+      let host = apiHost net cfg+      -- Generate the addresses to sweep from+      let sweepFrom = nub $ concatMap (genPossibleAddrs net ctx) prvKeys+      -- Get the unspent coins of the sweepFrom addresses+      Store.SerialList coins <-+        liftExcept . apiBatch ctx (configCoinBatch cfg) host $+          GetAddrsUnspent sweepFrom def {limit = Just 0}+      when (null coins) $+        throwError "There are no coins to sweep in those addresses"+      -- Build a set of sweep transactions+      gen <- liftIO initStdGen+      tx <- liftEither $ buildSweepTx net ctx gen coins sweepTo feeByte dust+      -- Get the dependent transactions+      let depTxHash = (.hash) . (.outpoint) <$> (.inputs) tx+      Store.RawResultList depTxs <-+        liftExcept . apiBatch ctx (configTxFullBatch cfg) host $+          GetTxsRaw depTxHash+      -- Check if any of the coins belong to us+      inDerivs <- rights <$> lift (mapM (getAddrDeriv net accId) sweepFrom)+      -- Check if any of the sweepTo addrs belong to us+      outDerivs <- rights <$> lift (mapM (getAddrDeriv net accId) sweepTo)+      return $ TxSignData tx depTxs (nub inDerivs) (nub outDerivs) False++genPossibleAddrs :: Network -> Ctx -> SecKey -> [Address]+genPossibleAddrs net ctx k+  | net `elem` [btc, btcTest, btcRegTest] =+      [ pubKeyAddr ctx pc,+        pubKeyAddr ctx pu,+        pubKeyWitnessAddr ctx pc,+        pubKeyWitnessAddr ctx pu,+        pubKeyCompatWitnessAddr ctx pc,+        pubKeyCompatWitnessAddr ctx pu+      ]+  | otherwise =+      [ pubKeyAddr ctx pc,+        pubKeyAddr ctx pu+      ]+  where+    c = wrapSecKey False k :: PrivateKey -- Compressed+    u = wrapSecKey True k :: PrivateKey -- Uncompressed+    pc = derivePublicKey ctx c+    pu = derivePublicKey ctx u++buildSweepTx ::+  Network ->+  Ctx ->+  StdGen ->+  [Store.Unspent] ->+  [Address] ->+  Natural ->+  Natural ->+  Either String Tx+buildSweepTx net ctx gen coins sweepTo feeByte dust =+  flip evalStateT gen $ do+    rdmOutpoints <- ((.outpoint) <$>) <$> randomShuffle coins+    rdmSweepTo <- randomShuffle sweepTo+    let coinsTot = sum $ (.value) <$> coins+        fee = guessTxFee (fromIntegral feeByte) (length sweepTo) (length coins)+    when (coinsTot < fee) $+      throwError "Could not find a sweep solution: fee is too large"+    let (q, r) = (coinsTot - fee) `quotRem` fromIntegral (length sweepTo)+        amnts = (q + r) : repeat q+    when (q <= fromIntegral dust) $+      throwError "Outputs are smaller than the dust value"+    addrsT <- lift $ mapM (maybeToEither "Addr" . addrToText net) rdmSweepTo+    lift $ buildAddrTx net ctx rdmOutpoints (zip addrsT amnts)++-- Utilities --++randomRange :: (Random a, MonadState StdGen m) => a -> a -> m a+randomRange low hi = do+  gen <- get+  let (res, newGen) = randomR (low, hi) gen+  put newGen+  return res++randomShuffle :: (MonadState StdGen m) => [a] -> m [a]+randomShuffle [] = return []+randomShuffle [x] = return [x]+randomShuffle xs = do+  i <- randomRange 0 (length xs - 1)+  case splitAt i xs of+    (as, e : bs) -> (e :) <$> randomShuffle (as <> bs)+    _ -> error "randomShuffle"
+ src/Haskoin/Wallet/TxInfo.hs view
@@ -0,0 +1,403 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}++module Haskoin.Wallet.TxInfo where++import Control.Arrow ((&&&))+import Control.Monad (unless)+import Data.Aeson (object, withObject, (.:), (.:?), (.=))+import qualified Data.Aeson as Json+import Data.Aeson.Types (Parser)+import qualified Data.ByteString as BS+import Data.Either (partitionEithers)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Maybe (fromMaybe, isJust)+import qualified Data.Serialize as S+import Data.Text (Text)+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.Util+import Numeric.Natural (Natural)++data TxType = TxDebit | TxInternal | TxCredit+  deriving (Show, Eq)++instance Json.ToJSON TxType where+  toJSON =+    Json.String . \case+      TxDebit -> "debit"+      TxInternal -> "internal"+      TxCredit -> "credit"++instance Json.FromJSON TxType where+  parseJSON =+    Json.withText "txtype" $ \case+      "debit" -> return TxDebit+      "internal" -> return TxInternal+      "credit" -> return TxCredit+      _ -> fail "Invalid TxType"++data TxInfo = TxInfo+  { txInfoHash :: !(Maybe TxHash),+    txInfoType :: !TxType,+    txInfoAmount :: !Integer,+    txInfoMyOutputs :: !(Map Address MyOutputs),+    txInfoOtherOutputs :: !(Map Address Natural),+    txInfoNonStdOutputs :: ![Store.StoreOutput],+    txInfoMyInputs :: !(Map Address MyInputs),+    txInfoOtherInputs :: !(Map Address OtherInputs),+    txInfoNonStdInputs :: ![Store.StoreInput],+    txInfoSize :: !Natural,+    txInfoFee :: !Natural,+    txInfoFeeByte :: !Natural,+    txInfoBlockRef :: !Store.BlockRef,+    txInfoConfirmations :: !Natural,+    txInfoPending :: !(Maybe TxInfoPending)+  }+  deriving (Eq, Show)++data TxInfoPending = TxInfoPending+  { pendingNosigHash :: !TxHash,+    pendingSigned :: !Bool,+    pendingOnline :: !Bool+  }+  deriving (Eq, Show)++instance Json.ToJSON TxInfoPending where+  toJSON (TxInfoPending h s o) =+    object+      [ "nosighash" .= h,+        "signed" .= s,+        "online" .= o+      ]++instance Json.FromJSON TxInfoPending where+  parseJSON =+    withObject "TxInfoPending" $ \o -> do+      h <- o .: "nosighash"+      s <- o .: "signed"+      onl <- o .: "online"+      return $ TxInfoPending h s onl++data MyOutputs = MyOutputs+  { myOutputsValue :: !Natural,+    myOutputsPath :: !SoftPath,+    myOutputsLabel :: !Text+  }+  deriving (Eq, Show)++instance Json.ToJSON MyOutputs where+  toJSON (MyOutputs i p l) =+    object+      [ "value" .= i,+        "path" .= p,+        "label" .= l+      ]++instance Json.FromJSON MyOutputs where+  parseJSON =+    withObject "MyOutputs" $ \o -> do+      i <- o .: "value"+      p <- o .: "path"+      l <- o .: "label"+      return $ MyOutputs i p l++data MyInputs = MyInputs+  { myInputsValue :: !Natural,+    myInputsPath :: !SoftPath,+    myInputsLabel :: !Text,+    myInputsSigInput :: [SigInput]+  }+  deriving (Eq, Show)++instance MarshalJSON Ctx MyInputs where+  marshalValue ctx (MyInputs i p l s) =+    object $+      [ "value" .= i,+        "path" .= p,+        "label" .= l+      ]+        ++ ["siginput" .= (marshalValue ctx <$> s) | not (null s)]++  unmarshalValue ctx =+    withObject "MyInputs" $ \o -> do+      i <- o .: "value"+      p <- o .: "path"+      l <- o .: "label"+      sM <- o .:? "siginput"+      s <- unmarshalValue ctx `mapM` fromMaybe [] sM+      return $ MyInputs i p l s++data OtherInputs = OtherInputs+  { otherInputsValue :: !Natural,+    otherInputsSigInput :: [SigInput]+  }+  deriving (Eq, Show)++instance MarshalJSON Ctx OtherInputs where+  marshalValue ctx (OtherInputs i s) =+    object $+      ("value" .= i)+        : ["siginput" .= (marshalValue ctx <$> s) | not (null s)]++  unmarshalValue ctx =+    withObject "OtherInputs" $ \o -> do+      i <- o .: "value"+      sM <- o .:? "siginput"+      s <- unmarshalValue ctx `mapM` fromMaybe [] sM+      return $ OtherInputs i s++instance MarshalJSON (Network, Ctx) TxInfo where+  marshalValue (net, ctx) tx =+    object+      [ "txid" .= txInfoHash tx,+        "type" .= txInfoType tx,+        "amount" .= txInfoAmount tx,+        "myoutputs" .= mapAddrText net (txInfoMyOutputs tx),+        "otheroutputs" .= mapAddrText net (txInfoOtherOutputs tx),+        "nonstdoutputs" .= (marshalValue net <$> txInfoNonStdOutputs tx),+        "myinputs" .= marshalMap net ctx (txInfoMyInputs tx),+        "otherinputs" .= marshalMap net ctx (txInfoOtherInputs tx),+        "nonstdinputs" .= (marshalValue net <$> txInfoNonStdInputs tx),+        "size" .= txInfoSize tx,+        "fee" .= txInfoFee tx,+        "feebyte" .= txInfoFeeByte tx,+        "block" .= txInfoBlockRef tx,+        "confirmations" .= txInfoConfirmations tx,+        "pending" .= txInfoPending tx+      ]+  unmarshalValue (net, ctx) =+    Json.withObject "TxInfo" $ \o ->+      TxInfo+        <$> o .: "txid"+        <*> o .: "type"+        <*> o .: "amount"+        <*> (mapTextAddr net <$> o .: "myoutputs")+        <*> (mapTextAddr net <$> o .: "otheroutputs")+        <*> (mapM (unmarshalValue net) =<< o .: "nonstdoutputs")+        <*> (unmarshalMap net ctx =<< o .: "myinputs")+        <*> (unmarshalMap net ctx =<< o .: "otherinputs")+        <*> (mapM (unmarshalValue net) =<< o .: "nonstdinputs")+        <*> o .: "size"+        <*> o .: "fee"+        <*> o .: "feebyte"+        <*> o .: "block"+        <*> o .: "confirmations"+        <*> o .: "pending"++marshalMap ::+  (MarshalJSON Ctx v) =>+  Network ->+  Ctx ->+  Map Address v ->+  Map Text Json.Value+marshalMap net ctx m = mapAddrText net $ Map.map (marshalValue ctx) m++unmarshalMap ::+  (MarshalJSON Ctx v) =>+  Network ->+  Ctx ->+  Map Text Json.Value ->+  Parser (Map Address v)+unmarshalMap net ctx m = mapTextAddr net <$> (unmarshalValue ctx `mapM` m)++mapAddrText :: Network -> Map Address v -> Map Text v+mapAddrText net m =+  either error id $ do+    let f (a, v) = (,v) <$> addrToTextE net a+    Map.fromList <$> mapM f (Map.assocs m)++mapTextAddr :: Network -> Map Text v -> Map Address v+mapTextAddr net m =+  either error id $ do+    let f (a, v) = (,v) <$> textToAddrE net a+    Map.fromList <$> mapM f (Map.assocs m)++storeToTxInfo ::+  Map Address SoftPath -> Natural -> Store.Transaction -> TxInfo+storeToTxInfo walletAddrs currHeight sTx =+  TxInfo+    { txInfoHash = Just sTx.txid,+      txInfoType = txType amount fee,+      txInfoAmount = amount,+      txInfoMyOutputs = Map.map (\(v, p) -> MyOutputs v p "") myOutputsMap,+      txInfoOtherOutputs = othOutputsMap,+      txInfoNonStdOutputs = nonStdOut,+      txInfoMyInputs = Map.map (\(v, p) -> MyInputs v p "" []) myInputsMap,+      txInfoOtherInputs = Map.map (`OtherInputs` []) othInputsMap,+      txInfoNonStdInputs = nonStdIn,+      txInfoSize = size,+      txInfoFee = fee,+      txInfoFeeByte = feeByte,+      txInfoBlockRef = sTx.block,+      txInfoConfirmations = fromMaybe 0 confM,+      txInfoPending = Nothing+    }+  where+    size = fromIntegral sTx.size :: Natural+    fee = fromIntegral sTx.fee :: Natural+    feeByte = fee `div` size+    (outputMap, nonStdOut) = outputAddressMap sTx.outputs+    myOutputsMap = Map.intersectionWith (,) outputMap walletAddrs+    othOutputsMap = Map.difference outputMap walletAddrs+    (inputMap, nonStdIn) = inputAddressMap sTx.inputs+    myInputsMap = Map.intersectionWith (,) inputMap walletAddrs+    othInputsMap = Map.difference inputMap walletAddrs+    myOutputsSum =+      fromIntegral $ sum $ fst <$> Map.elems myOutputsMap :: Integer+    myInputsSum = fromIntegral $ sum $ fst <$> Map.elems myInputsMap :: Integer+    amount = myOutputsSum - myInputsSum+    confM =+      case sTx.block of+        Store.MemRef _ -> Nothing+        Store.BlockRef height _ ->+          (+ 1) <$> currHeight `safeSubtract` fromIntegral height++{- Helpers for building address maps -}++outputAddressMap ::+  [Store.StoreOutput] -> (Map Address Natural, [Store.StoreOutput])+outputAddressMap outs =+  (Map.fromListWith (+) rs, ls)+  where+    (ls, rs) = partitionEithers $ f <$> outs+    f (Store.StoreOutput v _ _ (Just a)) = Right (a, fromIntegral v)+    f s = Left s++inputAddressMap ::+  [Store.StoreInput] -> (Map Address Natural, [Store.StoreInput])+inputAddressMap ins =+  (Map.fromListWith (+) rs, ls)+  where+    (ls, rs) = partitionEithers $ f <$> ins+    f (Store.StoreInput _ _ _ _ v _ (Just a)) = Right (a, fromIntegral v)+    f s = Left s++txOutAddressMap :: Ctx -> [TxOut] -> (Map Address Natural, [TxOut])+txOutAddressMap ctx outs =+  (Map.fromListWith (+) rs, ls)+  where+    (ls, rs) = partitionEithers $ f <$> outs+    f to@(TxOut v s) =+      case scriptToAddressBS ctx s of+        Left _ -> Left to+        Right a -> Right (a, fromIntegral v)++txInAddressMap ::+  Network ->+  Ctx ->+  [(OutPoint, TxOut)] ->+  (Map Address (Natural, [SigInput]), [(OutPoint, TxOut)])+txInAddressMap net ctx ins =+  (Map.fromListWith (\(a, b) (c, d) -> (a + c, b <> d)) rs, mconcat ls)+  where+    sh = maybeSetForkId net sigHashAll+    (ls, rs) = partitionEithers $ f <$> ins+    f (op, to@(TxOut v s)) =+      case scriptToAddressBS ctx s of+        Left _ -> Left [(op, to)]+        Right a ->+          Right+            ( a,+              ( fromIntegral v,+                [SigInput (addressToOutput a) v op sh Nothing]+              )+            )++maybeSetForkId :: Network -> SigHash -> SigHash+maybeSetForkId net+  | isJust net.sigHashForkId = setForkIdFlag+  | otherwise = id++parseTxSignData ::+  Network ->+  Ctx ->+  XPubKey ->+  TxSignData ->+  Either String TxInfo+parseTxSignData net ctx pubkey tsd@(TxSignData tx _ inPaths outPaths signed) = do+  coins <- txSignDataCoins tsd+  -- Fees+  let outSum = fromIntegral $ sum $ (.value) <$> tx.outputs :: Natural+      inSum = fromIntegral $ sum $ (.value) . snd <$> coins :: Natural+  fee <- maybeToEither "Fee is negative" $ inSum `safeSubtract` outSum+  let feeByte = fee `div` fromIntegral size+      -- Outputs+      (outputMap, nonStdOut) = txOutAddressMap ctx tx.outputs+      myOutputsMap = Map.intersectionWith (,) outputMap outPathAddrs+      othOutputsMap = Map.difference outputMap outPathAddrs+      -- Inputs+      (inputMap, nonStdIn) = txInAddressMap net ctx coins+      myInputsMap =+        Map.intersectionWith (\(n, xs) p -> (n, p, xs)) inputMap inPathAddrs+      othInputsMap = Map.difference inputMap inPathAddrs+      -- Amounts+      myOutputsSum =+        fromIntegral $ sum $ fst <$> Map.elems myOutputsMap :: Integer+      myInputsSum =+        fromIntegral $ sum $ fst3 <$> Map.elems myInputsMap :: Integer+      amount = myOutputsSum - myInputsSum+  -- Sanity checks+  unless (null nonStdOut) $+    Left "There are non-standard outputs in the transaction"+  unless (null nonStdIn) $+    Left "There are non-standard inputs in the transaction"+  unless (length coins == length tx.inputs) $+    Left "Referenced input transactions are missing"+  unless (length inPaths == Map.size myInputsMap) $+    Left "Input derivations don't match the transaction inputs"+  unless (length outPaths == Map.size myOutputsMap) $+    Left "Output derivations don't match the transaction outputs"+  return $+    TxInfo+      { txInfoHash = if signed then Just $ txHash tx else Nothing,+        txInfoType = txType amount fee,+        txInfoAmount = amount,+        txInfoMyOutputs =+          Map.map (\(v, p) -> MyOutputs v p "") myOutputsMap,+        txInfoOtherOutputs = othOutputsMap,+        txInfoNonStdOutputs = [],+        txInfoMyInputs =+          Map.map (\(i, p, s) -> MyInputs i p "" s) myInputsMap,+        txInfoOtherInputs = Map.map (uncurry OtherInputs) othInputsMap,+        txInfoNonStdInputs = [],+        txInfoSize = fromIntegral size,+        txInfoFee = fee,+        txInfoFeeByte = feeByte,+        txInfoBlockRef = Store.MemRef 0,+        txInfoConfirmations = 0,+        txInfoPending = Just $ TxInfoPending (nosigTxHash tx) signed False+      }+  where+    inPathAddrs = Map.fromList $ (pathToAddr ctx pubkey &&& id) <$> inPaths+    outPathAddrs = Map.fromList $ (pathToAddr ctx pubkey &&& id) <$> outPaths+    size+      | signed = BS.length $ S.encode tx+      | otherwise = guessTxSize (length tx.inputs) [] (length tx.outputs) 0++txSignDataCoins :: TxSignData -> Either String [(OutPoint, TxOut)]+txSignDataCoins (TxSignData tx depTxs _ _ _) =+  maybeToEither "Referenced input transactions are missing" $ mapM f ops+  where+    ops = (.outpoint) <$> tx.inputs :: [OutPoint]+    txMap =+      Map.fromList $ (txHash &&& (.outputs)) <$> depTxs :: Map TxHash [TxOut]+    f :: OutPoint -> Maybe (OutPoint, TxOut)+    f op@(OutPoint h i) =+      (op,) <$> ((!!? fromIntegral i) =<< Map.lookup h txMap)++txType :: Integer -> Natural -> TxType+txType amount fee+  | amount > 0 = TxCredit+  | abs amount == fromIntegral fee = TxInternal+  | otherwise = TxDebit++pathToAddr :: Ctx -> XPubKey -> SoftPath -> Address+pathToAddr ctx pk path = xPubAddr ctx $ derivePubPath ctx path pk
+ src/Haskoin/Wallet/Util.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}++module Haskoin.Wallet.Util where++import Control.Arrow (second)+import Control.Monad.Except+import qualified Data.Aeson as JSON+import qualified Data.Aeson.Encode.Pretty as Pretty+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import Data.List (sortBy)+import Data.Maybe (fromMaybe)+import qualified Data.Serialize as S+import Data.Text (Text)+import qualified Data.Text as Text+import Haskoin+import qualified Haskoin.Store.Data as Store+import Numeric.Natural (Natural)++{- Data.Aeson Compatibility -}++encodeJsonPretty :: (JSON.ToJSON a) => a -> BS.ByteString+encodeJsonPretty =+  BL.toStrict+    . Pretty.encodePretty' Pretty.defConfig {Pretty.confIndent = Pretty.Spaces 2}++encodeJsonPrettyLn :: (JSON.ToJSON a) => a -> BS.ByteString+encodeJsonPrettyLn =+  BL.toStrict+    . Pretty.encodePretty'+      Pretty.defConfig+        { Pretty.confIndent = Pretty.Spaces 2,+          Pretty.confTrailingNewline = True+        }++{- Haskoin helper functions -}++data Page = Page+  { pageLimit :: !Natural,+    pageOffset :: !Natural+  }+  deriving (Eq, Show)++toPage :: Page -> [a] -> [a]+toPage (Page limit offset) xs =+  take (fromIntegral limit) $ drop (fromIntegral offset) xs++liftExcept :: (MonadError String m) => ExceptT Store.Except m a -> m a+liftExcept action = do+  e <- runExceptT action+  case e of+    Right a -> return a+    Left err -> throwError $ show (err :: Store.Except)++addrToTextE :: Network -> Address -> Either String Text+addrToTextE net a =+  maybeToEither "Invalid Address in addrToTextE" (addrToText net a)++textToAddrE :: Network -> Text -> Either String Address+textToAddrE net a =+  maybeToEither "Invalid Address in textToAddrE" (textToAddr net a)++addrToText2 :: Network -> (Address, v) -> Either String (Text, v)+addrToText2 net (a, v) = (,v) <$> addrToTextE net a++textToAddr2 :: Network -> (Text, v) -> Either String (Address, v)+textToAddr2 net (a, v) = (,v) <$> textToAddrE net a++addrToText3 :: Network -> (Address, v, w) -> Either String (Text, v, w)+addrToText3 net (a, v, w) = (,v,w) <$> addrToTextE net a++textToAddr3 :: Network -> (Text, v, w) -> Either String (Address, v, w)+textToAddr3 net (a, v, w) = (,v,w) <$> textToAddrE net a++lastList :: Natural -> [a] -> [a]+lastList count xs = drop (max 0 $ length xs - fromIntegral count) xs++xPubChecksum :: Ctx -> XPubKey -> Text+xPubChecksum ctx = encodeHex . S.encode . xPubFP ctx++(</>) :: String -> String -> String+a </> b = a <> "/" <> b++(!!?) :: [a] -> Natural -> Maybe a+xs !!? i+  | fromIntegral i < length xs = Just $ xs !! fromIntegral i+  | otherwise = Nothing++chunksOf :: Natural -> [a] -> [[a]]+chunksOf n xs+  | null xs = []+  | otherwise =+      uncurry (:) $ second (chunksOf n) $ splitAt (fromIntegral n) xs++sortDesc :: (Ord a) => [a] -> [a]+sortDesc = sortBy (flip compare)++padStart :: Int -> Text -> Text -> Text+padStart n c t =+  Text.replicate (fromMaybe 0 $ n `safeSubtract` Text.length t) c <> t++padEnd :: Int -> Text -> Text -> Text+padEnd n c t =+  t <> Text.replicate (fromMaybe 0 $ n `safeSubtract` Text.length t) c++dropPatternEnd :: Text -> Text -> Text+dropPatternEnd p t = fromMaybe t $ Text.stripSuffix p t++chunksOfEnd :: Int -> Text -> [Text]+chunksOfEnd n t = reverse $ Text.reverse <$> Text.chunksOf n (Text.reverse t)++safeSubtract :: (Integral a) => a -> a -> Maybe a+safeSubtract a b+  | b > a = Nothing+  | otherwise = Just $ a - b
+ test/Haskoin/Wallet/AmountsSpec.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Haskoin.Wallet.AmountsSpec where++import Haskoin.Wallet.Amounts+import Haskoin.Wallet.TestUtils (genNatural)+import Test.Hspec (Spec, describe, it, shouldBe)+import Test.Hspec.QuickCheck (prop)+import Test.QuickCheck (Arbitrary (arbitrary), elements, forAll)++spec :: Spec+spec =+  describe "Amount parser" $ do+    prop "readAmount . showAmount identity" $+      forAll genNatural $ \n unit ->+        readAmount unit (showAmount unit n) `shouldBe` Just n+    prop "readIntegerAmount . showIntegerAmount identity" $ \i unit ->+      readIntegerAmount unit (showIntegerAmount unit i) `shouldBe` Just i+    it "Satoshi amounts vectors" $ do+      readAmount UnitSatoshi "0" `shouldBe` Just 0+      readAmount UnitSatoshi "0000" `shouldBe` Just 0+      readAmount UnitSatoshi "0.0" `shouldBe` Nothing+      readAmount UnitSatoshi "8" `shouldBe` Just 8+      readAmount UnitSatoshi "100" `shouldBe` Just 100+      readAmount UnitSatoshi "1234567890" `shouldBe` Just 1234567890+      readAmount UnitSatoshi "1'234'567'890" `shouldBe` Just 1234567890+      readAmount UnitSatoshi "1 234 567 890" `shouldBe` Just 1234567890+      readAmount UnitSatoshi "1_234_567_890" `shouldBe` Just 1234567890+    it "Bit amounts vectors" $ do+      readAmount UnitBit "0" `shouldBe` Just 0+      readAmount UnitBit "0000" `shouldBe` Just 0+      readAmount UnitBit "0.0" `shouldBe` Just 0+      readAmount UnitBit "0.00" `shouldBe` Just 0+      readAmount UnitBit "0.000" `shouldBe` Nothing+      readAmount UnitBit "0.10" `shouldBe` Just 10+      readAmount UnitBit "0.1" `shouldBe` Just 10+      readAmount UnitBit "0.01" `shouldBe` Just 1+      readAmount UnitBit "1" `shouldBe` Just 100+      readAmount UnitBit "100" `shouldBe` Just 10000+      readAmount UnitBit "100.00" `shouldBe` Just 10000+      readAmount UnitBit "100.01" `shouldBe` Just 10001+      readAmount UnitBit "1234567890.9" `shouldBe` Just 123456789090+      readAmount UnitBit "1'234'567'890.90" `shouldBe` Just 123456789090+      readAmount UnitBit "1 234 567 890.90" `shouldBe` Just 123456789090+      readAmount UnitBit "1_234_567_890.90" `shouldBe` Just 123456789090+    it "Bitcoin amounts vectors" $ do+      readAmount UnitBitcoin "0" `shouldBe` Just 0+      readAmount UnitBitcoin "0000" `shouldBe` Just 0+      readAmount UnitBitcoin "0.0" `shouldBe` Just 0+      readAmount UnitBitcoin "0.00000000" `shouldBe` Just 0+      readAmount UnitBitcoin "0.000000000" `shouldBe` Nothing+      readAmount UnitBitcoin "0.1" `shouldBe` Just 10000000+      readAmount UnitBitcoin "0.1000" `shouldBe` Just 10000000+      readAmount UnitBitcoin "0.10000000" `shouldBe` Just 10000000+      readAmount UnitBitcoin "0.100000000" `shouldBe` Nothing+      readAmount UnitBitcoin "1" `shouldBe` Just 100000000+      readAmount UnitBitcoin "100" `shouldBe` Just 10000000000+      readAmount UnitBitcoin "1234567890.9" `shouldBe` Just 123456789090000000+      readAmount UnitBitcoin "1'234'567'890.9009"+        `shouldBe` Just 123456789090090000+      readAmount UnitBitcoin "1 234 567 890.9009"+        `shouldBe` Just 123456789090090000+      readAmount UnitBitcoin "1_234_567_890.9009"+        `shouldBe` Just 123456789090090000++instance Arbitrary AmountUnit where+  arbitrary = elements [UnitBitcoin, UnitBit, UnitSatoshi]
+ test/Haskoin/Wallet/CommandsSpec.hs view
@@ -0,0 +1,811 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+{-# OPTIONS_GHC -Wno-ambiguous-fields -fno-warn-orphans #-}++module Haskoin.Wallet.CommandsSpec where++import Conduit (MonadIO, liftIO)+import Control.Arrow (second)+import Control.Monad+import Control.Monad.Except+import Control.Monad.Trans (lift)+import Data.Default (def)+import Data.Either+import qualified Data.Map as Map+import Data.Maybe+import qualified Data.Serialize as S+import Data.Text (Text)+import Database.Esqueleto.Legacy hiding (isNothing)+import qualified Database.Persist as P+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Util.Arbitrary+import Haskoin.Wallet.Config+import Haskoin.Wallet.Database+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.Signing+import Haskoin.Wallet.SigningSpec+import Haskoin.Wallet.TestUtils+import Haskoin.Wallet.TxInfo+import Haskoin.Wallet.Util+import Test.Hspec++identityTests :: Ctx -> IdentityTests+identityTests ctx =+  def+    { jsonTests =+        [ JsonBox $ arbitraryDBAccount btc ctx,+          JsonBox $ arbitraryDBAddress btc,+          JsonBox arbitraryAddressBalance,+          JsonBox $ arbitraryTxSignData btc ctx,+          JsonBox arbitraryConfig+        ],+      marshalJsonTests =+        [ MarshalJsonBox ((btc,) <$> arbitraryJsonCoin),+          MarshalJsonBox ((ctx,) <$> arbitraryPubKeyDoc ctx),+          MarshalJsonBox (((btc, ctx),) <$> arbitraryTxInfo btc ctx),+          MarshalJsonBox ((ctx,) <$> arbitraryResponse btc ctx),+          MarshalJsonBox ((ctx,) <$> arbitraryAccountBackup ctx)+        ]+    }++spec :: Spec+spec = do+  let cfg = def :: Config+  prepareContext $ \ctx -> do+    testIdentity $ identityTests ctx+    describe "Database" $ do+      bestSpec+      accountSpec ctx+      extAddressSpec ctx cfg+      intAddressSpec ctx cfg+      txsSpec ctx+      coinSpec ctx+      pendingTxsSpec ctx cfg++liftTest :: (MonadIO m) => Expectation -> m ()+liftTest = liftIO++shouldBeLeft :: (Show a) => ExceptT String (DB IO) a -> DB IO ()+shouldBeLeft action = liftTest . (`shouldSatisfy` isLeft) =<< runExceptT action++shouldBeLeft' ::+  (Show a, Eq a) => String -> ExceptT String (DB IO) a -> DB IO ()+shouldBeLeft' err action =+  liftTest . (`shouldBe` Left err) =<< runExceptT action++dbShouldBe :: (Show a, Eq a) => DB IO a -> a -> DB IO ()+dbShouldBe action a = liftTest . (`shouldBe` a) =<< action++dbShouldBeE ::+  (Show a, Eq a) =>+  ExceptT String (DB IO) a ->+  a ->+  ExceptT String (DB IO) ()+dbShouldBeE action a = liftTest . (`shouldBe` a) =<< action++dbShouldSatisfy :: (Show a) => DB IO a -> (a -> Bool) -> DB IO ()+dbShouldSatisfy action f = liftTest . (`shouldSatisfy` f) =<< action++testNewAcc :: Ctx -> Text -> ExceptT String (DB IO) (DBAccountId, DBAccount)+testNewAcc ctx name = do+  let fp = forceRight $ walletFingerprint btc ctx mnemPass+  d <- lift $ nextAccountDeriv fp btc+  let prv = forceRight $ signingKey btc ctx mnemPass d+      pub = deriveXPubKey ctx prv+  insertAccount btc ctx fp name pub++testNewAcc2 :: Ctx -> Text -> ExceptT String (DB IO) (DBAccountId, DBAccount)+testNewAcc2 ctx name = do+  let fp = forceRight $ walletFingerprint btc ctx mnemPass2+  d <- lift $ nextAccountDeriv fp btc+  let prv = forceRight $ signingKey btc ctx mnemPass2 d+      pub = deriveXPubKey ctx prv+  insertAccount btc ctx fp name pub++bestSpec :: Spec+bestSpec =+  it "can insert and get the best block" $+    runDBMemory $ do+      res1 <- getBest btcTest+      liftTest $ res1 `shouldBe` Nothing+      updateBest+        btc+        "00000000000000000004727b3cc0946dc2054f59e362369e0437325c0a992efb"+        814037+      updateBest+        btcTest+        "000000000000001400d91785c92efc15ccfc1949e8581f06db7914a707c3f81d"+        2535443+      res2 <- getBest btc+      res3 <- getBest btcTest+      liftTest $ do+        res2+          `shouldBe` Just+            ( "00000000000000000004727b3cc0946dc2054f59e362369e0437325c0a992efb",+              814037+            )+        res3+          `shouldBe` Just+            ( "000000000000001400d91785c92efc15ccfc1949e8581f06db7914a707c3f81d",+              2535443+            )++accountSpec :: Ctx -> Spec+accountSpec ctx = do+  it "can find the correct account derivations with smallestUnused" $ do+    smallestUnused [] `shouldBe` 0+    smallestUnused [0] `shouldBe` 1+    smallestUnused [1] `shouldBe` 0+    smallestUnused [2] `shouldBe` 0+    smallestUnused [1,2,3] `shouldBe` 0+    smallestUnused [1,3,4] `shouldBe` 0+    smallestUnused [0,2] `shouldBe` 1+    smallestUnused [0,1,3,4] `shouldBe` 2+    smallestUnused [0,1,3,4,6,7] `shouldBe` 2+    smallestUnused [0,1,2,3,4,5] `shouldBe` 6+  it "can create and rename accounts" $ do+    runDBMemoryE $ do+      -- No accounts+      lift $ shouldBeLeft $ getAccountByName $ Just "acc1"+      lift $ shouldBeLeft $ getAccountByName Nothing+      lift $ nextAccountDeriv walletFP btc `dbShouldBe` 0+      lift $ nextAccountDeriv walletFP btc `dbShouldBe` 0 -- Still 0+      lift $ getAccounts `dbShouldBe` []+      -- Check basic account properties+      (accId, acc) <- testNewAcc ctx "acc1"+      lift $ nextAccountDeriv walletFP btc `dbShouldBe` 1+      lift $ nextAccountDeriv walletFP btc `dbShouldBe` 1 -- Still 1+      liftTest $ do+        dBAccountName acc `shouldBe` "acc1"+        dBAccountWallet acc `shouldBe` DBWalletKey walletFPText+        dBAccountIndex acc `shouldBe` 0+        dBAccountNetwork acc `shouldBe` "btc"+        dBAccountDerivation acc `shouldBe` "/44'/0'/0'"+        dBAccountExternal acc `shouldBe` 0+        dBAccountInternal acc `shouldBe` 0+        dBAccountXPubKey acc `shouldBe` snd keysT+        dBAccountBalanceConfirmed acc `shouldBe` 0+        dBAccountBalanceUnconfirmed acc `shouldBe` 0+        dBAccountBalanceCoins acc `shouldBe` 0+      -- Check account retrieval+      lift $ getAccounts `dbShouldBe` [(accId, acc)]+      getAccountByName (Just "acc1") `dbShouldBeE` (accId, acc)+      getAccountByName Nothing `dbShouldBeE` (accId, acc) -- There is just 1 account+      getAccountById accId `dbShouldBeE` acc+      lift $ getAccounts `dbShouldBe` [(accId, acc)]+      -- Check the creation of a second account+      (accId2, acc2) <- testNewAcc ctx "acc2"+      lift $ nextAccountDeriv walletFP btc `dbShouldBe` 2+      getAccountByName (Just "acc2") `dbShouldBeE` (accId2, acc2)+      lift $ getAccounts `dbShouldBe` [(accId, acc), (accId2, acc2)]+      lift $ shouldBeLeft $ getAccountByName Nothing -- There are > 1 accounts+      liftTest $ do+        dBAccountWallet acc2 `shouldBe` DBWalletKey walletFPText+        dBAccountDerivation acc2 `shouldBe` "/44'/0'/1'"+        dBAccountIndex acc2 `shouldBe` 1+      -- Create an account in a new wallet+      (accId3, acc3) <- testNewAcc2 ctx "acc3"+      liftTest $ do+        dBAccountName acc3 `shouldBe` "acc3"+        dBAccountWallet acc3 `shouldBe` DBWalletKey walletFPText2+        dBAccountIndex acc3 `shouldBe` 0+        dBAccountNetwork acc3 `shouldBe` "btc"+        dBAccountDerivation acc3 `shouldBe` "/44'/0'/0'"+        dBAccountXPubKey acc3 `shouldBe` snd keysT2+      lift $ nextAccountDeriv walletFP2 btc `dbShouldBe` 1+      lift $+        getAccounts `dbShouldBe` [(accId3, acc3), (accId, acc), (accId2, acc2)]+      getAccountByName (Just "acc3") `dbShouldBeE` (accId3, acc3)+      lift $ shouldBeLeft $ getAccountByName Nothing -- There are > 1 accounts+      getAccountById accId3 `dbShouldBeE` acc3+      -- Rename an account+      lift $ shouldBeLeft $ renameAccount "acc2" "acc2"+      lift $ shouldBeLeft $ renameAccount "acc2" "acc3"+      lift $ shouldBeLeft $ renameAccount "doesnotexist" "hello world"+      acc2' <- renameAccount "acc2" "hello world"+      liftIO $ acc2' `shouldBe` acc2 {dBAccountName = "hello world"}+      lift $ getAccountNames `dbShouldBe` ["acc1", "hello world", "acc3"]+      _ <- renameAccount "hello world" "acc2"+      lift $ getAccountNames `dbShouldBe` ["acc1", "acc2", "acc3"]++extAddressSpec :: Ctx -> Config -> Spec+extAddressSpec ctx cfg =+  it "can generate external addresses" $ do+    runDBMemoryE $ do+      (accId, _) <- testNewAcc ctx "test"+      -- No addresses yet+      lift $ bestAddrWithFunds accId AddrInternal `dbShouldBe` Nothing+      lift $ bestAddrWithFunds accId AddrExternal `dbShouldBe` Nothing+      lift $ addressPage accId (Page 5 0) `dbShouldBe` []+      -- Generate external addresses+      ext1 <- genExtAddress ctx cfg accId "Address 1"+      liftTest $ do+        dBAddressIndex ext1 `shouldBe` 0+        dBAddressAccountWallet ext1 `shouldBe` DBWalletKey walletFPText+        dBAddressAccountDerivation ext1 `shouldBe` "/44'/0'/0'"+        dBAddressDerivation ext1 `shouldBe` "/0/0"+        dBAddressAddress ext1 `shouldBe` head extAddrsT+        dBAddressLabel ext1 `shouldBe` "Address 1"+        dBAddressInternal ext1 `shouldBe` False+        dBAddressFree ext1 `shouldBe` False+      lift $ addressPage accId (Page 5 0) `dbShouldBe` [ext1]+      ext2 <- genExtAddress ctx cfg accId "Address 2"+      liftTest $ do+        dBAddressIndex ext2 `shouldBe` 1+        dBAddressAccountWallet ext2 `shouldBe` DBWalletKey walletFPText+        dBAddressAccountDerivation ext2 `shouldBe` "/44'/0'/0'"+        dBAddressDerivation ext2 `shouldBe` "/0/1"+        dBAddressAddress ext2 `shouldBe` extAddrsT !! 1+        dBAddressLabel ext2 `shouldBe` "Address 2"+        dBAddressInternal ext2 `shouldBe` False+        dBAddressFree ext2 `shouldBe` False+      -- Test Paging+      lift $ addressPage accId (Page 5 0) `dbShouldBe` [ext2, ext1]+      lift $ addressPage accId (Page 1 0) `dbShouldBe` [ext2]+      lift $ addressPage accId (Page 1 1) `dbShouldBe` [ext1]+      lift $ addressPage accId (Page 1 2) `dbShouldBe` []+      -- Set address labels+      _ <- setAddrLabel accId 0 "test address"+      lift $+        addressPage accId (Page 5 0)+          `dbShouldBe` [ext2, ext1 {dBAddressLabel = "test address"}]+      -- Test the gap+      replicateM_ 18 $ genExtAddress ctx cfg accId "" -- We have 20 addresses+      lift $ shouldBeLeft $ genExtAddress ctx cfg accId "" -- fail gap+      lift $ addressPage accId (Page 100 0) `dbShouldSatisfy` ((== 20) . length)+      updateAddressBalances btc [Store.Balance (extAddrs !! 2) 0 0 0 1 1]+      updateAddressBalances btc [Store.Balance (extAddrs !! 4) 0 0 0 1 1]+      lift $ bestAddrWithFunds accId AddrExternal `dbShouldBe` Just 4+      replicateM_ 5 $ genExtAddress ctx cfg accId "" -- We have 25 addresses+      lift $ shouldBeLeft $ genExtAddress ctx cfg accId "" -- fail gap+      lift $ addressPage accId (Page 100 0) `dbShouldSatisfy` ((== 25) . length)+      -- Test discoverAccGenAddrs+      updateAddressBalances btc [Store.Balance (extAddrs !! 9) 0 0 0 1 1]+      discoverAccGenAddrs ctx cfg accId AddrExternal 0+      lift $ addressPage accId (Page 100 0) `dbShouldSatisfy` ((== 25) . length)+      discoverAccGenAddrs ctx cfg accId AddrExternal 25+      lift $ addressPage accId (Page 100 0) `dbShouldSatisfy` ((== 25) . length)+      discoverAccGenAddrs ctx cfg accId AddrExternal 26+      lift $ addressPage accId (Page 100 0) `dbShouldSatisfy` ((== 26) . length)+      -- Test getAddrDeriv+      lift $+        getAddrDeriv btc accId (head extAddrs)+          `dbShouldBe` Right (Deriv :/ 0 :/ 0)+      lift $+        getAddrDeriv btc accId (extAddrs !! 7)+          `dbShouldBe` Right (Deriv :/ 0 :/ 7)+      -- Test accound balances+      acc1 <- lift $ updateAccountBalances accId+      liftTest $ do+        dBAccountBalanceConfirmed acc1 `shouldBe` 0+        dBAccountBalanceUnconfirmed acc1 `shouldBe` 0+        dBAccountBalanceCoins acc1 `shouldBe` 0+      updateAddressBalances btc [Store.Balance (extAddrs !! 1) 2 0 1 1 2]+      updateAddressBalances btc [Store.Balance (extAddrs !! 4) 2 8 2 2 10]+      _ <- nextFreeIntAddr ctx cfg accId+      updateAddressBalances btc [Store.Balance (head intAddrs) 3 2 1 1 5]+      acc1' <- lift $ updateAccountBalances accId+      liftTest $ do+        dBAccountBalanceConfirmed acc1' `shouldBe` 7+        dBAccountBalanceUnconfirmed acc1' `shouldBe` 10+        dBAccountBalanceCoins acc1' `shouldBe` 4+      -- Test account indices+      liftTest $ do+        dBAccountInternal acc1' `shouldBe` 1+        dBAccountExternal acc1' `shouldBe` 26++intAddressSpec :: Ctx -> Config -> Spec+intAddressSpec ctx cfg =+  it "can generate internal addresses" $ do+    runDBMemoryE $ do+      (accId, _) <- testNewAcc ctx "test"+      -- Generate internal addresses+      int1 <- nextFreeIntAddr ctx cfg accId+      liftTest $ do+        dBAddressIndex int1 `shouldBe` 0+        dBAddressAccountWallet int1 `shouldBe` DBWalletKey walletFPText+        dBAddressAccountDerivation int1 `shouldBe` "/44'/0'/0'"+        dBAddressDerivation int1 `shouldBe` "/1/0"+        dBAddressAddress int1 `shouldBe` head intAddrsT+        dBAddressLabel int1 `shouldBe` "Internal Address"+        dBAddressInternal int1 `shouldBe` True+        dBAddressFree int1 `shouldBe` True+      -- Test the Free status+      nextFreeIntAddr ctx cfg accId `dbShouldBeE` int1 -- Should return the same address+      lift $ setAddrsFree AddrBusy [head intAddrsT] `dbShouldBe` 1+      lift $ setAddrsFree AddrBusy [intAddrsT !! 1] `dbShouldBe` 0+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 1+      _ <- lift $ setAddrsFree AddrFree [head intAddrsT]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 0+      _ <- lift $ setAddrsFree AddrBusy [intAddrsT !! 1]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 0+      _ <- lift $ setAddrsFree AddrBusy [head intAddrsT]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 2+      _ <- lift $ setAddrsFree AddrBusy [intAddrsT !! 2]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 3+      _ <- lift $ setAddrsFree AddrBusy [intAddrsT !! 3]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 4+      _ <- lift $ setAddrsFree AddrBusy [intAddrsT !! 4]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 5+      _ <- lift $ setAddrsFree AddrFree [intAddrsT !! 2]+      _ <- lift $ setAddrsFree AddrFree [intAddrsT !! 4]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 2+      _ <- lift $ setAddrsFree AddrBusy [intAddrsT !! 2]+      _ <- lift $ setAddrsFree AddrBusy [intAddrsT !! 5]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 4+      _ <- lift $ setAddrsFree AddrBusy [intAddrsT !! 4]+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 6+      -- Test account indices+      acc <- getAccountById accId+      liftTest $ do+        dBAccountInternal acc `shouldBe` 7+        dBAccountExternal acc `shouldBe` 0++emptyTxInfo :: TxInfo+emptyTxInfo =+  TxInfo+    (Just $ txid' 0)+    TxInternal+    0+    Map.empty+    Map.empty+    []+    Map.empty+    Map.empty+    []+    0+    0+    0+    (Store.MemRef 0)+    0+    Nothing++txsSpec :: Ctx -> Spec+txsSpec ctx =+  it "can manage transactions" $ do+    runDBMemoryE $ do+      (accId, _) <- testNewAcc ctx "test"+      -- Simple insert and retrieval+      (dbInfo, change) <- repsertTxInfo btc ctx accId emptyTxInfo+      liftTest $ do+        change `shouldBe` True+        dBTxInfoAccountWallet dbInfo `shouldBe` DBWalletKey walletFPText+        dBTxInfoAccountDerivation dbInfo `shouldBe` "/44'/0'/0'"+        dBTxInfoBlockRef dbInfo `shouldBe` S.encode (Store.MemRef 0)+        dBTxInfoConfirmed dbInfo `shouldBe` False+      -- Reinserting should produce no change+      (dbInfo2, change2) <- repsertTxInfo btc ctx accId emptyTxInfo+      liftTest $ do+        change2 `shouldBe` False+        dbInfo2 `shouldBe` dbInfo+      txsPage ctx accId (Page 5 0) `dbShouldBeE` [emptyTxInfo]+      -- Check that confirmations are updated correctly+      lift $ updateBest btc (bid' 0) 0+      getConfirmedTxs accId True `dbShouldBeE` []+      getConfirmedTxs accId False `dbShouldBeE` [txid' 0]+      txsPage ctx accId (Page 5 0) `dbShouldBeE` [emptyTxInfo]+      (dbInfo', change') <-+        repsertTxInfo+          btc+          ctx+          accId+          emptyTxInfo {txInfoBlockRef = Store.BlockRef 0 0}+      liftTest $ do+        change' `shouldBe` True+        dBTxInfoBlockRef dbInfo' `shouldBe` S.encode (Store.BlockRef 0 0)+        dBTxInfoConfirmed dbInfo' `shouldBe` True+        dBTxInfoCreated dbInfo' `shouldBe` dBTxInfoCreated dbInfo+      txsPage ctx accId (Page 5 0)+        `dbShouldBeE` [ emptyTxInfo+                          { txInfoBlockRef = Store.BlockRef 0 0,+                            txInfoConfirmations = 1+                          }+                      ]+      getConfirmedTxs accId True `dbShouldBeE` [txid' 0]+      getConfirmedTxs accId False `dbShouldBeE` []+      lift $ updateBest btcTest (bid' 0) 10+      lift $ updateBest btc (bid' 0) 20+      txsPage ctx accId (Page 5 0)+        `dbShouldBeE` [ emptyTxInfo+                          { txInfoBlockRef = Store.BlockRef 0 0,+                            txInfoConfirmations = 21+                          }+                      ]+      (dbInfo'', change'') <-+        repsertTxInfo+          btc+          ctx+          accId+          emptyTxInfo {txInfoBlockRef = Store.BlockRef 10 0}+      liftTest $ do+        change'' `shouldBe` True+        dBTxInfoBlockRef dbInfo'' `shouldBe` S.encode (Store.BlockRef 10 0)+        dBTxInfoConfirmed dbInfo'' `shouldBe` True+      txsPage ctx accId (Page 5 0)+        `dbShouldBeE` [ emptyTxInfo+                          { txInfoBlockRef = Store.BlockRef 10 0,+                            txInfoConfirmations = 11+                          }+                      ]++coinSpec :: Ctx -> Spec+coinSpec ctx =+  it "can manage coins" $ do+    runDBMemoryE $ do+      (accId, _) <- testNewAcc ctx "test"+      -- Insert a single coin+      let coin1 = coin' ctx (txid' 0, 0) Nothing (head extAddrs) 10+      (c, dbCoin) <- second head <$> refreshCoins btc accId extAddrs [coin1]+      liftTest $ do+        c `shouldBe` 1+        dBCoinAccountWallet dbCoin `shouldBe` DBWalletKey walletFPText+        dBCoinAccountDerivation dbCoin `shouldBe` "/44'/0'/0'"+        dBCoinAddress dbCoin `shouldBe` head extAddrsT+        dBCoinConfirmed dbCoin `shouldBe` False+        dBCoinLocked dbCoin `shouldBe` False+      -- Test JSON coins+      let jsonCoin1 = forceRight $ toJsonCoin btc Nothing dbCoin+      liftTest $+        jsonCoin1+          `shouldBe` JsonCoin+            { jsonCoinOutpoint = OutPoint (txid' 0) 0,+              jsonCoinAddress = head extAddrs,+              jsonCoinValue = 10,+              jsonCoinBlock = Store.MemRef 0,+              jsonCoinConfirmations = 0,+              jsonCoinLocked = False+            }+      coinPage btc accId (Page 5 0) `dbShouldBeE` [jsonCoin1]+      getSpendableCoins btc accId 0 `dbShouldBeE` [coin1]+      getSpendableCoins btc accId 1 `dbShouldBeE` []+      -- Nothing should happen when refreshing the same data+      refreshCoins btc accId extAddrs [coin1] `dbShouldBeE` (0, [])+      -- Confirm and update the coin+      let coin1' = coin1 {Store.block = Store.BlockRef 1 0} :: Store.Unspent+      refreshCoins btc accId extAddrs [coin1'] `dbShouldBeE` (1, [])+      getSpendableCoins btc accId 0 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 1 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 2 `dbShouldBeE` []+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin1+                          { jsonCoinBlock = Store.BlockRef 1 0,+                            jsonCoinConfirmations = 1+                          }+                      ]+      -- Best = 0+      lift $ updateBest btc (bid' 0) 0+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin1+                          { jsonCoinBlock = Store.BlockRef 1 0,+                            jsonCoinConfirmations = 1+                          }+                      ]+      getSpendableCoins btc accId 0 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 1 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 2 `dbShouldBeE` []+      -- Best = 1+      lift $ updateBest btc (bid' 0) 1+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin1+                          { jsonCoinBlock = Store.BlockRef 1 0,+                            jsonCoinConfirmations = 1+                          }+                      ]+      getSpendableCoins btc accId 0 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 1 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 2 `dbShouldBeE` []+      -- Best = 10+      lift $ updateBest btc (bid' 0) 10+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin1+                          { jsonCoinBlock = Store.BlockRef 1 0,+                            jsonCoinConfirmations = 10+                          }+                      ]+      getSpendableCoins btc accId 9 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 10 `dbShouldBeE` [coin1']+      getSpendableCoins btc accId 11 `dbShouldBeE` []+      let coin1'' = coin1 {Store.block = Store.BlockRef 10 0} :: Store.Unspent+      refreshCoins btc accId extAddrs [coin1''] `dbShouldBeE` (1, [])+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin1+                          { jsonCoinBlock = Store.BlockRef 10 0,+                            jsonCoinConfirmations = 1+                          }+                      ]+      getSpendableCoins btc accId 0 `dbShouldBeE` [coin1'']+      getSpendableCoins btc accId 1 `dbShouldBeE` [coin1'']+      getSpendableCoins btc accId 2 `dbShouldBeE` []+      -- Lock the coin+      lift $ setLockCoin (OutPoint (txid' 0) 0) True `dbShouldBe` 1+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin1+                          { jsonCoinBlock = Store.BlockRef 10 0,+                            jsonCoinConfirmations = 1,+                            jsonCoinLocked = True+                          }+                      ]+      getSpendableCoins btc accId 0 `dbShouldBeE` []+      lift $ setLockCoin (OutPoint (txid' 0) 0) True `dbShouldBe` 0+      lift $ setLockCoin (OutPoint (txid' 0) 0) False `dbShouldBe` 1+      lift $ setLockCoin (OutPoint (txid' 0) 0) False `dbShouldBe` 0+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin1+                          { jsonCoinBlock = Store.BlockRef 10 0,+                            jsonCoinConfirmations = 1,+                            jsonCoinLocked = False+                          }+                      ]+      getSpendableCoins btc accId 0 `dbShouldBeE` [coin1'']+      getSpendableCoins btc accId 1 `dbShouldBeE` [coin1'']+      getSpendableCoins btc accId 2 `dbShouldBeE` []+      -- Delete the coin+      refreshCoins btc accId extAddrs [] `dbShouldBeE` (1, [])+      getSpendableCoins btc accId 0 `dbShouldBeE` []+      coinPage btc accId (Page 5 0) `dbShouldBeE` []++checkFree :: Int -> Bool -> ExceptT String (DB IO) ()+checkFree i free =+  lift $+    ( dBAddressFree . entityVal . fromJust+        <$> P.getBy (UniqueAddress (intAddrsT !! i))+    )+      `dbShouldBe` free++pendingTxsSpec :: Ctx -> Config -> Spec+pendingTxsSpec ctx cfg =+  it "can manage pending transactions" $ do+    runDBMemoryE $ do+      (accId, _) <- testNewAcc ctx "test"+      lift $ updateBest btc (bid' 0) 0+      -- Build some test data coins/txs+      (dBAddressAddress <$> genExtAddress ctx cfg accId "")+        `dbShouldBeE` head extAddrsT+      (dBAddressAddress <$> nextFreeIntAddr ctx cfg accId)+        `dbShouldBeE` head intAddrsT+      checkFree 0 True+      lift $ setAddrsFree AddrBusy [head intAddrsT] `dbShouldBe` 1+      checkFree 0 False+      let fundTx1 = tx' ctx [(txid' 1, 0)] [(addr' 0, 20)]+          fundTx2 = tx' ctx [(txid' 1, 1)] [(iAddr' 0, 10)]+          coin1 = coin' ctx (txHash fundTx1, 0) (Just 0) (addr' 0) 20+          coin2 = coin' ctx (txHash fundTx2, 0) (Just 0) (iAddr' 0) 10+      -- Insert the dependent transactions+      lift $ insertRawTx fundTx1+      lift $ insertRawTx fundTx2+      -- Insert two coins in the database+      (c, dbCoins) <- refreshCoins btc accId (extAddrs <> intAddrs) [coin1, coin2]+      liftTest $ c `shouldBe` 2+      let jsonCoin1 = forceRight $ toJsonCoin btc Nothing (head dbCoins)+          jsonCoin2 = forceRight $ toJsonCoin btc Nothing (dbCoins !! 1)+      tsd <- buildTxSignData btc ctx cfg gen accId [(oAddr' 0, 8)] 0 0 False 1+      liftTest $+        tsd+          `shouldBe` TxSignData+            { txSignDataTx =+                tx'+                  ctx+                  [(txHash fundTx1, 0)]+                  [(iAddr' 1, 12), (oAddr' 0, 8)],+              txSignDataInputs = [fundTx1],+              txSignDataInputPaths = [Deriv :/ 0 :/ 0],+              txSignDataOutputPaths = [Deriv :/ 1 :/ 1],+              txSignDataSigned = False+            }+      -- Import the pending transaction+      checkFree 0 False+      checkFree 1 True+      let h1 = "db1085753d1d9b14005dd2951ff643803d4fa7ffa3bde6841dbb817958cb74e1"+      importPendingTx btc ctx accId tsd `dbShouldBeE` h1+      liftTest $ nosigTxHash (txSignDataTx tsd) `shouldBe` h1+      -- Check locked coins+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin2 {jsonCoinLocked = False},+                        jsonCoin1 {jsonCoinLocked = True}+                      ]+      ((txInfoPending <$>) <$> pendingTxPage ctx accId (Page 5 0))+        `dbShouldBeE` [Just $ TxInfoPending h1 False False]+      -- Check address free status+      checkFree 0 False+      checkFree 1 False+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 2+      lift $+        shouldBeLeft' "chooseCoins: No solution found" $+          buildTxSignData btc ctx cfg gen accId [(oAddr' 1, 12)] 0 0 False 1+      lift $+        shouldBeLeft' "The transaction already exists" $+          importPendingTx btc ctx accId tsd+      -- Create second pending transaction+      tsd2 <- buildTxSignData btc ctx cfg gen accId [(oAddr' 1, 7)] 0 0 False 1+      liftTest $+        tsd2+          `shouldBe` TxSignData+            { txSignDataTx =+                tx'+                  ctx+                  [(txHash fundTx2, 0)]+                  [(iAddr' 2, 3), (oAddr' 1, 7)],+              txSignDataInputs = [fundTx2],+              txSignDataInputPaths = [Deriv :/ 1 :/ 0],+              txSignDataOutputPaths = [Deriv :/ 1 :/ 2],+              txSignDataSigned = False+            }+      -- Import the second transaction+      checkFree 2 True+      let h2 = "bb0e46f95f90bf0d2b0805655a77e632a0bd5088d5722d0055bc2862158ba747"+      importPendingTx btc ctx accId tsd2 `dbShouldBeE` h2+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin2 {jsonCoinLocked = True},+                        jsonCoin1 {jsonCoinLocked = True}+                      ]+      ((txInfoPending <$>) <$> pendingTxPage ctx accId (Page 5 0))+        `dbShouldBeE` [ Just $ TxInfoPending h2 False False,+                        Just $ TxInfoPending h1 False False+                      ]+      checkFree 0 False+      checkFree 1 False+      checkFree 2 False+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 3+      lift $+        shouldBeLeft' "chooseCoins: No solution found" $+          buildTxSignData btc ctx cfg gen accId [(oAddr' 2, 1)] 0 0 False 1+      -- Delete first transaction+      deletePendingTx ctx h1 `dbShouldBeE` (1, 1)+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin2 {jsonCoinLocked = True},+                        jsonCoin1 {jsonCoinLocked = False}+                      ]+      ((txInfoPending <$>) <$> pendingTxPage ctx accId (Page 5 0))+        `dbShouldBeE` [Just $ TxInfoPending h2 False False]+      checkFree 0 False+      checkFree 1 True+      checkFree 2 False+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 1+      -- Create transaction with no change+      tsd3 <- buildTxSignData btc ctx cfg gen accId [(oAddr' 2, 20)] 0 0 False 1+      liftTest $+        tsd3+          `shouldBe` TxSignData+            { txSignDataTx =+                tx'+                  ctx+                  [(txHash fundTx1, 0)]+                  [(oAddr' 2, 20)],+              txSignDataInputs = [fundTx1],+              txSignDataInputPaths = [Deriv :/ 0 :/ 0],+              txSignDataOutputPaths = [],+              txSignDataSigned = False+            }+      let h3 = "abe82b09dddb8abdb5490c3d404d34f2ff3c8f7b893bbe80f0af4018576e1f9e"+      importPendingTx btc ctx accId tsd3 `dbShouldBeE` h3+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin2 {jsonCoinLocked = True},+                        jsonCoin1 {jsonCoinLocked = True}+                      ]+      ((txInfoPending <$>) <$> pendingTxPage ctx accId (Page 5 0))+        `dbShouldBeE` [ Just $ TxInfoPending h3 False False,+                        Just $ TxInfoPending h2 False False+                      ]+      checkFree 0 False+      checkFree 1 True+      checkFree 2 False+      (dBAddressIndex <$> nextFreeIntAddr ctx cfg accId) `dbShouldBeE` 1+      -- Import a signed transaction+      let resE = signWalletTx btc ctx tsd2 (fst $ keys ctx)+      liftTest $ resE `shouldSatisfy` isRight+      let tsd2' = fst $ forceRight resE+      liftTest $ txSignDataSigned tsd2' `shouldBe` True+      importPendingTx btc ctx accId tsd2' `dbShouldBeE` h2+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin2 {jsonCoinLocked = True},+                        jsonCoin1 {jsonCoinLocked = True}+                      ]+      ((txInfoPending <$>) <$> pendingTxPage ctx accId (Page 5 0))+        `dbShouldBeE` [ Just $ TxInfoPending h3 False False,+                        Just $ TxInfoPending h2 True False+                      ]+      checkFree 0 False+      checkFree 1 True+      checkFree 2 False+      lift $ do+        shouldBeLeft' "The transaction already exists" $+          importPendingTx btc ctx accId tsd2'+        shouldBeLeft' "Can not replace a signed transaction with an unsigned one" $+          importPendingTx btc ctx accId tsd2+      -- Set tsd2 to online+      lift $ do+        setPendingTxOnline h2 `dbShouldBe` 1+        shouldBeLeft' "The transaction is already online" $+          importPendingTx btc ctx accId tsd2'+        -- Can not delete an online transaction+        shouldBeLeft $ deletePendingTx ctx h2+      ((txInfoPending <$>) <$> pendingTxPage ctx accId (Page 5 0))+        `dbShouldBeE` [ Just $ TxInfoPending h3 False False,+                        Just $ TxInfoPending h2 True True+                      ]+      lift $ deletePendingTxOnline $ DBPendingTxKey $ txHashToHex h2+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin2 {jsonCoinLocked = True},+                        jsonCoin1 {jsonCoinLocked = True}+                      ]+      ((txInfoPending <$>) <$> pendingTxPage ctx accId (Page 5 0))+        `dbShouldBeE` [Just $ TxInfoPending h3 False False]+      checkFree 0 False+      checkFree 1 True+      checkFree 2 False+      -- Check for other errors+      let fundTx3 = tx' ctx [(txid' 2, 0)] [(addr' 8, 30)]+          coin3 = coin' ctx (txHash fundTx3, 0) (Just 0) (addr' 8) 30+      lift $ do+        shouldBeLeft' "A coin referenced by the transaction is locked" $+          importPendingTx btc ctx accId $+            TxSignData+              { txSignDataTx =+                  tx'+                    ctx+                    [(txHash fundTx1, 0)]+                    [(oAddr' 3, 20)],+                txSignDataInputs = [fundTx3, fundTx1],+                txSignDataInputPaths = [Deriv :/ 0 :/ 0],+                txSignDataOutputPaths = [],+                txSignDataSigned = False+              }+        shouldBeLeft' "A coin referenced by the transaction does not exist" $+          importPendingTx btc ctx accId $+            TxSignData+              { txSignDataTx =+                  tx'+                    ctx+                    [(txHash fundTx3, 0)]+                    [(oAddr' 3, 20)],+                txSignDataInputs = [fundTx3, fundTx1],+                txSignDataInputPaths = [Deriv :/ 0 :/ 8],+                txSignDataOutputPaths = [],+                txSignDataSigned = False+              }+      (_, dbCoins3) <- refreshCoins btc accId (extAddrs <> intAddrs) [coin1, coin3]+      let jsonCoin3 = forceRight $ toJsonCoin btc Nothing (head dbCoins3)+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin3 {jsonCoinLocked = False},+                        jsonCoin1 {jsonCoinLocked = True}+                      ]+      lift $+        shouldBeLeft' "Some referenced addresses do not exist" $+          importPendingTx btc ctx accId $+            TxSignData+              { txSignDataTx =+                  tx'+                    ctx+                    [(txHash fundTx3, 0)]+                    [(iAddr' 1, 5), (oAddr' 3, 25)],+                txSignDataInputs = [fundTx3],+                txSignDataInputPaths = [Deriv :/ 0 :/ 8],+                txSignDataOutputPaths = [Deriv :/ 1 :/ 1],+                txSignDataSigned = False+              }+      -- The transaction is not being rolled back so we roll it back manually+      replicateM_ 10 $ genExtAddress ctx cfg accId ""+      _ <- lift $ setLockCoin (jsonCoinOutpoint jsonCoin3) False+      coinPage btc accId (Page 5 0)+        `dbShouldBeE` [ jsonCoin3 {jsonCoinLocked = False},+                        jsonCoin1 {jsonCoinLocked = True}+                      ]+      checkFree 0 False+      checkFree 1 True+      checkFree 2 False+      lift $+        shouldBeLeft' "Some of the internal output addresses are not free" $+          importPendingTx btc ctx accId $+            TxSignData+              { txSignDataTx =+                  tx'+                    ctx+                    [(txHash fundTx3, 0)]+                    [(iAddr' 2, 1), (oAddr' 3, 25)],+                txSignDataInputs = [fundTx3],+                txSignDataInputPaths = [Deriv :/ 0 :/ 8],+                txSignDataOutputPaths = [Deriv :/ 1 :/ 2],+                txSignDataSigned = False+              }
+ test/Haskoin/Wallet/EntropySpec.hs view
@@ -0,0 +1,265 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Haskoin.Wallet.EntropySpec where++import Control.Exception (evaluate)+import Control.Monad+import Control.Monad.Trans (liftIO)+import qualified Data.ByteString as BS+import Data.Default (def)+import Data.Maybe+import Data.Text (Text)+import Haskoin+import Haskoin.Util.Arbitrary+import Haskoin.Wallet.Config+import Haskoin.Wallet.Database+import Haskoin.Wallet.Entropy+import Haskoin.Wallet.Signing+import Haskoin.Wallet.TestUtils+import Test.HUnit+import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck++spec :: Spec+spec = do+  let cfg = def :: Config+  prepareContext $ \ctx -> do+    diceSpec+    entropySpec+    mnemonicSpec ctx cfg++diceSpec :: Spec+diceSpec =+  describe "Base6 Encoding" $ do+    it "can encode a Word8 to Base6" $ do+      let vs = [6, 1, 2, 3, 4, 5]+          xs = [[a, b, c] | a <- vs, b <- vs, c <- vs]+          ys = [[a, b] | a <- vs, b <- vs]+          as = [0x00 .. 0xd7]+          bs = [0xd8 .. 0xfb]+          cs = [0xfc .. 0xff]+      forM_ (zip as xs) $ \(a, b) -> word8ToBase6 a `shouldBe` b+      forM_ (zip bs ys) $ \(a, b) -> word8ToBase6 a `shouldBe` b+      forM_ cs $ \c -> word8ToBase6 c `shouldBe` []+      -- Checking a few fixed values+      word8ToBase6 0x00 `shouldBe` [6, 6, 6]+      word8ToBase6 0x01 `shouldBe` [6, 6, 1]+      word8ToBase6 0xd7 `shouldBe` [5, 5, 5]+      word8ToBase6 0xd8 `shouldBe` [6, 6]+      word8ToBase6 0xd9 `shouldBe` [6, 1]+      word8ToBase6 0xfb `shouldBe` [5, 5]+    it "can decode base6 to Word8" $ do+      base6ToWord8 [6] [] `shouldBe` ([], [False, False])+      base6ToWord8 [1] [] `shouldBe` ([], [False, True])+      base6ToWord8 [2] [] `shouldBe` ([], [True, False])+      base6ToWord8 [3] [] `shouldBe` ([], [True, True])+      base6ToWord8 [4] [] `shouldBe` ([], [False])+      base6ToWord8 [5] [] `shouldBe` ([], [True])+      base6ToWord8 [5, 6] [] `shouldBe` ([], [True, False, False])+      base6ToWord8 [6, 6, 6, 5] []+        `shouldBe` ([], [False, False, False, False, False, False, True])+      base6ToWord8 [6, 6, 6, 6] [] `shouldBe` ([0x00], [])+      base6ToWord8 [6, 6, 6, 1] [] `shouldBe` ([0x01], [])+      base6ToWord8 [3, 3, 3, 3] [] `shouldBe` ([0xff], [])+      base6ToWord8 [4, 3, 3, 3, 3] [] `shouldBe` ([0xff], [False])+      base6ToWord8 [1, 1, 1, 1, 3, 3, 3, 3] [] `shouldBe` ([0x55, 0xff], [])++entropySpec :: Spec+entropySpec = do+  it "can mix entropy" $ do+    evaluate (xorBytes BS.empty $ BS.pack [0x00]) `shouldThrow` anyException+    evaluate (xorBytes (BS.pack [0x00]) BS.empty) `shouldThrow` anyException+    evaluate (xorBytes (BS.pack [0x00, 0x00]) (BS.pack [0x00]))+      `shouldThrow` anyException+    evaluate (xorBytes (BS.pack [0x00]) (BS.pack [0x00, 0x00]))+      `shouldThrow` anyException+    xorBytes (BS.pack [0x00]) (BS.pack [0x00]) `shouldBe` BS.pack [0x00]+    xorBytes (BS.pack [0x00]) (BS.pack [0xff]) `shouldBe` BS.pack [0xff]+    xorBytes (BS.pack [0xff]) (BS.pack [0x00]) `shouldBe` BS.pack [0xff]+    xorBytes (BS.pack [0xff]) (BS.pack [0xff]) `shouldBe` BS.pack [0x00]+    xorBytes (BS.pack [0xaa]) (BS.pack [0x55]) `shouldBe` BS.pack [0xff]+    xorBytes (BS.pack [0x55, 0xaa]) (BS.pack [0xaa, 0x55])+      `shouldBe` BS.pack [0xff, 0xff]+    xorBytes (BS.pack [0x7a, 0x54]) (BS.pack [0xd3, 0x8e])+      `shouldBe` BS.pack [0xa9, 0xda]+  it "can split entropy" $ do+    splitEntropyWith (BS.pack [0x00]) [] `shouldBe` [BS.pack [0x00]]+    splitEntropyWith (BS.pack [0x00]) [BS.pack [0x00]]+      `shouldBe` [BS.pack [0x00], BS.pack [0x00]]+    splitEntropyWith (BS.pack [0x55]) [BS.pack [0xaa]]+      `shouldBe` [BS.pack [0xff], BS.pack [0xaa]]+    splitEntropyWith (BS.pack [0x55, 0xaa]) [BS.pack [0xaa, 0x55]]+      `shouldBe` [BS.pack [0xff, 0xff], BS.pack [0xaa, 0x55]]+  prop "prop: can split entropy x2" $+    forAll (arbitraryBSn 32) $ \s ->+      forAll (arbitraryBSn 32) $ \k ->+        splitEntropyWith s [k] `shouldBe` [s `xorBytes` k, k]+  prop "prop: can split entropy x3" $+    forAll (arbitraryBSn 32) $ \s ->+      forAll (arbitraryBSn 32) $ \k1 ->+        forAll (arbitraryBSn 32) $ \k2 ->+          splitEntropyWith s [k1, k2]+            `shouldBe` [s `xorBytes` k1 `xorBytes` k2, k1, k2]+  prop "prop: can reconstruct entropy" $+    forAll (arbitraryBSn 32) $ \s ->+      forAll (arbitraryBSn 32) $ \k1 ->+        forAll (arbitraryBSn 32) $ \k2 -> do+          case splitEntropyWith s [k1, k2] of+            [a, b, c] -> (a `xorBytes` b `xorBytes` c) `shouldBe` s+            _ -> expectationFailure "Invalid splitEntropyWith"+  prop "prop: can reconstruct original mnemonic" $+    forAll (arbitraryBSn 32) $ \s ->+      forAll (arbitraryBSn 32) $ \k1 ->+        case splitEntropyWith s [k1] of+          [a, b] ->+            let mnem = toMnemonic s+                splitMnems = mapM toMnemonic [a, b]+                unsplitMnem = mergeMnemonicParts $ forceRight splitMnems+             in unsplitMnem `shouldBe` mnem+          _ -> expectationFailure "Invalid splitEntropyWith"++mnemonicSpec :: Ctx -> Config -> Spec+mnemonicSpec ctx cfg =+  describe "Mnemonic API" $ do+    -- https://github.com/iancoleman/bip39/issues/58+    it "Can derive iancoleman issue 58" $ do+      let m = "fruit wave dwarf banana earth journey tattoo true farm silk olive fence"+          p = "banana"+          mnemPass = MnemonicPass m p+          xpub = forceRight $ deriveXPubKey ctx <$> signingKey btc ctx mnemPass 0+          (addr0, _) = derivePathAddr ctx xpub extDeriv 0+      addrToText btc addr0 `shouldBe` Just "17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F"+    it "Passes the test vectors 3 (zero padding)" $+      mapM_ (testVector ctx) testVectors3+    it "Passes the test vectors 4 (zero padding)" $+      mapM_ (testVector ctx) testVectors4+    it "Passes the BIP-44 test vectors" $+      mapM_ (testBip44Vector ctx cfg) bip44Vectors++testVector :: Ctx -> (Text, HardPath, Text, Text) -> Assertion+testVector ctx (seedT, deriv, pubT, prvT) = do+  xPrvExport btc xPrv `shouldBe` prvT+  xPubExport btc ctx xPub `shouldBe` pubT+  where+    seed = fromJust $ decodeHex seedT+    xPrv = derivePath ctx deriv (makeXPrvKey seed)+    xPub = deriveXPubKey ctx xPrv++-- bitpay/bitcore-lib#47 and iancoleman/bip39#58+-- https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-3+testVectors3 :: [(Text, HardPath, Text, Text)]+testVectors3 =+  [ ( "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",+      Deriv, -- m/+      "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",+      "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6"+    ),+    ( "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",+      Deriv :| 0, -- m/0'+      "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y",+      "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L"+    )+  ]++-- btcsuite/btcutil#172+-- https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-4+testVectors4 :: [(Text, HardPath, Text, Text)]+testVectors4 =+  [ ( "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678",+      Deriv, -- m/+      "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa",+      "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv"+    ),+    ( "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678",+      Deriv :| 0, -- m/0'+      "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m",+      "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G"+    ),+    ( "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678",+      Deriv :| 0 :| 1, -- m/0'/1'+      "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt",+      "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1"+    )+  ]++testBip44Vector :: Ctx -> Config -> (Text, Text, Text, Text) -> Assertion+testBip44Vector ctx cfg (mnem, pass, addr0, addr1) = do+  runDBMemoryE $ do+    (accId, _) <- insertAccount btc ctx walletFP "test" pub+    extAddr <- genExtAddress ctx cfg accId ""+    intAddr <- nextFreeIntAddr ctx cfg accId+    liftIO $ addr0 `shouldBe` dBAddressAddress extAddr+    liftIO $ addr1 `shouldBe` dBAddressAddress intAddr+  where+    mnemPass = MnemonicPass mnem pass+    walletFP = forceRight $ walletFingerprint btc ctx mnemPass+    prv = forceRight $ signingKey btc ctx mnemPass 0+    pub = deriveXPubKey ctx prv++-- (Mnemonic, BIP38 password, external 0/0, internal 1/0)+bip44Vectors :: [(Text, Text, Text, Text)]+bip44Vectors =+  [ ( "modify truck lens identify brief coffee \+      \gather volcano fatal together muscle elephant",+      "",+      "1KiWbwzHhwH2KdLyreGuJq36SP2pPeGeim",+      "1BY9FShyC6rnEUdPAVQTX37TFZqEkR1enQ"+    ),+    ( "modify truck lens identify brief coffee \+      \gather volcano fatal together muscle elephant",+      "password",+      "1Ha18XQ74YfCnuUAjGTF9rnbS7pm1aCzW4",+      "17SounFZ11k8urp9Pee2FNYjzGU5qwLUfT"+    ),+    ( "modify truck lens identify brief coffee \+      \gather volcano fatal together muscle elephant",+      "Hello world",+      "1KpHPSa9eMohQznkddzmHT9AeMAscYe29Y",+      "16WJrKzdik1HgnoiNiLUW2uGDqry2fSGVx"+    ),+    ( "fiscal gadget drastic coconut awful crime \+      \during common salmon manage random cost \+      \evil owner city",+      "",+      "131dALC6WxwTZzTsHUsifAJ8bkQdDEeeqJ",+      "1JHCmBLzDARCdDgn2WwJQr88tGW8V3EyKj"+    ),+    ( "host mind elephant tone sound apple \+      \service tomato subject attend motion stick \+      \fuel fan rail bamboo tree build",+      "",+      "15nbJ6oHvYiapKu1vBYmyQzVSTjiJNcfuV",+      "12rMvadbhRxFaUP4FoRUmABa1JEPLrrqHL"+    ),+    ( "flight skill wisdom mixture patch dirt \+      \trouble behind chair glad detect swarm \+      \swap truly cruise medal walnut glide \+      \wrestle route defy",+      "",+      "12uDDJU7BJUxJomzbFt5BW3GnukGZz6Y5t",+      "1Gv62T4UkLxJ6dnsmNXENMgjBhLzEzJ1Sc"+    ),+    ( "use turtle trap pause spin venue \+      \hazard hope slot april cattle fork \+      \stand finish arrest nasty acoustic clog \+      \entire course universe evil desert produce",+      "",+      "19fJdt5HjJUrmnvKM8fTS43o9aK3NmFZMT",+      "17Kmofo8651DdydBS7zVVy9JLkixkxj6SK"+    ),+    ( "use turtle trap pause spin venue \+      \hazard hope slot april cattle fork \+      \stand finish arrest nasty acoustic clog \+      \entire course universe evil desert produce",+      "nMCm#Qe7u-*LG^99KS%hSNZJh6w&e&w4S&7HA-22^7vDE#F\+      \@=hNZZZ$M=ZSYG42J8P+U+3sf$xS6YpJf7DemaU#fLEVBW8\+      \!FmugPj=K=E*KDmUuQh_2%7K2W8@RJ4?Pua2HFCb8$@ZB^p\+      \s=T=PNFy5APBBdaz_eTq&r&v3hp6P-tTfvNP9J+xfQ-FKZS\+      \7_?g+P*9MTkpjy*K5=8y+y9db#Sv+b*5bfmV8?@YKrf#AEH\+      \7!ARqBXMhS7d%GYAxB%RG",+      "1L1oKPYJ9j3EFX5CcFXz1d3kkAW9Hoh5ic",+      "1J7jpmP7NVoryofiVSCKMMvMitkCP3U6Qj"+    )+  ]
+ test/Haskoin/Wallet/SigningSpec.hs view
@@ -0,0 +1,471 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoFieldSelectors #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Haskoin.Wallet.SigningSpec where++import Control.Arrow (second)+import qualified Data.ByteString as BS+import Data.Either (fromRight)+import qualified Data.Map as Map+import Data.Maybe (fromJust, fromMaybe)+import qualified Data.Serialize as S+import Data.Text (Text)+import Data.Word (Word32, Word8)+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Wallet.Database+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.Signing+import Haskoin.Wallet.TestUtils+import Haskoin.Wallet.TxInfo+import Numeric.Natural (Natural)+import System.Random (StdGen, mkStdGen)+import Test.Hspec++spec :: Spec+spec =+  prepareContext $ \ctx -> do+    buildWalletTxSpec ctx+    signWalletTxSpec ctx++buildWalletTxSpec :: Ctx -> Spec+buildWalletTxSpec ctx =+  describe "Transaction builder" $ do+    it "can build a transaction" $ do+      let coins =+            [ coin' ctx (txid' 1, 0) Nothing (addr' 0) 100000000,+              coin' ctx (txid' 1, 1) Nothing (addr' 1) 200000000,+              coin' ctx (txid' 1, 2) Nothing (addr' 1) 300000000,+              coin' ctx (txid' 1, 3) Nothing (addr' 2) 400000000+            ]+          change = iAddr' 0+          rcps = [(oAddr' 0, 200000000), (oAddr' 1, 200000000)]+          resE = buildWalletTx btc ctx gen rcps change coins 314 10000 False+      (fst <$> resE)+        `shouldBe` Right+          ( tx'+              ctx+              [(txid' 1, 2), (txid' 1, 1), (txid' 1, 0)] -- Greedy algorithm+              ((rcps !! 1) : (change, 199825416) : [head rcps])+          )+      (snd <$> resE)+        `shouldBe` Right [coins !! 2, coins !! 1, head coins]+    it "can fail to build a transaction if funds are insufficient" $ do+      let coins =+            [ coin' ctx (txid' 1, 0) Nothing (addr' 0) 100000000,+              coin' ctx (txid' 1, 1) Nothing (addr' 1) 200000000,+              coin' ctx (txid' 1, 2) Nothing (addr' 1) 300000000,+              coin' ctx (txid' 1, 3) Nothing (addr' 2) 400000000+            ]+          change = iAddr' 0+          rcps = [(oAddr' 0, 500000000), (oAddr' 1, 500000000)]+          resE = buildWalletTx btc ctx gen rcps change coins 1 10000 False+      resE `shouldBe` Left "chooseCoins: No solution found"+    it "will drop the change output if it is dust" $ do+      let coins =+            [ coin' ctx (txid' 1, 0) Nothing (addr' 0) 100000000,+              coin' ctx (txid' 1, 1) Nothing (addr' 1) 200000000,+              coin' ctx (txid' 1, 2) Nothing (addr' 1) 300000000,+              coin' ctx (txid' 1, 3) Nothing (addr' 2) 400000000+            ]+          change = iAddr' 0+          rcps = [(oAddr' 0, 500000000), (oAddr' 1, 499990000)]+          resE1 = buildWalletTx btc ctx gen rcps change coins 0 9999 False+          resE2 = buildWalletTx btc ctx gen rcps change coins 0 10000 False+          resE3 = buildWalletTx btc ctx gen rcps change coins 1 9999 False+      (fst <$> resE1)+        `shouldBe` Right+          ( tx'+              ctx+              [(txid' 1, 3), (txid' 1, 2), (txid' 1, 1), (txid' 1, 0)]+              ((rcps !! 1) : (change, 10000) : [head rcps])+          )+      (fst <$> resE2)+        `shouldBe` Right+          ( tx'+              ctx+              [(txid' 1, 3), (txid' 1, 2), (txid' 1, 1), (txid' 1, 0)]+              ((rcps !! 1) : [head rcps])+          )+      (fst <$> resE3)+        `shouldBe` Right+          ( tx'+              ctx+              [(txid' 1, 3), (txid' 1, 2), (txid' 1, 1), (txid' 1, 0)]+              ((rcps !! 1) : [head rcps])+          )+    it "will fail if sending dust" $ do+      let coins =+            [ coin' ctx (txid' 1, 0) Nothing (addr' 0) 100000000,+              coin' ctx (txid' 1, 1) Nothing (addr' 1) 200000000,+              coin' ctx (txid' 1, 2) Nothing (addr' 1) 300000000,+              coin' ctx (txid' 1, 3) Nothing (addr' 2) 400000000+            ]+          change = iAddr' 0+          rcps = [(oAddr' 0, 500000000), (oAddr' 1, 10000)]+          resE = buildWalletTx btc ctx gen rcps change coins 1 10000 False+      resE `shouldBe` Left "Recipient output is smaller than the dust value"+    it "can make the recipient pay for the fees" $ do+      let coins =+            [ coin' ctx (txid' 1, 0) Nothing (addr' 0) 100000000,+              coin' ctx (txid' 1, 1) Nothing (addr' 1) 200000000,+              coin' ctx (txid' 1, 2) Nothing (addr' 1) 300000000,+              coin' ctx (txid' 1, 3) Nothing (addr' 2) 400000000+            ]+          change = iAddr' 0+          rcps = [(oAddr' 0, 200000000), (oAddr' 1, 200000000)]+          resE = buildWalletTx btc ctx gen rcps change coins 314 10000 True+      (fst <$> resE)+        `shouldBe` Right+          ( tx'+              ctx+              [(txid' 1, 2), (txid' 1, 1), (txid' 1, 0)]+              ( (oAddr' 1, 199912708)+                  : (change, 200000000)+                  : [(oAddr' 0, 199912708)]+              )+          )+    it "fails when recipients cannot pay" $+      do+        let coins =+              [ coin' ctx (txid' 1, 0) Nothing (addr' 0) 100000000,+                coin' ctx (txid' 1, 1) Nothing (addr' 1) 200000000,+                coin' ctx (txid' 1, 2) Nothing (addr' 1) 300000000,+                coin' ctx (txid' 1, 3) Nothing (addr' 2) 400000000+              ]+            change = iAddr' 0+            rcps1 = [(oAddr' 0, 400000000), (oAddr' 1, 87291)] -- fee is 2*87292+            rcps2 = [(oAddr' 0, 400000000), (oAddr' 1, 87292)]+            rcps3 = [(oAddr' 0, 400000000), (oAddr' 1, 97293)]+            resE1 = buildWalletTx btc ctx gen rcps1 change coins 314 10000 True+            resE2 = buildWalletTx btc ctx gen rcps2 change coins 314 10000 True+            resE3 = buildWalletTx btc ctx gen rcps3 change coins 314 10000 True+        resE1 `shouldBe` Left "Recipients can't pay for the fee"+        resE2 `shouldBe` Left "Recipient output is smaller than the dust value"+        (fst <$> resE3)+          `shouldBe` Right+            ( tx'+                ctx+                [(txid' 1, 2), (txid' 1, 1), (txid' 1, 0)]+                ((oAddr' 1, 10001) : (change, 199902707) : [(oAddr' 0, 399912708)])+            )++signWalletTxSpec :: Ctx -> Spec+signWalletTxSpec ctx =+  describe "Transaction signer" $ do+    it "can derive private signing keys" $ do+      let xPrvE = signingKey btc ctx mnemPass 0+          xPubE = deriveXPubKey ctx <$> xPrvE+      xPrvE `shouldBe` Right (fst $ keys ctx)+      xPubE `shouldBe` Right (snd $ keys ctx)+    it "can sign a simple transaction" $ do+      let fundTx = tx' ctx [(txid' 1, 0)] [(addr' 0, 100000000)]+          newTx =+            tx'+              ctx+              [(txHash fundTx, 0)]+              [(oAddr' 0, 50000000), (iAddr' 0, 40000000)]+          dat =+            TxSignData+              newTx+              [fundTx]+              [extDeriv :/ 0]+              [intDeriv :/ 0]+              False+          xPrv = fst $ keys ctx+      let resE = signWalletTx btc ctx dat xPrv+          (resDat, resTxInfo) = fromRight (error "fromRight") resE+          signedTx = txSignDataTx resDat+      txSignDataSigned resDat `shouldBe` True+      resTxInfo+        `shouldBe` TxInfo+          { txInfoHash =+              Just "e66b790c73d4e72fe13a07e247e4439bdea210cac2f947040e901f1e0ce59ac2",+            txInfoType = TxDebit,+            txInfoAmount = -60000000,+            txInfoMyOutputs =+              Map.fromList [(iAddr' 0, MyOutputs 40000000 (intDeriv :/ 0) "")],+            txInfoOtherOutputs = Map.fromList [(oAddr' 0, 50000000)],+            txInfoNonStdOutputs = [],+            txInfoMyInputs =+              Map.fromList+                [ ( addr' 0,+                    MyInputs+                      100000000+                      (extDeriv :/ 0)+                      ""+                      [ SigInput+                          (PayPKHash (addr' 0).hash160)+                          100000000+                          (OutPoint (txHash fundTx) 0)+                          sigHashAll+                          Nothing+                      ]+                  )+                ],+            txInfoOtherInputs = Map.empty,+            txInfoNonStdInputs = [],+            txInfoSize = fromIntegral $ BS.length $ S.encode signedTx,+            txInfoFee = 10000000,+            txInfoFeeByte = 44247,+            txInfoBlockRef = Store.MemRef 0,+            txInfoConfirmations = 0,+            txInfoPending =+              Just $+                TxInfoPending+                  "9b41b3cb7b10bfd1dc3f7f1885ce3623789b1b946d206fd59fb432ad4fdb6d70"+                  True+                  False+          }+    it "can set the correct TxInternal transaction types" $ do+      let fundTx =+            tx' ctx [(txid' 1, 0)] [(addr' 0, 100000000), (addr' 1, 200000000)]+          newTx =+            tx'+              ctx+              [(txHash fundTx, 0), (txHash fundTx, 1)]+              [(iAddr' 0, 50000000), (addr' 2, 200000000)]+          dat =+            TxSignData+              newTx+              [fundTx]+              [extDeriv :/ 0, extDeriv :/ 1]+              [intDeriv :/ 0, extDeriv :/ 2]+              False+          xPrv = fst $ keys ctx+      let resE = signWalletTx btc ctx dat xPrv+      (txInfoType . snd <$> resE) `shouldBe` Right TxInternal+    it "fails when an input is not signed" $ do+      let fundTx =+            tx' ctx [(txid' 1, 0)] [(addr' 0, 100000000), (addr' 1, 100000000)]+          newTx =+            tx'+              ctx+              [(txHash fundTx, 0), (txHash fundTx, 1)]+              [(oAddr' 0, 50000000), (iAddr' 0, 40000000)]+          dat =+            TxSignData+              newTx+              [fundTx]+              [extDeriv :/ 0] -- We omit derivation 1+              [intDeriv :/ 0]+              False+          xPrv = fst $ keys ctx+      let resE = signWalletTx btc ctx dat xPrv+      resE `shouldBe` Left "The transaction could not be signed"+    it "fails when referenced input transactions are missing" $ do+      let fundTx = tx' ctx [(txid' 1, 0)] [(addr' 0, 100000000)]+          newTx =+            tx'+              ctx+              [(txHash fundTx, 0), (txHash fundTx, 1)]+              [(oAddr' 0, 50000000), (iAddr' 0, 40000000)]+          dat =+            TxSignData+              newTx+              [fundTx]+              [extDeriv :/ 0, extDeriv :/ 1] -- 1 is missing in fundTx+              [intDeriv :/ 0]+              False+          xPrv = fst $ keys ctx+      let resE = signWalletTx btc ctx dat xPrv+      resE `shouldBe` Left "Referenced input transactions are missing"+    it "fails when private key derivations don't match the Tx inputs" $+      do+        let fundTx =+              tx' ctx [(txid' 1, 0)] [(addr' 0, 100000000), (addr' 1, 100000000)]+            newTx =+              tx'+                ctx+                [(txHash fundTx, 0), (txHash fundTx, 1)]+                [(oAddr' 0, 50000000), (iAddr' 0, 40000000)]+            dat =+              TxSignData+                newTx+                [fundTx]+                [extDeriv :/ 1, extDeriv :/ 2] -- 1 and 2 instead of 0 and 1+                [intDeriv :/ 0]+                False+            xPrv = fst $ keys ctx+        let resE = signWalletTx btc ctx dat xPrv+        resE `shouldBe` Left "Input derivations don't match the transaction inputs"+    it "fails when output derivations don't match the Tx outputs" $+      do+        let fundTx = tx' ctx [(txid' 1, 0)] [(addr' 0, 100000000)]+            newTx =+              tx'+                ctx+                [(txHash fundTx, 0)]+                [(oAddr' 0, 50000000), (iAddr' 0, 40000000)]+            dat =+              TxSignData+                newTx+                [fundTx]+                [extDeriv :/ 0]+                [intDeriv :/ 1] -- 1 instead of 0+                False+            xPrv = fst $ keys ctx+        let resE = signWalletTx btc ctx dat xPrv+        resE `shouldBe` Left "Output derivations don't match the transaction outputs"++-- Test Helpers --++tx' :: Ctx -> [(TxHash, Word32)] -> [(Address, Natural)] -> Tx+tx' ctx xs ys = Tx 1 txi txo [] 0+  where+    txi =+      fmap+        (\(h, p) -> TxIn (OutPoint h p) BS.empty maxBound)+        xs+    f = marshal ctx . PayPKHash . (.hash160)+    txo = fmap (\(a, v) -> TxOut v $ f a) (Control.Arrow.second fromIntegral <$> ys)++txid' :: Word8 -> TxHash+txid' w =+  fromRight (error "Could not decode txhash") $+    S.decode $+      w `BS.cons` BS.replicate 31 0x00++bid' :: Word8 -> BlockHash+bid' w =+  fromRight (error "Could not decode block hash") $+    S.decode $+      w `BS.cons` BS.replicate 31 0x00++coin' ::+  Ctx ->+  (TxHash, Word32) ->+  Maybe Natural ->+  Address ->+  Natural ->+  Store.Unspent+coin' ctx (h, p) hM a v =+  Store.Unspent+    { Store.block =+        maybe (Store.MemRef 0) ((`Store.BlockRef` 0) . fromIntegral) hM,+      Store.outpoint = OutPoint h p,+      Store.value = fromIntegral v,+      Store.script = marshal ctx $ PayPKHash $ (.hash160) a,+      Store.address = Just a+    }++addr' :: Int -> Address+addr' i = extAddrs !! i++iAddr' :: Int -> Address+iAddr' i = intAddrs !! i++oAddr' :: Int -> Address+oAddr' i = othAddrs !! i++-- Test Constants++-- Use a predictable seed for tests+gen :: StdGen+gen = mkStdGen 0++mnemPass :: MnemonicPass+mnemPass =+  MnemonicPass+    "snow senior nerve virus fabric now \+    \fringe clip marble interest analyst can"+    "correct horse battery staple"++mnemPass2 :: MnemonicPass+mnemPass2 =+  MnemonicPass+    "boring auction demand filter frog accuse \+    \company exchange rely slogan trim typical"+    "correct horse battery staple"++walletFPText :: Text+walletFPText = "892eb8e4"++walletFPText2 :: Text+walletFPText2 = "807a5cfb"++walletFP :: Fingerprint+walletFP = forceRight $ textToFingerprint walletFPText++walletFP2 :: Fingerprint+walletFP2 = forceRight $ textToFingerprint walletFPText2++-- Keys for account 0+keys :: Ctx -> (XPrvKey, XPubKey)+keys ctx =+  ( fromJust $+      xPrvImport+        btc+        (fst keysT),+    fromJust $+      xPubImport+        btc+        ctx+        (snd keysT)+  )++-- Account /44'/0'/0' mnemonic 1+keysT :: (Text, Text)+keysT =+  ( "xprv9yHxeaLAZvxXb9VtJNesqk8avfN8misGAW9DUW9eacZJNqsfZxqKLmK5jfmvFideQqGesviJeagzSQYCuQySjgvt7TdfowKja5aJqbgyuNh",+    "xpub6CHK45s4QJWpodaMQQBtCt5KUhCdBBb7Xj4pGtZG8x6HFeCp7W9ZtZdZaxA34YtFAhuebiKqLqHLYoB8HDadGutW8kEH4HeMdeS1KJz8Uah"+  )++-- Account /44'/0'/0' mnemonic 2+keysT2 :: (Text, Text)+keysT2 =+  ( "xprv9yXnZpEVdonEtT3strknsAgso5qq1cwRooo6susmzVmB5E2vvNw1KKRBgvwvNLxXdBHnkEN5R5uXi2QDs3tpkoBbL61NE6bnSbcrvH6keGa",+    "xpub6CX8yKmPUBLY6w8LztHoEJdcM7gKR5fHB2ihgJHPYqJ9x2N5TvFFs7jfYCX6So9oYyu6eLDTG5dbQWPncv1PYJtXLJ4cwymhoCpeTEmnZFZ"+  )++extAddrs :: [Address]+extAddrs = fromMaybe (error "extAddrs no parse") . textToAddr btc <$> extAddrsT++extAddrsT :: [Text]+extAddrsT =+  [ "1KEn7jEXa7KCLeZy59dka5qRJBLnPMmrLj",+    "1AVj9WSYayTwUd8rS1mTTo4A6CPsS83VTg",+    "1Dg6Kg7kQuyiZz41HRWXKUWKRu6ZyEf1Nr",+    "1yQZuJjA6w7hXpc3C2LRiCv22rKCas7F1",+    "1cWcYiGK7NwjPBJuKRqZxV4aymUnPu1mx",+    "1MZuimSXigp8oqxkVUvZofqHNtVjdcdAqc",+    "1JReTkpFnsrMqhSEJwUNZXPAyeTo2HQfnE",+    "1Hx9xWAHhcjea5uJnyADktCfcLbuBnRnwA",+    "1HXJhfiD7JFCGMFZnhKRsZxoPF7xDTqWXP",+    "1MZpAt1FofY69B6fzooFxZqe6SdrVrC3Yw"+  ]++intAddrs :: [Address]+intAddrs = fromMaybe (error "intAddrs no parse") . textToAddr btc <$> intAddrsT++intAddrsT :: [Text]+intAddrsT =+  [ "17KiDLpE3r92gWR8kFGkYDtgHqEVJrznvn",+    "1NqNFsuS7K3dfF8RnAVr9YYCMvJuF9GCn6",+    "1MZNPWwFwy2CqVgWBq6unPWBWrZTQ7WTnr",+    "19XbPiR98wmoJQZ42K8pVMzdCwSXZBh7iz",+    "1Gkn7EsphiaYuv6XXvG4Kyg3LSfqFMeXHX",+    "14VkCGcLkNqUwRMVjpLEyodAhXvzUWLqPM",+    "1PkyVUxPMGTLzUWNFNraMagACA1x3eD4CF",+    "1M2mmDhWTjEuqPfUdaQH6XPsr5i29gx581",+    "184JdZjasQUmNo2AimkbKAW2sxXMF9BAvK",+    "13b1QVnWFRwCrjvhthj4JabpnJ4nyxbBqm"+  ]++othAddrs :: [Address]+othAddrs =+  fromMaybe (error "othAddrs no parse") . textToAddr btc+    <$> [ "1JCq8Aa9d9rg4T4XV93RV3DMxd5u7GkSSU",+          "1PxH6Yutj49mRAabGvcTxnLkFZCuXDXvRJ",+          "191J7K3FaXXyM7C9ceSMRsJNF6aWCvvf1Q",+          "1FVnYNLRdR5vQkynApupUez6ZfcDqsLHdj",+          "1PmNJHnbk7Kct5FMqbEVRxqqR2mXVQKK5P",+          "18CaQNcVwzUkE9KvwmMd6a5UWNgqJFEAh1",+          "1M2Cv69B7LRud8su2wdd7HV2i6MrXqzdKP",+          "19xYPmoJ2XV1vJnSkzsrXUJXCgKvPE3ri4",+          "1N2JAKWVFAoKFEUci3tY3kvrGFY6poRgvm",+          "15EANoYyJoo1J51ERdQzNwZCyhEtPfcP8g"+        ]
+ test/Haskoin/Wallet/TestUtils.hs view
@@ -0,0 +1,315 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Haskoin.Wallet.TestUtils where++import Control.Monad+import Control.Monad.Except (ExceptT)+import Control.Monad.Trans (liftIO)+import Control.Monad.Trans.Except (runExceptT)+import Data.Either+import qualified Data.Map.Strict as Map+import Data.Maybe+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Word+import Database.Persist.Sql (runMigrationQuiet)+import Database.Persist.Sqlite (runSqlite)+import Haskoin+import qualified Haskoin.Store.Data as Store+import Haskoin.Util.Arbitrary+import Haskoin.Wallet.Backup+import Haskoin.Wallet.Commands+import Haskoin.Wallet.Config+import Haskoin.Wallet.Database+import Haskoin.Wallet.FileIO+import Haskoin.Wallet.TxInfo+import Numeric.Natural+import Test.HUnit+import Test.Hspec+import Test.QuickCheck++genNatural :: Test.QuickCheck.Gen Natural+genNatural = arbitrarySizedNatural++forceRight :: Either a b -> b+forceRight = fromRight (error "fromRight")++runDBMemory :: DB IO a -> Assertion+runDBMemory action = do+  runSqlite ":memory:" $ do+    _ <- runMigrationQuiet migrateAll+    void action++runDBMemoryE :: (Show a) => ExceptT String (DB IO) a -> Assertion+runDBMemoryE action = do+  runSqlite ":memory:" $ do+    _ <- runMigrationQuiet migrateAll+    resE <- runExceptT action+    liftIO $ resE `shouldSatisfy` isRight++arbitraryText :: Gen Text+arbitraryText = cs <$> (arbitrary :: Gen String)++arbitraryPositive :: (Arbitrary a, Integral a) => Gen a+arbitraryPositive = abs <$> arbitrary++arbitraryNatural :: Gen Natural+arbitraryNatural = fromIntegral <$> (arbitraryPositive :: Gen Word64)++arbitraryBlockRef :: Gen Store.BlockRef+arbitraryBlockRef =+  oneof [a, b]+  where+    a = Store.BlockRef <$> arbitraryPositive <*> arbitraryPositive+    b = Store.MemRef <$> arbitrary++arbitraryDBAccount :: Network -> Ctx -> Gen DBAccount+arbitraryDBAccount net ctx =+  DBAccount+    <$> arbitraryText+    <*> (DBWalletKey . fingerprintToText <$> arbitraryFingerprint)+    <*> arbitraryPositive+    <*> (cs . (.name) <$> arbitraryNetwork)+    <*> (cs . pathToStr <$> arbitraryDerivPath)+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> (xPubExport net ctx <$> arbitraryXPubKey ctx)+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryUTCTime++arbitraryDBAddress :: Network -> Gen DBAddress+arbitraryDBAddress net =+  DBAddress+    <$> arbitraryPositive+    <*> (DBWalletKey . fingerprintToText <$> arbitraryFingerprint)+    <*> (cs . pathToStr <$> arbitraryDerivPath)+    <*> (cs . pathToStr <$> arbitraryDerivPath)+    <*> (fromJust . addrToText net <$> arbitraryAddress)+    <*> arbitraryText+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitrary+    <*> arbitrary+    <*> arbitraryUTCTime++arbitraryAddressBalance :: Gen AddressBalance+arbitraryAddressBalance =+  AddressBalance+    <$> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryPositive+    <*> arbitraryPositive++arbitraryJsonCoin :: Gen JsonCoin+arbitraryJsonCoin =+  JsonCoin+    <$> arbitraryOutPoint+    <*> arbitraryAddress+    <*> arbitraryPositive+    <*> arbitraryBlockRef+    <*> arbitraryNatural+    <*> arbitrary++arbitraryPubKeyDoc :: Ctx -> Gen PubKeyDoc+arbitraryPubKeyDoc ctx =+  PubKeyDoc+    <$> arbitraryXPubKey ctx+    <*> arbitraryNetwork+    <*> arbitraryText+    <*> arbitraryFingerprint++arbitraryTxSignData :: Network -> Ctx -> Gen TxSignData+arbitraryTxSignData net ctx =+  TxSignData+    <$> arbitraryTx net ctx+    <*> resize 10 (listOf (arbitraryTx net ctx))+    <*> resize 10 (listOf arbitrarySoftPath)+    <*> resize 10 (listOf arbitrarySoftPath)+    <*> arbitrary++arbitraryMyOutputs :: Gen MyOutputs+arbitraryMyOutputs =+  MyOutputs+    <$> arbitraryNatural+    <*> arbitrarySoftPath+    <*> arbitraryText++arbitraryMyInputs :: Network -> Ctx -> Gen MyInputs+arbitraryMyInputs net ctx =+  MyInputs+    <$> arbitraryNatural+    <*> arbitrarySoftPath+    <*> arbitraryText+    <*> resize 10 (listOf $ fst <$> arbitrarySigInput net ctx)++arbitraryOtherInputs :: Network -> Ctx -> Gen OtherInputs+arbitraryOtherInputs net ctx =+  OtherInputs+    <$> arbitraryNatural+    <*> resize 10 (listOf $ fst <$> arbitrarySigInput net ctx)++arbitraryTxType :: Gen TxType+arbitraryTxType = oneof [pure TxDebit, pure TxInternal, pure TxCredit]++arbitraryStoreOutput :: Gen Store.StoreOutput+arbitraryStoreOutput =+  Store.StoreOutput+    <$> arbitrary+    <*> arbitraryBS1+    <*> arbitraryMaybe arbitrarySpender+    <*> arbitraryMaybe arbitraryAddress++arbitrarySpender :: Gen Store.Spender+arbitrarySpender = Store.Spender <$> arbitraryTxHash <*> arbitrary++arbitraryStoreInput :: Gen Store.StoreInput+arbitraryStoreInput =+  oneof+    [ Store.StoreCoinbase+        <$> arbitraryOutPoint+        <*> arbitrary+        <*> arbitraryBS1+        <*> listOf arbitraryBS1,+      Store.StoreInput+        <$> arbitraryOutPoint+        <*> arbitrary+        <*> arbitraryBS1+        <*> arbitraryBS1+        <*> arbitrary+        <*> listOf arbitraryBS1+        <*> arbitraryMaybe arbitraryAddress+    ]++arbitraryTxInfo :: Network -> Ctx -> Gen TxInfo+arbitraryTxInfo net ctx =+  TxInfo+    <$> arbitraryMaybe arbitraryTxHash+    <*> arbitraryTxType+    <*> arbitrary+    <*> ( Map.fromList+            <$> resize+              10+              (listOf $ (,) <$> arbitraryAddress <*> arbitraryMyOutputs)+        )+    <*> ( Map.fromList+            <$> resize+              10+              (listOf $ (,) <$> arbitraryAddress <*> arbitraryNatural)+        )+    <*> resize 10 (listOf arbitraryStoreOutput)+    <*> ( Map.fromList+            <$> resize+              10+              (listOf $ (,) <$> arbitraryAddress <*> arbitraryMyInputs net ctx)+        )+    <*> ( Map.fromList+            <$> resize+              10+              (listOf $ (,) <$> arbitraryAddress <*> arbitraryOtherInputs net ctx)+        )+    <*> resize 10 (listOf arbitraryStoreInput)+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> arbitraryBlockRef+    <*> arbitraryNatural+    <*> arbitraryMaybe arbitraryTxInfoPending++arbitraryTxInfoPending :: Gen TxInfoPending+arbitraryTxInfoPending =+  TxInfoPending+    <$> arbitraryTxHash+    <*> arbitrary+    <*> arbitrary++arbitraryAccountBackup :: Ctx -> Gen AccountBackup+arbitraryAccountBackup ctx =+  AccountBackup+    <$> arbitraryText+    <*> arbitraryFingerprint+    <*> arbitraryXPubKey ctx+    <*> arbitraryNetwork+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> ( Map.fromList+            <$> resize 20 (listOf $ (,) <$> arbitraryAddress <*> arbitraryText)+        )+    <*> resize 20 (listOf arbitraryAddress)+    <*> arbitraryUTCTime++arbitrarySyncRes :: Network -> Ctx -> Gen SyncRes+arbitrarySyncRes net ctx =+  SyncRes+    <$> arbitraryDBAccount net ctx+    <*> arbitraryBlockHash+    <*> arbitrary+    <*> arbitraryNatural+    <*> arbitraryNatural++arbitraryResponse :: Network -> Ctx -> Gen Response+arbitraryResponse net ctx =+  oneof+    [ ResponseError <$> arbitraryText,+      ResponseMnemonic+        <$> arbitraryText+        <*> resize 12 (listOf arbitraryText)+        <*> resize 12 (listOf $ resize 12 $ listOf arbitraryText),+      ResponseAccount <$> arbitraryDBAccount net ctx,+      ResponseAccResult+        <$> arbitraryDBAccount net ctx+        <*> arbitrary+        <*> arbitraryText,+      ResponseFile <$> (cs <$> arbitraryText),+      ResponseAccounts <$> resize 20 (listOf $ arbitraryDBAccount net ctx),+      ResponseAddress <$> arbitraryDBAccount net ctx <*> arbitraryDBAddress net,+      ResponseAddresses+        <$> arbitraryDBAccount net ctx+        <*> resize 20 (listOf $ arbitraryDBAddress net),+      ResponseTxs+        <$> arbitraryDBAccount net ctx+        <*> resize 20 (listOf $ arbitraryTxInfo net ctx),+      ResponseTx <$> arbitraryDBAccount net ctx <*> arbitraryTxInfo net ctx,+      ResponseDeleteTx+        <$> arbitraryTxHash+        <*> arbitraryNatural+        <*> arbitraryNatural,+      ResponseCoins+        <$> arbitraryDBAccount net ctx+        <*> resize 20 (listOf arbitraryJsonCoin),+      ResponseSync+        <$> (resize 10 $ listOf $ arbitrarySyncRes net ctx),+      ResponseRestore+        <$> resize+          20+          ( listOf $+              (,,)+                <$> arbitraryDBAccount net ctx+                <*> arbitraryNatural+                <*> arbitraryNatural+          ),+      ResponseVersion+        <$> arbitraryText+        <*> arbitraryText,+      ResponseRollDice <$> resize 20 (listOf arbitraryNatural) <*> arbitraryText+    ]++arbitraryConfig :: Gen Config+arbitraryConfig =+  Config+    <$> arbitraryText+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> arbitraryNatural+    <*> arbitrary
+ test/Haskoin/Wallet/VersionSpec.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-ambiguous-fields -fno-warn-orphans #-}++module Haskoin.Wallet.VersionSpec where++import Haskoin.Wallet.TestUtils+import Haskoin.Wallet.Migration.SemVersion+import Test.Hspec+import Test.QuickCheck+import Test.Hspec.QuickCheck (prop)++instance Arbitrary SemVersion where+  arbitrary =+    oneof+      [ VerMajor <$> arbitraryNatural+      , VerMinor <$> arbitraryNatural <*> arbitraryNatural+      , VerPatch <$> arbitraryNatural <*> arbitraryNatural <*> arbitraryNatural+      ]++spec :: Spec+spec =+  describe "Semantic Version" $ do+    prop "parseSemVersion . verString identity" $+      forAll (arbitrary :: Gen SemVersion) $ \v ->+        parseSemVersion (verString v) `shouldBe` v+    it "passes equality tests" $ do+      parseSemVersion "0" == parseSemVersion "0" `shouldBe` True+      parseSemVersion "0" == parseSemVersion "0.9" `shouldBe` True+      parseSemVersion "0" == parseSemVersion "0.9.0" `shouldBe` True+      parseSemVersion "0.9" == parseSemVersion "0" `shouldBe` True+      parseSemVersion "0.9.0" == parseSemVersion "0" `shouldBe` True+      parseSemVersion "0.9" == parseSemVersion "0.9" `shouldBe` True+      parseSemVersion "0.9" == parseSemVersion "0.9.1" `shouldBe` True+      parseSemVersion "0.9.1" == parseSemVersion "0.9" `shouldBe` True+      parseSemVersion "0.9.1" == parseSemVersion "0.9.1" `shouldBe` True+      parseSemVersion "0" == parseSemVersion "1" `shouldBe` False+      parseSemVersion "0.9" == parseSemVersion "1" `shouldBe` False+      parseSemVersion "0.9.1" == parseSemVersion "1" `shouldBe` False+      parseSemVersion "1" == parseSemVersion "0.9" `shouldBe` False+      parseSemVersion "1" == parseSemVersion "0.9.1" `shouldBe` False+      parseSemVersion "0.9" == parseSemVersion "0.8" `shouldBe` False+      parseSemVersion "0.9" == parseSemVersion "0.8.1" `shouldBe` False+      parseSemVersion "0.8.1" == parseSemVersion "0.9" `shouldBe` False+      parseSemVersion "0.8.1" == parseSemVersion "0.8.2" `shouldBe` False+    it "passes toMajor and toMinor tests" $ do+      toMajor (parseSemVersion "0") `shouldBe` VerMajor 0+      toMajor (parseSemVersion "0.8") `shouldBe` VerMajor 0+      toMajor (parseSemVersion "0.8.1") `shouldBe` VerMajor 0+      toMinor (parseSemVersion "0.8") `shouldBe` VerMinor 0 8+      toMinor (parseSemVersion "0.8.1") `shouldBe` VerMinor 0 8+
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
− tests/Main.hs
@@ -1,16 +0,0 @@-module Main where--import Test.Framework (defaultMain)--import qualified Network.Haskoin.Wallet.Units (tests)-import qualified Network.Haskoin.Wallet.Tests (tests)--import Network.Haskoin.Constants--main :: IO ()-main | networkName == "prodnet" = defaultMain-        (  Network.Haskoin.Wallet.Tests.tests-        ++ Network.Haskoin.Wallet.Units.tests-        )-     | otherwise = error "Tests are only available on prodnet"-
− tests/Network/Haskoin/Wallet/Arbitrary.hs
@@ -1,37 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-module Network.Haskoin.Wallet.Arbitrary where--import Test.QuickCheck (Arbitrary, arbitrary, oneof)--import Network.Haskoin.Test-import Network.Haskoin.Wallet--instance Arbitrary AccountType where-    arbitrary = oneof-        [ return AccountRegular-        , do-            ArbitraryMSParam m n <- arbitrary-            return $ AccountMultisig m n-        ]--instance Arbitrary NodeAction where-    arbitrary = oneof [ NodeActionRescan <$> arbitrary-                      , return NodeActionStatus-                      ]--instance Arbitrary TxAction where-    arbitrary = oneof-        [ do-            as' <- arbitrary-            let as = map (\(ArbitraryAddress a, x) -> (a, x)) as'-            fee <- arbitrary-            rcptFee <- arbitrary-            minConf <- arbitrary-            sign <- arbitrary-            return $ CreateTx as fee rcptFee minConf sign-        , do-            ArbitraryTx tx <- arbitrary-            return (ImportTx tx)-        , SignTx <$> (arbitrary >>= \(ArbitraryTxHash h) -> return h)-        ]-
− tests/Network/Haskoin/Wallet/Tests.hs
@@ -1,24 +0,0 @@-module Network.Haskoin.Wallet.Tests (tests) where--import Test.Framework (Test, testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)--import Data.Aeson (FromJSON, ToJSON, encode, decode)-import Data.HashMap.Strict (singleton)--import Network.Haskoin.Wallet.Arbitrary ()-import Network.Haskoin.Wallet--tests :: [Test]-tests =-    [ testGroup "Serialize & de-serialize types to JSON"-        [ testProperty "AccountType" (metaID :: AccountType -> Bool)-        , testProperty "TxAction" (metaID :: TxAction -> Bool)-        , testProperty "NodeAction" (metaID :: NodeAction -> Bool)-        ]-    ]--metaID :: (FromJSON a, ToJSON a, Eq a) => a -> Bool-metaID x = (decode . encode) (singleton ("object" :: String) x) ==-    Just (singleton ("object" :: String) x)-
− tests/Network/Haskoin/Wallet/Units.hs
@@ -1,1524 +0,0 @@-{-# LANGUAGE RecordWildCards #-}-module Network.Haskoin.Wallet.Units (tests) where--import           Control.Concurrent.STM-import           Control.Concurrent.STM.TBMChan-import           Control.Exception                (Exception, handleJust)-import           Control.Monad                    (guard)-import           Control.Monad.Logger             (NoLoggingT)-import           Control.Monad.Trans              (liftIO)-import           Control.Monad.Trans.Resource     (ResourceT)-import qualified Data.ByteString                  as BS (ByteString, pack)-import           Data.List                        (sort)-import           Data.Maybe                       (fromJust, isJust)-import           Data.String.Conversions          (cs)-import           Data.Word                        (Word32, Word64)-import           Database.Persist                 (Entity (..), entityVal,-                                                   getBy)-import           Database.Persist.Sqlite          (SqlPersistT,-                                                   runMigrationSilent,-                                                   runSqlite)-import           Network.Haskoin.Block-import           Network.Haskoin.Crypto-import           Network.Haskoin.Node.HeaderTree-import           Network.Haskoin.Script-import           Network.Haskoin.Transaction-import           Network.Haskoin.Wallet.Internals-import           Test.Framework                   (Test, testGroup)-import           Test.Framework.Providers.HUnit   (testCase)-import           Test.HUnit                       (Assertion, assertBool,-                                                   assertEqual, assertFailure)--type App = SqlPersistT (NoLoggingT (ResourceT IO))---- TODO: Add tests for accounts with no private key-tests :: [Test]-tests =-    [ testGroup "Account tests"-        [ testCase "Fail create account with wrong keys" $-            assertException-                (WalletException "Invalid account keys")-                ( newAccount NewAccount-                    { newAccountName = "fail-this"-                    , newAccountType = AccountRegular-                    -- This key does not correspond to the one below-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K33Ezpb81k5upGyhrVcwgqNzHRHnQ2kGBPHkJ3sLPjGwj4LML1kr1bLfguJiY21XrYfVrL1CGurfVoMKSPwRdmzt1LwBtVyR"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }-                )--        , testCase "Creating two accounts with the same data should fail" $-            assertException-                (WalletException "Account already exists") $ do-                    _ <- newAccount NewAccount-                        { newAccountName = "main"-                        , newAccountType = AccountRegular-                        , newAccountMaster = Just-                            "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                        , newAccountDeriv = Nothing-                        , newAccountKeys =-                            ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                        , newAccountMnemonic = Nothing-                        , newAccountReadOnly = False-                        }-                    newAccount NewAccount-                        { newAccountName = "main"-                        , newAccountType = AccountRegular-                        , newAccountMaster = Nothing-                        , newAccountDeriv = Nothing-                        , newAccountKeys =-                            ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                        , newAccountMnemonic = Nothing-                        , newAccountReadOnly = False-                        }-        , testCase "Invalid multisig parameters (0 of 1)" $-            assertException (WalletException "Invalid account type") $-                newAccount NewAccount-                    { newAccountName = "multisig-0-of-1"-                    , newAccountType = AccountMultisig 0 1-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }--        , testCase "Invalid multisig parameters (2 of 1)" $-            assertException (WalletException "Invalid account type") $-                newAccount NewAccount-                    { newAccountName = "multisig-2-of-1"-                    , newAccountType = AccountMultisig 2 1-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }--        , testCase "Invalid multisig parameters (15 of 16)" $-            assertException (WalletException "Invalid account type") $-                newAccount NewAccount-                    { newAccountName = "multisig-15-of-16"-                    , newAccountType = AccountMultisig 15 16-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }--        , testCase "To many multisig keys (3 keys for 1 of 2)" $-            assertException (WalletException "Invalid account keys") $-                newAccount NewAccount-                    { newAccountName = "multisig-1-of-2-with-3"-                    , newAccountType = AccountMultisig 1 2-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        [ "xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"-                        , "xpub661MyMwAqRbcFEPH5Aon6F7edspeu1v6a1Nw5qJgk1aX5XYg1ktBL9Azra2CKaAJ2bHXEXkeKHE3eFaCJktFiA5tSMDQDs6bi83maQtdYby"-                        , "xpub661MyMwAqRbcFtDszBWpawpg4KbNWL9qD4VdRwjd1L5cmcS8nXHWXpg9WL1Xc9Yh7HbQBwWDw37YJfc4AF3YEpvAHEBPBFQPFkUcFHnopw8"-                        ]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }--        , testCase "Calling addAccountKeys with an empty key list should fail" $-            assertException-                (WalletException "Invalid account keys") $ do-                    res <- newAccount NewAccount-                        { newAccountName = "multisig-1-of-2-plus-empty"-                        , newAccountType = AccountMultisig 1 2-                        , newAccountMaster = Just-                            "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                        , newAccountDeriv = Nothing-                        , newAccountKeys =-                            ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                        , newAccountMnemonic = Nothing-                        , newAccountReadOnly = False-                        }-                    addAccountKeys (fst res) []--        , testCase "Calling addAccountKeys on a non-multisig account should fail" $-            assertException-                (WalletException "The account is already complete") $ do-                    res <- newAccount NewAccount-                        { newAccountName = "regular-plus-more"-                        , newAccountType = AccountRegular-                        , newAccountMaster = Just-                            "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                        , newAccountDeriv = Nothing-                        , newAccountKeys =-                            ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                        , newAccountMnemonic = Nothing-                        , newAccountReadOnly = False-                        }-                    addAccountKeys (fst res)-                        ["xpub661MyMwAqRbcFEPH5Aon6F7edspeu1v6a1Nw5qJgk1aX5XYg1ktBL9Azra2CKaAJ2bHXEXkeKHE3eFaCJktFiA5tSMDQDs6bi83maQtdYby"]--        , testCase "Adding keys to a complete multisig account should fail" $-            assertException-                (WalletException "The account is already complete") $ do-                    res <- newAccount NewAccount-                        { newAccountName = "regular-plus-more"-                        , newAccountType = AccountMultisig 1 2-                        , newAccountMaster = Just-                            "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                        , newAccountDeriv = Nothing-                        , newAccountKeys =-                            [ "xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"-                            , "xpub661MyMwAqRbcFEPH5Aon6F7edspeu1v6a1Nw5qJgk1aX5XYg1ktBL9Azra2CKaAJ2bHXEXkeKHE3eFaCJktFiA5tSMDQDs6bi83maQtdYby"-                            ]-                        , newAccountMnemonic = Nothing-                        , newAccountReadOnly = False-                        }-                    addAccountKeys (fst res)-                        ["xpub661MyMwAqRbcFtDszBWpawpg4KbNWL9qD4VdRwjd1L5cmcS8nXHWXpg9WL1Xc9Yh7HbQBwWDw37YJfc4AF3YEpvAHEBPBFQPFkUcFHnopw8"]--        , testCase "Getting a non-existing account should fail" $-            assertException-                (WalletException "Account inexistent does not exist") $-                    getAccount "inexistent"--        ]-    , testGroup "Address tests"-        [ testCase "Decreasing the address gap should fail" $-            assertException (WalletException "The gap of an account can only be increased") $ do-                res <- newAccount NewAccount-                    { newAccountName = "reduce-gap"-                    , newAccountType = AccountRegular-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }-                accE' <- setAccountGap (fst res) 15-                setAccountGap accE' 14--        , testCase "Setting a label on a hidden address key should fail" $-            assertException (WalletException "Invalid address index 10") $ do-                res <- newAccount NewAccount-                    { newAccountName = "label-hidden"-                    , newAccountType = AccountRegular-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }-                setAddrLabel (fst res) 10 AddressExternal "Gym membership"--        , testCase "Setting a label on an invalid address key should fail" $-            assertException (WalletException "Invalid address index 20") $ do-                res <- newAccount NewAccount-                    { newAccountName = "label-invalid"-                    , newAccountType = AccountRegular-                    , newAccountMaster = Just-                        "xprv9s21ZrQH143K4a5123LatJaWPdyMvCG4Phpb79kLUXNF3Y9U537QUeKzUjkrdoZVVse747ZnNNUGryPZXEoMFjkuUKyWpEMcg7jbxYECE2b"-                    , newAccountDeriv = Nothing-                    , newAccountKeys =-                        ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                    , newAccountMnemonic = Nothing-                    , newAccountReadOnly = False-                    }-                setAddrLabel (fst res) 20 AddressExternal "Gym membership"--        , testCase "Requesting an address prvkey on a read-only account should fail" $-            assertException-                (WalletException "Could not get private key") $ do-                    res <- newAccount NewAccount-                        { newAccountName = "label-invalid"-                        , newAccountType = AccountRegular-                        , newAccountMaster = Nothing-                        , newAccountDeriv = Nothing-                        , newAccountKeys =-                            ["xpub661MyMwAqRbcH49U84sbFSXEwforKeyukvkBuY9x2ruDvLUccaRf2SeUL1f6StQke7sCuvott5CjzFqw6aA49g2NSjaARBkQdHE18zjC5hB"]-                        , newAccountMnemonic = Nothing-                        , newAccountReadOnly = False-                        }-                    addressPrvKey (fst res) Nothing 2 AddressExternal-        ]-    , testGroup "Wallet tests"-        [ testCase "Verify address derivations" $ runUnit testDerivations-        , testCase "Verify balances" $ runUnit testBalances-        , testCase "Verify balances in conflict" $ runUnit testConflictBalances-        , testCase "Offline transactions" $ runUnit testOffline-        , testCase "Kill an offline tx by spending its coins" $ runUnit testKillOffline-        , testCase "Offline transaction exceptions" testOfflineExceptions-        , testCase "Multisig test 1" $ runUnit testImportMultisig-        , testCase "Kill Tx" $ runUnit testKillTx-        , testCase "Delete Tx" $ runUnit testDeleteTx-        , testCase "Delete Unsigned Tx" $ runUnit testDeleteUnsignedTx-        , testCase "Notifications" $ runUnit testNotification-        ]-    ]--assertException :: (Exception e, Eq e) => e -> App a -> Assertion-assertException ex action =-    handleJust matchEx (const $ return ()) $ do-        runUnit action-        assertFailure $ "Expecting exception: " ++ show ex-  where-    matchEx = guard . (== ex)--runUnit :: App a -> Assertion-runUnit action = do-    _ <- runSqlite ":memory:" $ do-        _ <- runMigrationSilent migrateWallet-        initWallet 0.0001-        action-    return ()--ms :: Mnemonic-ms = "mass coast dance birth online various renew alert crunch middle absurd health"--tid1 :: TxHash-tid1 = "0000000000000000000000000000000000000000000000000000000000000001"--z :: Hash256-z = "0000000000000000000000000000000000000000000000000000000000000000"--fakeNode :: NodeBlock     -- ^ Parent-         -> [TxHash]      -- ^ Transactions-         -> Word32        -- ^ Chain-         -> Word32        -- ^ Nonce-         -> NodeBlock-fakeNode parent tids chain nonce =-    nodeBlock parent chain header-  where-    header = createBlockHeader-        (blockVersion $ nodeHeader parent)-        (nodeHash parent)-        (if null tids then z else buildMerkleRoot tids)-        (nodeTimestamp parent + 600)-        (blockBits $ nodeHeader parent)-        nonce---- -- Creates fake testing blocks--- fakeNode :: Word32 -> BlockHash -> NodeBlock--- fakeNode i h = BlockHeaderNode---     { blockHeaderNodeHash = headerHash header---     , blockHeaderNodeHeader = BlockHeader 1 z1 z2 0 0 0---     , blockHeaderNodeHeight = i---     , blockHeaderNodeWork = 0---     , blockHeaderNodeMedianTimes = []---     , blockHeaderNodeMinWork = 0---     }---   where---     z1 = "0000000000000000000000000000000000000000000000000000000000000000"---     z2 = "0000000000000000000000000000000000000000000000000000000000000000"--fakeTx :: [(TxHash, Word32)] -> [(BS.ByteString, Word64)] -> Tx-fakeTx xs ys =-    createTx 1 txi txo 0-  where-    txi = map (\(h,p) -> TxIn (OutPoint h p) (BS.pack [1]) maxBound) xs-    f = encodeOutputBS . PayPKHash . fromJust . base58ToAddr-    txo = map (\(a,v) -> TxOut v $ f a ) ys--testDerivations :: App ()-testDerivations = do-    accE <- fst <$> newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }--    unusedAddresses accE AddressExternal (ListRequest 0 0 False)-        >>= liftIO . assertEqual "Generated external addresses do not match"-            [ "1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe"-            , "1NrcKe9UNtxjjgMSLdhBYaEgrQWQFnvJXX"-            , "123XgHRNxkprjec72EpxBFPytPim21u9Kc"-            , "16AuD3mAQMzsUHkMGfkQQapG6jhHmV3Rys"-            , "1AjF1GhsxyXCN5doPGLjSztDcuLbfYMkqw"-            , "1LPUYEjUd1u9dgjM3RqnoYj7Zt4j4dmZYA"-            , "1Kzyb5Fpj2VmMoCNWxLNMYeMSu5ocS7u7e"-            , "131L3UXV6WakXpyXvmqzqNkHwWJzWExR9i"-            , "19FWGTZERHzMePTqKoR8nB9y6w7S5u9yGr"-            , "135dwGc8JG2dmhy79onerHKdqoqibHShRJ"-            ] . map (addrToBase58 . walletAddrAddress) . fst--    unusedAddresses accE AddressInternal (ListRequest 0 0 False)-        >>= liftIO . assertEqual "Generated internal addresses do not match"-            [ "1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm"-            , "1JVocGcqZvQFbfpeQUY92iL9ARLsAP4DjW"-            , "15yaPowqF2g9B3xbdyXaPygV9bfXFjYj2X"-            , "187xp9nQTsCa7QJLHYSMyaM1Mf5u2knJYY"-            , "1QDQwk4d4zVWH3TnUHMCvDvvrTqFSaA3hr"-            , "17ATGqWhkXYLxPdynWrFjnJBvD3EYxqC5A"-            , "1EHgPV5DEs3GwUSaRQD7hd2m5MkR4hdByb"-            , "1EvUYGoC7p7GC8BtYTz4RX2ERxjUsT3zLz"-            , "15857PJZQxUH8Jgomv83mYqJqDsVEUZwz1"-            , "18L5fQjr5yXdqqnK98v2UQi1WHAXfYHr7L"-            ] . map (addrToBase58 . walletAddrAddress) . fst---assertBalance :: AccountId -> Word32 -> Word64 -> App ()-assertBalance ai conf b = do-    bal <- accountBalance ai conf False-    liftIO $ assertEqual ("Balance is not " ++ show b) b bal--assertBalanceOffline :: AccountId -> Word32 -> Word64 -> App ()-assertBalanceOffline ai conf b = do-    bal <- accountBalance ai conf True-    liftIO $ assertEqual ("Balance is not " ++ show b) b bal--assertAddress :: Entity Account-              -> Word32      -- Confirmations-              -> Word32      -- Address Index-              -> AddressType-              -> [(KeyIndex, BalanceInfo)]-              -> App ()-assertAddress acc conf addr addrtype b = do-    b' <- addressBalances acc addr addr addrtype conf False-    liftIO $ assertEqual "Address Balance incorrect" b b'--assertAddressOffline :: Entity Account-                     -> Word32      -- Confirmations-                     -> Word32      -- Address Index-                     -> AddressType-                     -> [(KeyIndex, BalanceInfo)]-                     -> App ()-assertAddressOffline acc conf addr addrtype b = do-    b' <- addressBalances acc addr addr addrtype conf True-    liftIO $ assertEqual "Address Balance incorrect" b b'--assertImportTx :: AccountId -> Int -> TxConfidence -> Tx -> App ()-assertImportTx ai as conf tx = do-    tx' <- testTx <$> importNetTx tx Nothing-    liftIO $ assertEqual "Transaction import failed" ([(ai, conf)], as) tx'--assertImportTxOffline :: AccountId -> Int -> TxConfidence -> Tx -> App ()-assertImportTxOffline ai as conf tx = do-    tx' <- testTx <$> importTx tx Nothing ai-    liftIO $ assertEqual "Transaction import failed" ([(ai, conf)], as) tx'--assertTxConfidence :: AccountId -> TxHash -> TxConfidence -> App ()-assertTxConfidence ai txh conf = do-    txM <- getBy $ UniqueAccTx ai txh-    case txM of-        Just tx -> do-            let conf' = walletTxConfidence $ entityVal tx-            liftIO $ assertEqual "Transaction confidence wrong" conf' conf-        Nothing -> liftIO $ assertFailure "Transaction not found"--testBalances :: App ()-testBalances = do-    accE@(Entity ai _) <- fst <$> newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }-    let fundingTx = fakeTx-            [ (tid1, 0) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000)-            , ("1NrcKe9UNtxjjgMSLdhBYaEgrQWQFnvJXX", 20000000)-            ]-    let tx1 = fakeTx-            [ (txHash fundingTx, 0)-            , (txHash fundingTx, 1)-            ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 30000000) ] -- external-        tx2 = fakeTx-            [ (txHash fundingTx, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 5000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 5000000) -- change-            ]--    assertBalance ai 0 0--    -- Import funding transaction twice. This operation should be idempotent-    assertImportTx ai 2 TxPending fundingTx-    assertImportTx ai 0 TxPending fundingTx--    spendableCoins ai 0 (const . const []) >>=-        liftIO . assertEqual "0-conf spendable coins is not 2" 2 . length-    spendableCoins ai 1 (const . const []) >>=-        liftIO . assertEqual "1-conf spendable coins is not 0" 0 . length--    assertBalance ai 0 30000000-    assertBalance ai 1 0--    assertAddress accE 0 0 AddressExternal [(0, BalanceInfo 10000000 0 1 0)]-    assertAddress accE 0 1 AddressExternal [(1, BalanceInfo 20000000 0 1 0)]-    assertAddress accE 1 0 AddressExternal [(0, BalanceInfo 0 0 0 0)]-    assertAddress accE 1 1 AddressExternal [(1, BalanceInfo 0 0 0 0)]--    assertImportTx ai 0 TxPending tx1--    assertBalance ai 0 0-    assertBalance ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]-    assertAddress accE 1 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddress accE 1 1 AddressExternal-        [(1, BalanceInfo 0 0 0 0)]--    -- We re-import tx1. This operation has to be idempotent with respect to-    -- balances.-    assertImportTx ai 0 TxPending tx1--    assertBalance ai 1 0-    assertBalance ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]-    assertAddress accE 1 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddress accE 1 1 AddressExternal-        [(1, BalanceInfo 0 0 0 0)]--    -- Importing tx2 twice. This operation has to be idempotent.-    assertImportTx ai 1 TxDead tx2-    assertImportTx ai 0 TxDead tx2--    assertBalance ai 0 0-    assertBalance ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]--    -- Confirm the funding transaction at height 1-    let block1 = fakeNode genesisBlock [txHash fundingTx] 0 1-    importMerkles (BestChain [block1]) [[txHash fundingTx]] Nothing--    assertBalance ai 0 0-    assertBalance ai 1 0-    assertBalance ai 2 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]-    assertAddress accE 1 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 1 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]--    -- Confirm tx1 at height 2-    let block2 = fakeNode block1 [txHash tx1] 0 2-    importMerkles (BestChain [block2]) [[txHash tx1]] Nothing--    assertBalance ai 0 0-    assertBalance ai 1 0-    assertBalance ai 2 0-    assertBalance ai 3 0--    assertAddress accE 2 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 2 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]-    assertAddress accE 3 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddress accE 3 1 AddressExternal-        [(1, BalanceInfo 0 0 0 0)]--    -- Reorg on tx2-    let block2' = fakeNode block1 [] 1 22-        block3' = fakeNode block2 [txHash tx2] 1 33-    importMerkles (ChainReorg block1 [block2] [block2', block3']) [[], [txHash tx2]] Nothing--    getBy (UniqueAccTx ai (txHash tx1))-        >>= liftIO-        . assertEqual "Confidence is not dead" TxDead-        . walletTxConfidence . entityVal . fromJust--    getBy (UniqueAccTx ai (txHash tx2))-        >>= liftIO-        . assertEqual "Confidence is not building" TxBuilding-        . walletTxConfidence . entityVal . fromJust--    assertBalance ai 0 25000000-    assertBalance ai 1 25000000-    assertBalance ai 2 20000000-    assertBalance ai 3 20000000-    assertBalance ai 4 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 1 AddressExternal-        [(1, BalanceInfo 20000000 0 1 0)]--    assertAddress accE 3 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 3 1 AddressExternal-        [(1, BalanceInfo 20000000 0 1 0)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 5000000 0 1 0)]-    assertAddress accE 1 0 AddressInternal-        [(0, BalanceInfo 5000000 0 1 0)]-    assertAddress accE 2 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    -- Reimporting tx2 should be idempotent and return TxBuilding-    assertImportTx ai 0 TxBuilding tx2--    accountBalance ai 0 False >>=-        liftIO . assertEqual "Balance is not 25000000" 25000000-    accountBalance ai 1 False >>=-        liftIO . assertEqual "Balance is not 25000000" 25000000-    accountBalance ai 2 False >>=-        liftIO . assertEqual "Balance is not 20000000" 20000000-    accountBalance ai 3 False >>=-        liftIO . assertEqual "Balance is not 20000000" 20000000-    accountBalance ai 4 False >>=-        liftIO . assertEqual "Balance is not 0" 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 1 AddressExternal-        [(1, BalanceInfo 20000000 0 1 0)]-    assertAddress accE 3 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 3 1 AddressExternal-        [(1, BalanceInfo 20000000 0 1 0)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 5000000 0 1 0)]-    assertAddress accE 1 0 AddressInternal-        [(0, BalanceInfo 5000000 0 1 0)]-    assertAddress accE 2 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    -- Reorg back onto tx1-    let block3 = fakeNode block2 [] 0 3-        block4 = fakeNode block3 [] 0 4-    importMerkles (ChainReorg block1 [block2', block3'] [block2, block3, block4])-        [[txHash tx1], [], []] Nothing--    assertBalance ai 0 0-    assertBalance ai 1 0-    assertBalance ai 2 0-    assertBalance ai 3 0-    assertBalance ai 4 0-    assertBalance ai 5 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]-    assertAddress accE 4 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 4 1 AddressExternal-        [(1, BalanceInfo 20000000 20000000 1 1)]-    assertAddress accE 5 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddress accE 5 1 AddressExternal-        [(1, BalanceInfo 0 0 0 0)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]---- tx1, tx2 and tx3 form a chain, and tx4 is in conflict with tx1-testConflictBalances :: App ()-testConflictBalances = do-    accE@(Entity ai _) <- fst <$> newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }-    let tx1 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000) ]-        tx2 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 4000000) -- change-            ]-        tx3 = fakeTx-            [ (txHash tx2, 1) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external-        tx4 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 20000000) ]--    -- Import first transaction-    assertImportTx ai 1 TxPending tx1--    assertBalance ai 0 10000000-    assertBalance ai 0 10000000-    assertBalance ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]--    -- Import second transaction-    assertImportTx ai 1 TxPending tx2--    assertBalance ai 0 4000000-    assertBalance ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]--    -- Let's confirm these two transactions-    let block1 = fakeNode genesisBlock [txHash tx1] 0 1-        block2 = fakeNode block1 [txHash tx2] 0 2-    importMerkles (BestChain [block1, block2])-        [[txHash tx1], [txHash tx2]] Nothing--    assertBalance ai 0 4000000-    assertBalance ai 1 4000000-    assertBalance ai 2 0-    assertBalance ai 3 0--    assertAddress accE 1 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 1 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]-    assertAddress accE 2 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 2 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    -- Import third transaction-    assertImportTx ai 0 TxPending tx3--    assertBalance ai 0 0-    assertBalance ai 1 0-    assertBalance ai 2 0-    assertBalance ai 3 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]-    assertAddress accE 1 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 1 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]-    assertAddress accE 2 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 2 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    -- Now let's add tx4 which is in conflict with tx1-    assertImportTx ai 0 TxDead tx4--    assertBalance ai 0 0-    assertBalance ai 1 0-    assertBalance ai 2 0-    assertBalance ai 3 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]-    assertAddress accE 1 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 1 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]-    assertAddress accE 2 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 2 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    -- Now we trigger a reorg that validates tx4. tx1, tx2 and tx3 should be dead-    let block1' = fakeNode genesisBlock [] 1 11-        block2' = fakeNode block1' [txHash tx4] 1 22-        block3' = fakeNode block2' [] 1 33-    importMerkles-        (ChainReorg genesisBlock [block1, block2] [block1', block2', block3'])-        [[], [txHash tx4], []] Nothing--    assertTxConfidence ai (txHash tx1) TxDead-    assertTxConfidence ai (txHash tx2) TxDead-    assertTxConfidence ai (txHash tx3) TxDead--    assertBalance ai 0 20000000-    assertBalance ai 1 20000000-    assertBalance ai 2 20000000-    assertBalance ai 3 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 20000000 0 1 0)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddress accE 2 0 AddressExternal-        [(0, BalanceInfo 20000000 0 1 0)]-    assertAddress accE 3 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]--    -- Reorg back to tx1, tx2 and tx3-    let block3 = fakeNode block2 [] 0 3-        block4 = fakeNode block3 [] 0 4-    importMerkles-        (ChainReorg genesisBlock [block1', block2', block3']-         [block1, block2, block3, block4])-        [[txHash tx1], [txHash tx2], [], []] Nothing--    assertTxConfidence ai (txHash tx1) TxBuilding-    assertTxConfidence ai (txHash tx2) TxBuilding--    -- Tx3 remains dead until it is included into a block. Dead transaction are-    -- only revived upon confirmations. They are not revived if they are not-    -- confirmed even if they have no conflicts anymore.-    assertTxConfidence ai (txHash tx3) TxDead-    assertTxConfidence ai (txHash tx4) TxDead--    assertBalance ai 0 4000000-    assertBalance ai 1 4000000-    assertBalance ai 2 4000000-    assertBalance ai 3 4000000-    assertBalance ai 4 0-    assertBalance ai 5 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]-    assertAddress accE 3 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 3 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]-    assertAddress accE 4 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddress accE 4 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddress accE 5 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]--testOffline :: App ()-testOffline = do-    accE@(Entity ai _) <- fst <$> newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }-    let tx1 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000) ]-        tx2 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 4000000) -- change-            ]-        tx3 = fakeTx-            [ (txHash tx2, 1) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external-        tx4 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 20000000) ]--    -- Import first transaction-    assertImportTxOffline ai 1 TxOffline tx1--    assertBalance        ai 0 0-    assertBalanceOffline ai 0 10000000-    assertBalance        ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]--    -- Reimporting a transaction should me idempotent-    assertImportTxOffline ai 0 TxOffline tx1--    assertBalance        ai 0 0-    assertBalanceOffline ai 0 10000000-    assertBalance        ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]--    -- Import tx2-    assertImportTxOffline ai 1 TxOffline tx2--    assertBalanceOffline ai 0 4000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]--    -- Import tx3-    assertImportTxOffline ai 0 TxOffline tx3--    assertBalanceOffline ai 0 0--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]--    -- Import tx4-    assertImportTxOffline ai 0 TxOffline tx4--    assertTxConfidence ai (txHash tx1) TxDead-    assertTxConfidence ai (txHash tx2) TxDead-    assertTxConfidence ai (txHash tx3) TxDead--    assertBalanceOffline ai 0 20000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 20000000 0 1 0)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    -- importTx should be idempotent-    assertImportTxOffline ai 0 TxOffline tx4--    assertBalanceOffline ai 0 20000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 20000000 0 1 0)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--testKillOffline :: App ()-testKillOffline = do-    accE@(Entity ai _) <- fst <$> newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }-    let tx1 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000) ]-        tx2 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 4000000) -- change-            ]-        tx3 = fakeTx-            [ (txHash tx2, 1) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external-        tx4 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 2000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 3000000) -- change-            , ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 5000000) -- more change-            ]--    -- Import tx1 as a network transaction-    assertImportTx ai 1 TxPending tx1--    assertBalance        ai 0 10000000-    assertBalanceOffline ai 0 10000000-    assertBalance        ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]--    -- Import tx2 as offline-    assertImportTxOffline ai 1 TxOffline tx2--    assertBalance        ai 0 10000000-    assertBalanceOffline ai 0 4000000-    assertBalance        ai 1 0-    assertBalanceOffline ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]--    -- Import tx3 as offline-    assertImportTxOffline ai 0 TxOffline tx3--    assertBalance ai 0 10000000-    assertBalanceOffline ai 0 0-    assertBalance ai 1 0-    assertBalanceOffline ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]-    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]--    -- Import tx4 as a network transaction. It should override tx2 and tx3.-    assertImportTx ai 0 TxPending tx4--    assertTxConfidence ai (txHash tx2) TxDead-    assertTxConfidence ai (txHash tx3) TxDead--    assertBalance        ai 0 8000000-    assertBalanceOffline ai 0 8000000-    assertBalance        ai 1 0-    assertBalanceOffline ai 1 0--    assertAddress accE 0 0 AddressExternal-        [(0, BalanceInfo 15000000 10000000 2 1)]-    assertAddress accE 0 0 AddressInternal-        [(0, BalanceInfo 3000000 0 1 0)]-    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 15000000 10000000 2 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 3000000 0 1 0)]--testOfflineExceptions :: Assertion-testOfflineExceptions = do-    let tx1 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000) ]-        tx2 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 4000000) -- change-            ]-        tx3 = fakeTx-            [ (txHash tx2, 1) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external-        tx4 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 20000000) ]--    assertException (WalletException "Could not import offline transaction") $ do-        _ <- newAccount NewAccount-            { newAccountName = "acc1"-            , newAccountType = AccountRegular-            , newAccountDeriv = Just (Deriv :| 0)-            , newAccountMaster = Nothing-            , newAccountMnemonic = Just (cs ms)-            , newAccountKeys = []-            , newAccountReadOnly = False-            }-        Entity ai _ <- getAccount "acc1"-        assertImportTx ai 1 TxPending tx1-        importTx tx4 Nothing ai--    assertException (WalletException "Could not import offline transaction") $ do-        _ <- newAccount NewAccount-            { newAccountName = "acc1"-            , newAccountType = AccountRegular-            , newAccountDeriv = Just (Deriv :| 0)-            , newAccountMaster = Nothing-            , newAccountMnemonic = Just (cs ms)-            , newAccountKeys = []-            , newAccountReadOnly = False-            }-        Entity ai _ <- getAccount "acc1"-        assertImportTx ai 1 TxPending tx4-        assertImportTx ai 0 TxDead tx1-        assertImportTx ai 1 TxDead tx2-        importTx tx3 Nothing ai--    assertException (WalletException "Could not import offline transaction") $ do-        _ <- newAccount NewAccount-            { newAccountName = "acc1"-            , newAccountType = AccountRegular-            , newAccountDeriv = Just (Deriv :| 0)-            , newAccountMaster = Nothing-            , newAccountMnemonic = Just (cs ms)-            , newAccountKeys = []-            , newAccountReadOnly = False-            }-        Entity ai _ <- getAccount "acc1"-        assertImportTx ai 1 TxPending tx1-        importTx tx1 Nothing ai---- This test create a multisig account with the key of testImportMultisig2-testImportMultisig :: App ()-testImportMultisig = do-    accE1@(Entity ai1 _) <- fst <$> newAccount NewAccount-        { newAccountName = "ms1"-        , newAccountType = AccountMultisig 2 2-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = ["xpub68kRFKHWxUt3oS8X5kVogwH5rvuAd4jrLkxVfHeudFC4MfwQ8oYV59F91uFnsLXANRB1MkN4Wa1PwymE4cRsU8PE755HNCb1EoBbSoAKXpW"]-        , newAccountReadOnly = False-        }-    accE2@(Entity ai2 _) <- fst <$> newAccount NewAccount-        { newAccountName = "ms2"-        , newAccountType = AccountMultisig 2 2-        , newAccountDeriv = Just (Deriv :| 1)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = ["xpub68kRFKHWxUt3mfJjcXdLeuDjCHnByqKSBVfMktJRXM6LSNNDR4ae6Nw1Kh621fzyKiBf6ssyZWPPTDUTQp1BhuZQuoVdtb8j2TRzqDLHmY7"]-        , newAccountReadOnly = False-        }-    let fundingTx = createTx-            1-            [ TxIn (OutPoint tid1 0) (BS.pack [1]) maxBound ] -- dummy input-            [ TxOut 10000000 $-              encodeOutputBS $ PayScriptHash $ fromJust $-              base58ToAddr "32RexHZdsMoV8yzL1pQyFhYY6XeUNcWP78"-            ]-            0--    importNetTx fundingTx Nothing-        >>= liftIO-        . assertEqual "Transaction import failed"-            ([(ai1, TxPending), (ai2, TxPending)], 2)-        . testTx--    -- Create a transaction which has 0 signatures in ms1-    tx1 <- fst <$> createWalletTx accE1 Nothing Nothing-        [ ( fromJust $ base58ToAddr "3BYWaQHz6AVXx7wXmCka4846tRfa1ccWvh"-          , 5000000-          )-        ] 10000 0 False True--    liftIO $ assertEqual "Confidence is not offline" TxOffline $-        walletTxConfidence tx1--    spendableCoins ai1 0 (const . const [])-        >>= liftIO-        . assertEqual "Wrong txhash in coins" []-        . map (walletCoinHash . entityVal . inCoinDataCoin)--    txs Nothing ai1 (ListRequest 0 10 False)-        >>= liftIO-        . assertEqual "Wrong txhash in tx list"-            (sort [txHash fundingTx, walletTxHash tx1])-        . sort . map walletTxHash . fst--    assertBalance        ai1 0 10000000-    assertBalance        ai1 1 0-    assertBalanceOffline ai1 0 9990000--    -- Import the empty transaction in ms2-    tx2 <- head . fst <$> importTx (walletTxTx tx1) Nothing ai2--    -- This second import should be idempotent-    _ <- importTx (walletTxTx tx1) Nothing ai2--    liftIO $ assertEqual "Txid do not match"-        (walletTxHash tx1) (walletTxHash tx2)--    liftIO $ assertEqual "Confidence is not offline" TxOffline $-        walletTxConfidence tx2--    spendableCoins ai2 0 (const . const [])-        >>= liftIO-        . assertEqual "Wrong txhash in coins" []-        . map (walletCoinHash . entityVal . inCoinDataCoin)--    txs Nothing ai2 (ListRequest 0 10 False)-        >>= liftIO-        . assertEqual "Wrong txhash in tx list"-            (sort [txHash fundingTx, walletTxHash tx2])-        . sort . map walletTxHash . fst--    assertBalance        ai2 0 10000000-    assertBalance        ai2 1 0-    assertBalanceOffline ai2 0 9990000--    -- Sign the transaction in ms2-    tx3:_ <- fst <$> signAccountTx accE2 Nothing Nothing (walletTxHash tx2)--    liftIO $ assertEqual "Confidence is not pending" TxPending $-        walletTxConfidence tx3--    spendableCoins ai2 0 (const . const [])-        >>= liftIO-        . assertEqual "Wrong txhash in coins"-            [walletTxHash tx3, walletTxHash tx3]-        . map (walletCoinHash . entityVal . inCoinDataCoin)--    txs Nothing ai2 (ListRequest 0 10 False)-        >>= liftIO-        . assertEqual "Wrong txhash in tx list"-            (sort [txHash fundingTx, walletTxHash tx3])-        . sort . map walletTxHash . fst--    assertBalance        ai2 0 9990000-    assertBalance        ai2 1 0-    assertBalanceOffline ai2 0 9990000--    tx4 <- fmap (entityVal . fromJust) $-        getBy $ UniqueAccTx ai1 $ walletTxHash tx3--    liftIO $ assertEqual "Confidence is not pending" TxPending $-        walletTxConfidence tx4--    spendableCoins ai1 0 (const . const [])-        >>= liftIO-        . assertEqual "Wrong txhash in coins"-            [walletTxHash tx3, walletTxHash tx3]-        . map (walletCoinHash . entityVal . inCoinDataCoin)--    txs Nothing ai1 (ListRequest 0 10 False)-        >>= liftIO-        . assertEqual "Wrong txhash in tx list"-            (sort [txHash fundingTx, walletTxHash tx3])-        . sort . map walletTxHash . fst--    assertBalance        ai1 0 9990000-    assertBalance        ai1 1 0-    assertBalanceOffline ai1 0 9990000--    -- Importing the transaction should have no effect as it was globally-    -- imported already in the previous step.-    tx5 <- head . fst <$> importTx (walletTxTx tx3) Nothing ai1--    liftIO $ assertEqual "Confidence is not pending" TxPending $-        walletTxConfidence tx5--    spendableCoins ai1 0 (const . const [])-        >>= liftIO-        . assertEqual "Wrong txhash in coins"-            [walletTxHash tx5, walletTxHash tx5]-        . map (walletCoinHash . entityVal . inCoinDataCoin)--    txs Nothing ai1 (ListRequest 0 10 False)-        >>= liftIO-        . assertEqual "Wrong txhash in tx list"-            (sort [txHash fundingTx, walletTxHash tx5])-        . sort . map walletTxHash . fst--    assertBalance        ai1 0 9990000-    assertBalance        ai1 1 0-    assertBalanceOffline ai1 0 9990000--testDeleteTx :: App ()-testDeleteTx = do-    accE@(Entity ai _) <- fst <$> newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }-    let tx1 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000) ]-        tx2 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 4000000) -- change-            ]-        tx3 = fakeTx-            [ (txHash tx2, 1) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external--    assertImportTx ai 1 TxPending tx1-    assertImportTx ai 1 TxPending tx2-    assertImportTx ai 0 TxPending tx3--    assertBalance ai 0 0--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]--    tx2M' <- getTx $ txHash tx2-    liftIO $ assertBool "Transaction 2 not found" $ isJust tx2M'-    deleteTx $ txHash tx2--    tx1M   <- getTx $ txHash tx1-    tx2M'' <- getTx $ txHash tx2-    tx3M   <- getTx $ txHash tx3-    liftIO $ assertEqual "Transaction 1 removed" (Just tx1) tx1M-    liftIO $ assertEqual "Transaction 2 not removed" Nothing tx2M''-    liftIO $ assertEqual "Transaction 3 not removed" Nothing tx3M--    assertBalance ai 0 10000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--testDeleteUnsignedTx :: App ()-testDeleteUnsignedTx = do-    accE1@(Entity ai1 _) <- fst <$> newAccount NewAccount-        { newAccountName = "ms1"-        , newAccountType = AccountMultisig 2 2-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = ["xpub68kRFKHWxUt3oS8X5kVogwH5rvuAd4jrLkxVfHeudFC4MfwQ8oYV59F91uFnsLXANRB1MkN4Wa1PwymE4cRsU8PE755HNCb1EoBbSoAKXpW"]-        , newAccountReadOnly = False-        }-    Entity ai2 _ <- fst <$> newAccount NewAccount-        { newAccountName = "ms2"-        , newAccountType = AccountMultisig 2 2-        , newAccountDeriv = Just (Deriv :| 1)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = ["xpub68kRFKHWxUt3mfJjcXdLeuDjCHnByqKSBVfMktJRXM6LSNNDR4ae6Nw1Kh621fzyKiBf6ssyZWPPTDUTQp1BhuZQuoVdtb8j2TRzqDLHmY7"]-        , newAccountReadOnly = False-        }-    let fundingTx = createTx-            1-            [ TxIn (OutPoint tid1 0) (BS.pack [1]) maxBound ] -- dummy input-            [ TxOut 10000000 $-              encodeOutputBS $ PayScriptHash $ fromJust $-              base58ToAddr "32RexHZdsMoV8yzL1pQyFhYY6XeUNcWP78"-            ]-            0--    importNetTx fundingTx Nothing-        >>= liftIO-        . assertEqual "Confidence is not pending"-            ([(ai1, TxPending), (ai2, TxPending)], 2)-        . testTx--    -- Create a transaction which has 0 signatures in ms1-    tx1 <- fst <$> createWalletTx accE1 Nothing Nothing-        [ ( fromJust $ base58ToAddr "3BYWaQHz6AVXx7wXmCka4846tRfa1ccWvh"-          , 5000000-          )-        ] 10000 0 False True--    liftIO $ assertEqual "Confidence is not offline" TxOffline $-        walletTxConfidence tx1--    spendableCoins ai1 0 (const . const [])-        >>= liftIO-        . assertEqual "Wrong txhash in coins" []-        . map (walletCoinHash . entityVal . inCoinDataCoin)--    txs Nothing ai1 (ListRequest 0 10 False)-        >>= liftIO-        . assertEqual "Wrong txhash in tx list"-            (sort [txHash fundingTx, walletTxHash tx1])-        . sort . map walletTxHash . fst--    assertBalance        ai1 0 10000000-    assertBalance        ai1 1 0-    assertBalanceOffline ai1 0 9990000--    tx1EM <- getTx $ walletTxHash tx1-    liftIO $ assertBool "Transaction 1 not found" $ isJust tx1EM-    deleteTx $ walletTxHash tx1--    tx1M <- getTx $ walletTxHash tx1-    liftIO $ assertEqual "Transaction not removed" Nothing tx1M--testNotification :: App ()-testNotification = do-    _ <- newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }-    let tx1 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000) ]-        tx2 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 4000000) -- change-            ]-        tx3 = fakeTx-            [ (txHash tx2, 1) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external--    notifChan <- liftIO $ atomically $ newTBMChan 1000--    _ <- importNetTx tx1 (Just notifChan)-    tx1NM <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case tx1NM of-        Just (NotifTx JsonTx{..}) ->-            assertEqual "Notif hash does not match" (txHash tx1) jsonTxHash-        _ -> assertFailure "Transaction notification is not the right type"--    _ <- importNetTx tx2 (Just notifChan)-    tx2NM <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case tx2NM of-        Just (NotifTx JsonTx{..}) ->-            assertEqual "Notif hash does not match" (txHash tx2) jsonTxHash-        _ -> assertFailure "Transaction notification is not the right type"--    _ <- importNetTx tx3 Nothing--    let block1 = fakeNode genesisBlock [txHash tx1] 0 1-        block2 = fakeNode block1 [txHash tx2] 0 2-        best = BestChain [block1, block2]-        txs1 = [[txHash tx1], [txHash tx2]]-    importMerkles best txs1 (Just notifChan)-    b1NM <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case b1NM of-        Just (NotifBlock JsonBlock{..}) ->-            assertEqual "Block hash does not match"-                (nodeHash block1) jsonBlockHash-        _ -> assertFailure "Block notification not the right type"-    tx1NM' <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case tx1NM' of-        Just (NotifTx JsonTx{..}) ->-            assertEqual "Transaction list does not match" (txHash tx1)-                jsonTxHash-        _ -> assertFailure "Transaction notification not the right type"-    b2NM <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case b2NM of-        Just (NotifBlock JsonBlock{..}) ->-            assertEqual "Block hash does not match"-                (nodeHash block2) jsonBlockHash-        _ -> assertFailure "Block notification not the right type"-    tx2NM' <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case tx2NM' of-        Just (NotifTx JsonTx{..}) ->-            assertEqual "Transaction list does not match" (txHash tx2)-                jsonTxHash-        _ -> assertFailure "Transaction notification not the right type"---    let block2' = fakeNode block1 [txHash tx2, txHash tx3] 1 22-        txs2 = [[txHash tx2, txHash tx3]]-        reorg = ChainReorg block1 [block2] [block2']-    importMerkles reorg txs2 (Just notifChan)-    b3NM <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case b3NM of-        Just (NotifBlock JsonBlock{..}) ->-            assertEqual "Block hash does not match"-                (nodeHash block2') jsonBlockHash-        _ -> assertFailure "Block notification not the right type"-    tx3NM2 <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case tx3NM2 of-        Just (NotifTx JsonTx{..}) ->-            assertEqual "Transaction does not match"-                (txHash tx2)-                jsonTxHash-        _ -> assertFailure "Transaction notification not the right type"-    tx3NM3 <- liftIO $ atomically $ readTBMChan notifChan-    liftIO $ case tx3NM3 of-        Just (NotifTx JsonTx{..}) ->-            assertEqual "Transaction does not match"-                (txHash tx3)-                jsonTxHash-        _ -> assertFailure "Transaction notification not the right type"---testKillTx :: App ()-testKillTx = do-    accE@(Entity ai _) <- fst <$> newAccount NewAccount-        { newAccountName = "acc1"-        , newAccountType = AccountRegular-        , newAccountDeriv = Just (Deriv :| 0)-        , newAccountMaster = Nothing-        , newAccountMnemonic = Just (cs ms)-        , newAccountKeys = []-        , newAccountReadOnly = False-        }-    let tx1 = fakeTx-            [ (tid1, 4) ]-            [ ("1DLW4wieCwUPMh6ThVwT2bKqSzkjeb8wUe", 10000000) ]-        tx2 = fakeTx-            [ (txHash tx1, 0) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external-            , ("1PY7pWZ5FddWi747C6k5Y7okNHFUM2BKAm", 4000000) -- change-            ]-        tx3 = fakeTx-            [ (txHash tx2, 1) ]-            [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external--    assertImportTx ai 1 TxPending tx1-    assertImportTx ai 1 TxPending tx2-    assertImportTx ai 0 TxPending tx3--    assertBalanceOffline ai 0 0--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 4000000 1 1)]--    killTxs Nothing [txHash tx2]--    assertBalanceOffline ai 0 10000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    -- Killing a transaction should be idempotent-    killTxs Nothing [txHash tx2]--    assertBalanceOffline ai 0 10000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    killTxs Nothing [txHash tx3]--    assertBalanceOffline ai 0 10000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 0 1 0)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 0 0 0 0)]--    reviveTx Nothing tx2--    assertBalanceOffline ai 0 4000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]--    -- Reviving a transaction should be idempotent-    reviveTx Nothing tx2--    assertBalanceOffline ai 0 4000000--    assertAddressOffline accE 0 0 AddressExternal-        [(0, BalanceInfo 10000000 10000000 1 1)]-    assertAddressOffline accE 0 0 AddressInternal-        [(0, BalanceInfo 4000000 0 1 0)]--testTx :: ([WalletTx], [WalletAddr])-       -> ([(AccountId, TxConfidence)], Int)-testTx (txls, addrs) = (map f txls, length addrs)-  where-    f tx = (walletTxAccount tx, walletTxConfidence tx)-