Skip to content

Commit

Permalink
Use SQLite to store wallet state (#7)
Browse files Browse the repository at this point in the history
* Use an SQlite3 database to store accounts, addresses, transactions, coins and pending transactions locally
* The database is synced with the blockchain using the syncacc command
* Add a configuration file to specify a different API host, the gap value and batching values
* Account and transaction import/export are in separate commands
* Pending transactions now properly lock the coins they spend
* Improved internal address management
  • Loading branch information
plaprade authored Oct 31, 2023
1 parent ef7aaaa commit 36e6f5f
Show file tree
Hide file tree
Showing 32 changed files with 5,809 additions and 3,639 deletions.
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Haskoin Wallet

Haskoin Wallet (`hw`) is a lightweight [BIP44] command line wallet for bitcoin and bitcoin-cash. It can be used to manage cold storage funds in an online/offline environment. It requires a haskoin-store server for querying addresses and transactions. It is currently not suitable for large wallets as the local database is a JSON file.
Haskoin Wallet (`hw`) is a lightweight [BIP44] command line wallet for bitcoin
and bitcoin-cash. It can be used to manage cold storage funds in an
online/offline environment. It requires a haskoin-store server for querying
addresses and transactions. It is suitable for small-ish wallets that are
managed by hand although work is being done to improve performance.

## Dependencies

```console
apt install libsecp256k1-dev
```

## Build

Expand All @@ -13,9 +23,10 @@ stack install

```console
hw --help
hw COMMAND --help
```

## Verify binaries
## Verify release binaries

### Get the GPG key

Expand All @@ -39,7 +50,19 @@ sha256sum --check SHA256SUMS

## Critical Bugs

* Version 0.7.0 does not derive the correct account when using a --split mnemonic. Your funds are still secure and not lost but you should use the 0.7.0 binary to move your funds to a new wallet created with the latest release.
* Versions prior to 0.8.0 do not derive the correct accounts when using more
than one mnemonic (not the --split option, but different wallets). For
example, creating an account with mnemonic 1 yields account /44'/0'/0'. If you
then create an account using mnemonic 2, it yields the account /44'/0'/1'. As
it is a new mnemonic and thus a new wallet, it should yield account
/44'/0'/0'. Your funds are still secure and not lost but you should write down
the account derivation that you are using for reference if you need to recover
it. This bug is fixed in version 0.8.0.

* Version 0.7.0 does not derive the correct account when using a --split
mnemonic. Your funds are still secure and not lost but you should use the
0.7.0 binary to move your funds to a new wallet created with the latest
release.

[BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
[BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
Expand Down
2 changes: 1 addition & 1 deletion app/Main.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Network.Haskoin.Wallet
import Haskoin.Wallet.Main

main :: IO ()
main = clientMain
Expand Down
63 changes: 43 additions & 20 deletions haskoin-wallet.cabal
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
cabal-version: 1.12

-- This file has been generated from package.yaml by hpack version 0.35.2.
-- This file has been generated from package.yaml by hpack version 0.36.0.
--
-- see: https://github.com/sol/hpack
--
-- hash: 9428b61cc16c2222adae2f1931d2eb6ac71299ed2352c243d9c7f9e298c51bef
-- hash: 0e1826aa518f8c7890f529498b3362e18f28ddf992adaf9192167af089f6bd44

name: haskoin-wallet
version: 0.7.2
version: 0.8.0
synopsis: Lightweight command-line wallet for Bitcoin and Bitcoin Cash
description: haskoin-wallet (hw) is a lightweight Bitcoin wallet using BIP39 mnemonics and
BIP44 account structure. It requires a full blockchain index such as
Expand Down Expand Up @@ -42,38 +42,47 @@ library
, 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.0
, haskoin-store-data >=1.0.0
, 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.0.0
, string-conversions >=0.4.0.1
, text >=1.2.4.0
, time >=1.12.2
, tostring >=0.2.1.1
, transformers >=0.5.6.2
, unordered-containers >=0.2.10.0
, wreq >=0.5.3.2
exposed-modules:
Network.Haskoin.Wallet
Network.Haskoin.Wallet.AccountStore
Network.Haskoin.Wallet.Amounts
Network.Haskoin.Wallet.Commands
Network.Haskoin.Wallet.Entropy
Network.Haskoin.Wallet.FileIO
Network.Haskoin.Wallet.Parser
Network.Haskoin.Wallet.Signing
Network.Haskoin.Wallet.TxInfo
Network.Haskoin.Wallet.Util
Haskoin.Wallet
Haskoin.Wallet.Amounts
Haskoin.Wallet.Commands
Haskoin.Wallet.Config
Haskoin.Wallet.Database
Haskoin.Wallet.Entropy
Haskoin.Wallet.FileIO
Haskoin.Wallet.Main
Haskoin.Wallet.Parser
Haskoin.Wallet.Signing
Haskoin.Wallet.TxInfo
Haskoin.Wallet.Util
other-modules:
Paths_haskoin_wallet
default-language: Haskell2010
Expand All @@ -93,24 +102,31 @@ executable hw
, 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.0
, haskoin-store-data >=1.0.0
, haskoin-wallet ==0.7.2
, haskoin-wallet ==0.8.0
, 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.0.0
, string-conversions >=0.4.0.1
, text >=1.2.4.0
, time >=1.12.2
, tostring >=0.2.1.1
, transformers >=0.5.6.2
, unordered-containers >=0.2.10.0
Expand All @@ -123,11 +139,11 @@ test-suite spec
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
Network.Haskoin.Wallet.AccountStoreSpec
Network.Haskoin.Wallet.AmountsSpec
Network.Haskoin.Wallet.EntropySpec
Network.Haskoin.Wallet.SigningSpec
Network.Haskoin.Wallet.TestUtils
Haskoin.Wallet.AmountsSpec
Haskoin.Wallet.CommandsSpec
Haskoin.Wallet.EntropySpec
Haskoin.Wallet.SigningSpec
Haskoin.Wallet.TestUtils
Paths_haskoin_wallet
hs-source-dirs:
test
Expand All @@ -144,25 +160,32 @@ test-suite spec
, 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.0
, haskoin-store-data >=1.0.0
, haskoin-wallet ==0.7.2
, haskoin-wallet ==0.8.0
, 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.0.0
, string-conversions >=0.4.0.1
, text >=1.2.4.0
, time >=1.12.2
, tostring >=0.2.1.1
, transformers >=0.5.6.2
, unordered-containers >=0.2.10.0
Expand Down
9 changes: 8 additions & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: haskoin-wallet
version: &version 0.7.2
version: &version 0.8.0
synopsis: Lightweight command-line wallet for Bitcoin and Bitcoin Cash
description: !
haskoin-wallet (hw) is a lightweight Bitcoin wallet using BIP39 mnemonics and
Expand Down Expand Up @@ -30,24 +30,31 @@ dependencies:
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"
Decimal: ">= 0.5.1"
entropy: ">= 0.4.1.6"
esqueleto: ">=3.5.10.1"
haskeline: ">= 0.7.5.0"
haskoin-core: ">= 1.0.0"
haskoin-store-data: ">= 1.0.0"
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.0.0"
string-conversions: ">= 0.4.0.1"
text: ">= 1.2.4.0"
time: ">= 1.12.2"
tostring: ">= 0.2.1.1"
transformers: ">= 0.5.6.2"
unordered-containers: ">= 0.2.10.0"
Expand Down
24 changes: 24 additions & 0 deletions src/Haskoin/Wallet.hs
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,14 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}

module Network.Haskoin.Wallet.Amounts where
module Haskoin.Wallet.Amounts where

import Control.Arrow (second)
import Control.Monad (guard)
import Data.Text as Text
( Text,
breakOn,
drop,
filter,
intercalate,
length,
pack,
uncons,
)
import Data.Text.Read as Read (decimal)
import Network.Haskoin.Wallet.Util
( chunksOfEnd,
dropPatternEnd,
padEnd,
padStart,
)
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
Expand Down Expand Up @@ -58,7 +45,7 @@ showAmount unit amnt =
where
removeEnd = dropPatternEnd "0000" . dropPatternEnd "000000"
addSep = Text.intercalate "'" . chunksOfEnd 3
showT = pack . show
showT = Text.pack . show

readAmount :: AmountUnit -> Text -> Maybe Natural
readAmount unit amntStr =
Expand All @@ -76,7 +63,7 @@ readAmount unit amntStr =
UnitSatoshi -> readNatural str
where
str = dropAmountSep amntStr
(q, r) = second (Text.drop 1) $ breakOn "." str
(q, r) = second (Text.drop 1) $ Text.breakOn "." str

readNatural :: Text -> Maybe Natural
readNatural txt =
Expand All @@ -96,6 +83,6 @@ showIntegerAmount unit i
-- | Like 'readAmount' but can parse a negative amount
readIntegerAmount :: AmountUnit -> Text -> Maybe Integer
readIntegerAmount unit txt =
case uncons txt of
case Text.uncons txt of
Just ('-', rest) -> negate . toInteger <$> readAmount unit rest
_ -> toInteger <$> readAmount unit txt
Loading

0 comments on commit 36e6f5f

Please sign in to comment.