diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for grpc-etcd-client
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2018
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# grpc-etcd-client
+
+A gRPC client for the EtcdV3 api.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/grpc-etcd-client.cabal b/grpc-etcd-client.cabal
new file mode 100644
--- /dev/null
+++ b/grpc-etcd-client.cabal
@@ -0,0 +1,48 @@
+-- This file has been generated from package.yaml by hpack version 0.28.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 9f72b3a871af877169da36a4b0357d2ca7a2294a6ea8d5fa2d7729847c6517f3
+
+name:           grpc-etcd-client
+version:        0.1.0.0
+synopsis:       gRPC client for etcd
+description:    Please see the README on GitHub at <https://github.com/lucasdicioccio/grpc-etcd-client#readme>
+category:       Distributed Computing
+homepage:       https://github.com/lucasdicioccio/grpc-etcd-client#readme
+bug-reports:    https://github.com/lucasdicioccio/grpc-etcd-client/issues
+author:         Lucas DiCioccio
+maintainer:     lucas@dicioccio.fr
+copyright:      2018 Lucas DiCioccio
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+extra-source-files:
+    ChangeLog.md
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/lucasdicioccio/grpc-etcd-client
+
+library
+  exposed-modules:
+      Network.EtcdV3
+  other-modules:
+      Paths_grpc_etcd_client
+  hs-source-dirs:
+      src
+  default-extensions: DataKinds OverloadedStrings
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , data-default-class
+    , grpc-api-etcd ==0.1.0.0
+    , http2-client
+    , http2-client-grpc >=0.5 && <0.6
+    , lens
+    , network
+    , proto-lens
+    , proto-lens-protoc
+  default-language: Haskell2010
diff --git a/src/Network/EtcdV3.hs b/src/Network/EtcdV3.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/EtcdV3.hs
@@ -0,0 +1,146 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+-- | Simple lib to access Etcd over gRPC.
+module Network.EtcdV3
+    (
+    -- * Generalities.
+      etcdClientConfigSimple
+    , EtcdQuery
+    -- * Reading.
+    , KeyRange(..)
+    , range
+    , rangeResponsePairs
+    -- * Granting leases.
+    , grantLease
+    , fromLeaseGrantResponse
+    , keepAlive
+    -- * Writing.
+    , put
+    -- * re-exports
+    , def
+    , module Control.Lens
+    , module EtcdPB
+    , module EtcdRPC
+    ) where
+
+import Control.Lens
+import Data.Default.Class (def)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as C8
+import Data.Semigroup (Endo)
+import GHC.Int (Int64)
+import Network.GRPC.Client
+import Network.GRPC.Client.Helpers
+import Network.Socket (HostName, PortNumber)
+import Proto.Etcd.Etcdserver.Etcdserverpb.Rpc as EtcdRPC
+import Proto.Etcd.Etcdserver.Etcdserverpb.Rpc_Fields as EtcdPB
+
+-- | EtcdClient configuration.
+etcdClientConfigSimple :: HostName -> PortNumber -> UseTlsOrNot -> GrpcClientConfig
+etcdClientConfigSimple host port tls =
+    (grpcClientConfigSimple host port tls) { _grpcClientConfigCompression = uncompressed }
+
+-- | Helper to unpack an unary gRPC call output.
+unaryOutput
+  :: (Applicative f, Field3 a1 b1 (Either c1 a2) (Either c1 b2)) =>
+     (a2 -> f b2)
+     -> Either c2 (Either c3 a1) -> f (Either c2 (Either c3 b1))
+unaryOutput = _Right . _Right . _3 . _Right
+
+-- | Type alias to simplify type signatures.
+type EtcdQuery a = IO (Maybe a)
+
+-- | Data type to unify the three addressing schemes in etcd.
+--
+-- See 'range'.
+data KeyRange
+  = SingleKey !ByteString
+  -- ^ Exactly one key.
+  | FromKey !ByteString
+  | Prefixed !ByteString
+  deriving (Show, Eq, Ord)
+
+-- | Lookup a range of values
+range
+  :: GrpcClient
+  -- ^ Initialized gRPC client.
+  -> KeyRange
+  -- ^ Looked-up range.
+  -> EtcdQuery RangeResponse
+range grpc r = preview unaryOutput <$>
+    rawUnary (RPC :: RPC KV "range") grpc (def & key .~ k0 & rangeEnd .~ kend)
+  where
+    (k0, kend) = rangePairForRangeQuery r
+
+-- | Specific fold to get the key-values of a 'RangeResponse'
+--
+-- Typical usage is:
+--
+-- @ x <- range grpc (Prefixed "/some-dir/")
+-- @ print $ x ^.. _Just . rangePairs
+--
+-- Note that Etcd RangeResponse is a rich object, please refer to Etcd
+-- documentation to understand what you will miss out (e.g., whether the list
+-- is complete or not).
+rangeResponsePairs
+  :: Getting (Endo [(ByteString, ByteString)]) RangeResponse (ByteString, ByteString)
+rangeResponsePairs = kvs . traverse . to (\x -> (x ^. key, x ^. value))
+
+-- | Internal helper for turning a 'KeyRange' into the start/end queries Etcd
+-- 'range' RPC needs.
+rangePairForRangeQuery :: KeyRange -> (ByteString, ByteString)
+rangePairForRangeQuery (SingleKey k) = (k, "")
+rangePairForRangeQuery (FromKey k) = (k, "\NUL")
+rangePairForRangeQuery (Prefixed k) = (k, kPlus1)
+  where
+    rest = C8.dropWhile (== '\xff') $ C8.reverse k
+    kPlus1 = if C8.null rest then "\NUL" else C8.reverse $ C8.cons (succ (C8.head rest)) (C8.drop 1 rest)
+
+-- | Asks for a lease of a given duration.
+grantLease
+  :: GrpcClient
+  -- ^ Initialized gRPC client.
+  -> Int64
+  -- ^ TTL for the lease.
+  -> EtcdQuery LeaseGrantResponse
+grantLease grpc seconds =
+    preview unaryOutput <$> rawUnary (RPC :: RPC Lease "leaseGrant") grpc (def & ttl .~ seconds)
+
+-- | Opaque lease result.
+--
+-- Show instance used for printing purposes only.
+newtype GrantedLease = GrantedLease { _getGrantedLeaseId :: Int64 }
+
+instance Show GrantedLease where
+  show (GrantedLease n) = "(lease #" <> show n <> ")"
+
+-- | Constructor for 'GrantedLease'.
+fromLeaseGrantResponse :: LeaseGrantResponse -> GrantedLease
+fromLeaseGrantResponse r = GrantedLease $ r ^. EtcdPB.id
+
+-- | Keep a lease alive.
+keepAlive
+  :: GrpcClient
+  -- ^ Initialized gRPC client.
+  -> GrantedLease
+  -- ^ A previously-granted lease.
+  -> EtcdQuery LeaseKeepAliveResponse
+keepAlive grpc (GrantedLease leaseID) =
+    preview unaryOutput <$> rawUnary (RPC :: RPC Lease "leaseKeepAlive") grpc (def & EtcdPB.id .~ leaseID)
+
+-- | Put one value.
+put
+  :: GrpcClient
+  -- ^ initialized gRPC client
+  -> ByteString
+  -- ^ Key.
+  -> ByteString
+
+  -- ^ Value.
+  -> Maybe GrantedLease
+  -- ^ Lease on the key.
+  -> EtcdQuery PutResponse
+put grpc k v gl =
+    preview unaryOutput <$> rawUnary (RPC :: RPC KV "put") grpc (def & key .~ k & value .~ v & lease .~ l)
+  where
+    l = maybe 0 _getGrantedLeaseId gl
