diff --git a/Sources/ElixxirDAppsSDK/ClientStorage.swift b/Sources/ElixxirDAppsSDK/ClientStorage.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8471fa783efd5d7cd6ce80007bd9708d17ce3229
--- /dev/null
+++ b/Sources/ElixxirDAppsSDK/ClientStorage.swift
@@ -0,0 +1,69 @@
+import Bindings
+
+public struct ClientStorage {
+  public var hasStoredClient: () -> Bool
+  public var createClient: () throws -> Client
+  public var loadClient: () throws -> Client
+  public var removeClient: () throws -> Void
+}
+
+extension ClientStorage {
+  public static let defaultDirectoryURL = FileManager.default
+    .urls(for: .applicationSupportDirectory, in: .userDomainMask)
+    .first!
+    .appendingPathComponent("xx.network.client")
+
+  public static func live(
+    environment: Environment = .mainnet,
+    directoryURL: URL = defaultDirectoryURL,
+    fileManager: FileManager = .default,
+    generatePassword: PasswordGenerator = .live,
+    passwordStorage: PasswordStorage,
+    downloadNDF: NDFDownloader = .live,
+    createClient: ClientCreator = .live,
+    loadClient: ClientLoader = .live
+  ) -> ClientStorage {
+    ClientStorage(
+      hasStoredClient: {
+        let contents = try? fileManager.contentsOfDirectory(atPath: directoryURL.path)
+        return contents.map { $0.isEmpty == false } ?? false
+      },
+      createClient: {
+        let ndf = try downloadNDF(environment)
+        let password = try generatePassword()
+        try passwordStorage.save(password)
+        try? fileManager.removeItem(at: directoryURL)
+        try? fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
+        try createClient(directoryURL: directoryURL, ndf: ndf, password: password)
+        return try loadClient(directoryURL: directoryURL, password: password)
+      },
+      loadClient: {
+        let password = try passwordStorage.load()
+        return try loadClient(directoryURL: directoryURL, password: password)
+      },
+      removeClient: {
+        try fileManager.removeItem(at: directoryURL)
+      }
+    )
+  }
+}
+
+#if DEBUG
+extension ClientStorage {
+  public static let failing = ClientStorage(
+    hasStoredClient: { false },
+    createClient: {
+      struct NotImplemented: Error {}
+      throw NotImplemented()
+    },
+    loadClient: {
+      struct NotImplemented: Error {}
+      throw NotImplemented()
+    },
+    removeClient: {
+      struct NotImplemented: Error {}
+      throw NotImplemented()
+    }
+  )
+}
+#endif