diff --git a/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt b/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt
index 91ffff1f547b5e740c18005df1906c11ac18b6be..037a1c64a83b0a4ec2b35dd243793be5c0a25749 100644
--- a/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt
+++ b/app/src/main/java/com/futo/circles/extensions/LiveDataExtensions.kt
@@ -8,7 +8,7 @@ import org.matrix.android.sdk.api.failure.Failure
 
 fun <T> SingleEventLiveData<Response<T>>.observeResponse(
     hasLoadingState: HasLoadingState,
-    success: (T) -> Unit,
+    success: (T) -> Unit = {},
     error: ((String) -> Unit)? = null,
     onRequestInvoked: (() -> Unit)? = null
 ) {
diff --git a/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt b/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt
index 4470e2e0377d693b3ee25973fdecf9da7700c086..fe1564a11bcfea4972c81149e924d497856b73d4 100644
--- a/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt
+++ b/app/src/main/java/com/futo/circles/feature/log_in/LogInViewModel.kt
@@ -5,6 +5,7 @@ import com.futo.circles.core.SingleEventLiveData
 import com.futo.circles.extensions.Response
 import com.futo.circles.extensions.launchBg
 import com.futo.circles.feature.log_in.data_source.LoginDataSource
+import org.matrix.android.sdk.api.auth.data.LoginFlowResult
 import org.matrix.android.sdk.api.session.Session
 
 class LogInViewModel(
@@ -12,7 +13,7 @@ class LogInViewModel(
 ) : ViewModel() {
 
     var loginResultLiveData = SingleEventLiveData<Response<Session>>()
-    var signUpEventResultLiveData = SingleEventLiveData<Response<Unit?>>()
+    var signUpEventResultLiveData = SingleEventLiveData<Response<LoginFlowResult>>()
 
     fun logIn(name: String, password: String) {
         launchBg {
diff --git a/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt b/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt
index 69f54f5f277ca6ea5f4307c5a861810abf34336e..b978b92d592f46c51d58cbaf0b7c092d24ee9167 100644
--- a/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt
+++ b/app/src/main/java/com/futo/circles/feature/log_in/data_source/LoginDataSource.kt
@@ -5,16 +5,11 @@ import android.net.Uri
 import com.futo.circles.BuildConfig
 import com.futo.circles.R
 import com.futo.circles.extensions.createResult
-import com.futo.circles.feature.sign_up.data_source.SignUpDataSource
 import com.futo.circles.provider.MatrixInstanceProvider
 import com.futo.circles.provider.MatrixSessionProvider
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
-import org.matrix.android.sdk.api.auth.registration.RegistrationResult
 
-class LoginDataSource(
-    private val context: Context,
-    private val signUpDataSource: SignUpDataSource
-) {
+class LoginDataSource(private val context: Context) {
 
     private val homeServerConnectionConfig by lazy {
         HomeServerConnectionConfig
@@ -42,9 +37,5 @@ class LoginDataSource(
 
     suspend fun startSignUp() = createResult {
         authService.getLoginFlow(homeServerConnectionConfig)
-        (authService.getRegistrationWizard()
-            .getRegistrationFlow() as? RegistrationResult.FlowResponse)?.let {
-            signUpDataSource.startNewRegistration(it.flowResult.missingStages)
-        }
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt
index 5edbfe360069870a59c2d846eb4f2b30669d8197..f794ab3cd3909c93d0d6e86246f54a50b187988a 100644
--- a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt
+++ b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpFragment.kt
@@ -2,11 +2,15 @@ package com.futo.circles.feature.sign_up
 
 import android.os.Bundle
 import android.view.View
+import androidx.activity.OnBackPressedCallback
 import androidx.fragment.app.Fragment
+import androidx.navigation.findNavController
 import androidx.navigation.fragment.findNavController
 import by.kirich1409.viewbindingdelegate.viewBinding
 import com.futo.circles.R
 import com.futo.circles.databinding.SignUpFragmentBinding
+import com.futo.circles.extensions.observeData
+import com.futo.circles.feature.sign_up.data_source.NavigationEvents
 import org.koin.androidx.viewmodel.ext.android.viewModel
 
 class SignUpFragment : Fragment(R.layout.sign_up_fragment) {
@@ -16,7 +20,47 @@ class SignUpFragment : Fragment(R.layout.sign_up_fragment) {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        binding.toolbar.setNavigationOnClickListener { findNavController().popBackStack() }
+        binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
+
+        setupObservers()
+        setupBackPressCallback()
+    }
+
+    private fun setupObservers() {
+        viewModel.subtitleLiveData.observeData(this) {
+            binding.toolbar.subtitle = it
+        }
+        viewModel.navigationLiveData.observeData(this) {
+            handleNavigation(it)
+        }
+    }
+
+    private fun setupBackPressCallback() {
+        activity?.onBackPressedDispatcher
+            ?.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    val canPop = binding.navHostFragment.findNavController().popBackStack()
+                    if (!canPop) {
+                        isEnabled = false
+                        requireActivity().onBackPressed()
+                    }
+                }
+            }
+            )
+    }
+
+    private fun handleNavigation(event: NavigationEvents) {
+        val childNavigationController = binding.navHostFragment.findNavController()
+
+        when (event) {
+            NavigationEvents.TokenValidation -> childNavigationController.navigate(R.id.to_validateToken)
+            NavigationEvents.AcceptTerm -> childNavigationController.navigate(R.id.to_acceptTerms)
+            NavigationEvents.VerifyEmail -> childNavigationController.navigate(R.id.to_createAccount)
+            NavigationEvents.SetupAvatar -> TODO()
+            NavigationEvents.SetupCircles -> TODO()
+            NavigationEvents.FinishSignUp -> findNavController()
+                .navigate(SignUpFragmentDirections.toBottomNavigationFragment())
+        }
     }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt
index 7677ea8b1040209b593ba9116a2aad4d2b99a5f9..947bd48f696fa5700896f871eff6a6afcdfe7a7e 100644
--- a/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt
+++ b/app/src/main/java/com/futo/circles/feature/sign_up/SignUpViewModel.kt
@@ -7,5 +7,8 @@ class SignUpViewModel(
     datasource: SignUpDataSource
 ) : ViewModel() {
 
+    val subtitleLiveData = datasource.subtitleLiveData
+
+    val navigationLiveData = datasource.navigationLiveData
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt b/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt
index cf5da59c0d256f29c11230efade6f617f197ad68..04a26cf58a9db7426fe785aaf4a7b41de8ea768b 100644
--- a/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt
+++ b/app/src/main/java/com/futo/circles/feature/sign_up/data_source/SignUpDataSource.kt
@@ -1,24 +1,88 @@
 package com.futo.circles.feature.sign_up.data_source
 
-import com.futo.circles.extensions.getPendingSignUpSessionId
-import com.futo.circles.provider.MatrixInstanceProvider
+import android.content.Context
+import androidx.lifecycle.MutableLiveData
+import com.futo.circles.R
+import com.futo.circles.core.SingleEventLiveData
+import org.matrix.android.sdk.api.auth.registration.RegistrationResult
 import org.matrix.android.sdk.api.auth.registration.Stage
+import org.matrix.android.sdk.api.session.Session
 
-class SignUpDataSource {
+enum class ExtraSignUpStages { Avatar, Circles }
+
+enum class NavigationEvents { TokenValidation, AcceptTerm, VerifyEmail, SetupAvatar, SetupCircles, FinishSignUp }
+
+class SignUpDataSource(
+    private val context: Context
+) {
+
+    val subtitleLiveData = MutableLiveData<String>()
+    val navigationLiveData = SingleEventLiveData<NavigationEvents>()
 
     private val stagesToComplete = mutableListOf<Stage>()
+    private val completedStages = mutableListOf<Stage>()
+
+    var currentStage: Stage? = null
+        private set
 
-    private val registrationWizard by lazy {
-        MatrixInstanceProvider.matrix.authenticationService().getRegistrationWizard()
-    }
 
-    fun startNewRegistration(stages: List<Stage>) {
+    fun startSignUpStages(stages: List<Stage>) {
+        currentStage = null
+        completedStages.clear()
         stagesToComplete.clear()
+
         stagesToComplete.addAll(stages)
+        ExtraSignUpStages.values().forEach {
+            stagesToComplete.add(Stage.Other(false, it.name, null))
+        }
+        navigateToNextStage()
+    }
+
+    fun stageCompleted(result: RegistrationResult?) {
+        (result as? RegistrationResult.Success)?.let {
+            finishRegistration(it.session)
+        } ?: run {
+            currentStage?.let { completedStages.add(it) }
+            navigateToNextStage()
+        }
+    }
+
+    fun clearSubtitle() {
+        subtitleLiveData.postValue("")
     }
 
-    fun getPendingSessionId() = registrationWizard.getPendingSignUpSessionId()
+    private fun finishRegistration(session: Session) {
+        navigationLiveData.postValue(NavigationEvents.FinishSignUp)
+    }
+
+    private fun getCurrentStageIndex() =
+        stagesToComplete.indexOf(currentStage).takeIf { it != -1 } ?: 0
+
+    private fun navigateToNextStage() {
+        val stage = currentStage?.let {
+            stagesToComplete.getOrNull(getCurrentStageIndex() + 1)
+        } ?: stagesToComplete.firstOrNull()
+
+        currentStage = stage
+
+        val event = when (stage) {
+            is Stage.Email -> NavigationEvents.VerifyEmail
+            is Stage.Terms -> NavigationEvents.AcceptTerm
+            is Stage.Other -> NavigationEvents.TokenValidation
+            else -> throw IllegalArgumentException("Not supported stage $stage")
+        }
+
+        navigationLiveData.postValue(event)
+
+        updatePageSubtitle()
+    }
+
+    private fun updatePageSubtitle() {
+        val size = stagesToComplete.size
+        val number = getCurrentStageIndex() + 1
+        val subtitle = context.getString(R.string.sign_up_stage_subtitle_format, number, size)
+        subtitleLiveData.postValue(subtitle)
+    }
 
-    fun getCurrentStage() = stagesToComplete.first()
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt b/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt
index ccb60867fadc0f3e2fee22badc3d45855689dec9..e92f3b78df8317ccd750a889facfd2670e1719f3 100644
--- a/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt
+++ b/app/src/main/java/com/futo/circles/feature/sign_up_type/SelectSignUpTypeFragment.kt
@@ -3,23 +3,31 @@ package com.futo.circles.feature.sign_up_type
 import android.os.Bundle
 import android.view.View
 import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
 import by.kirich1409.viewbindingdelegate.viewBinding
 import com.futo.circles.R
+import com.futo.circles.core.HasLoadingState
 import com.futo.circles.databinding.SelectSignUpTypeFragmentBinding
+import com.futo.circles.extensions.observeResponse
+import org.koin.androidx.viewmodel.ext.android.viewModel
 
-class SelectSignUpTypeFragment : Fragment(R.layout.select_sign_up_type_fragment) {
+class SelectSignUpTypeFragment : Fragment(R.layout.select_sign_up_type_fragment), HasLoadingState {
+
+    override val fragment: Fragment = this
 
     private val binding by viewBinding(SelectSignUpTypeFragmentBinding::bind)
+    private val viewModel by viewModel<SelectSignUpTypeViewModel>()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        binding.btnToken.setOnClickListener { navigateToTokenValidation() }
-    }
+        viewModel.clearSubtitle()
 
-    private fun navigateToTokenValidation() {
-        findNavController()
-            .navigate(SelectSignUpTypeFragmentDirections.toValidateTokenFragment())
+        binding.btnToken.setOnClickListener {
+            startLoading(binding.btnToken)
+            viewModel.startSignUp()
+        }
+
+        viewModel.startSignUpEventLiveData.observeResponse(this)
     }
 
+
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt
index 6ee7a2364210e2f54e8e181a9be83d08d3e84c1b..147ea74028d788238bca3bbd476bf57170f761a3 100644
--- a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt
+++ b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenFragment.kt
@@ -43,9 +43,6 @@ class ValidateTokenFragment : Fragment(R.layout.validate_token_fragment), HasLoa
     }
 
     private fun setupObservers() {
-        viewModel.validateLiveData.observeResponse(
-            this,
-            success = { }
-        )
+        viewModel.validateLiveData.observeResponse(this)
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt
index 2d098ba966f14fdff2e665af0356ec0e434d6b3e..437459369540845e943ffeaa45ce198c2d93c191 100644
--- a/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt
+++ b/app/src/main/java/com/futo/circles/feature/validate_token/ValidateTokenViewModel.kt
@@ -5,15 +5,18 @@ import com.futo.circles.core.SingleEventLiveData
 import com.futo.circles.extensions.Response
 import com.futo.circles.extensions.launchBg
 import com.futo.circles.feature.validate_token.data_source.ValidateTokenDataSource
+import okhttp3.ResponseBody
 
 class ValidateTokenViewModel(
     private val dataSource: ValidateTokenDataSource
 ) : ViewModel() {
 
-    val validateLiveData = SingleEventLiveData<Response<okhttp3.Response>>()
+    val validateLiveData = SingleEventLiveData<Response<ResponseBody>>()
 
     fun validateToken(token: String) {
-        launchBg { dataSource.validateToken(token) }
+        launchBg {
+            validateLiveData.postValue(dataSource.validateToken(token))
+        }
     }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt b/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt
index fe3f8687066f3ae59ee2e8015a9fcff95b65af8f..7dc5cb89ab99ffbd9a800238c10fe33cb63792fd 100644
--- a/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt
+++ b/app/src/main/java/com/futo/circles/feature/validate_token/data_source/ValidateTokenDataSource.kt
@@ -1,8 +1,14 @@
 package com.futo.circles.feature.validate_token.data_source
 
+import com.futo.circles.extensions.Response
 import com.futo.circles.extensions.createResult
+import com.futo.circles.extensions.getPendingSignUpSessionId
 import com.futo.circles.feature.sign_up.data_source.SignUpDataSource
+import com.futo.circles.io.request.ValidateSignUpTokenRequestBody
+import com.futo.circles.io.request.ValidateTokenRequestParams
 import com.futo.circles.io.service.CirclesSignUpService
+import com.futo.circles.provider.MatrixInstanceProvider
+import okhttp3.ResponseBody
 import org.matrix.android.sdk.api.auth.registration.Stage
 
 class ValidateTokenDataSource(
@@ -10,14 +16,29 @@ class ValidateTokenDataSource(
     private val signUpDataSource: SignUpDataSource
 ) {
 
-    suspend fun validateToken(token: String) = createResult {
-        val type = (signUpDataSource.getCurrentStage() as? Stage.Other)?.type
-            ?: throw IllegalArgumentException()
+    private val pendingSessionId by lazy {
+        MatrixInstanceProvider.matrix.authenticationService()
+            .getRegistrationWizard().getPendingSignUpSessionId()
+    }
+
+    suspend fun validateToken(token: String): Response<ResponseBody> {
+        val result = createResult {
+            val type = (signUpDataSource.currentStage as? Stage.Other)?.type
+                ?: throw IllegalArgumentException()
+
+            service.validateSignUpToken(
+                ValidateSignUpTokenRequestBody(
+                    ValidateTokenRequestParams(
+                        type = type,
+                        token = token,
+                        session = pendingSessionId
+                    )
+                )
+            )
+        }
+
+        (result as? Response.Success)?.let { signUpDataSource.stageCompleted(null) }
 
-        service.validateSignUpToken(
-            type = type,
-            token = token,
-            session = signUpDataSource.getPendingSessionId()
-        )
+        return result
     }
 }
\ No newline at end of file
diff --git a/app/src/main/res/layout/select_sign_up_type_fragment.xml b/app/src/main/res/layout/select_sign_up_type_fragment.xml
index c653423bd95bc191eb6ceaba72bacd9db1561800..683a1a8bce7d10a0520eabf669750d8f7689540b 100644
--- a/app/src/main/res/layout/select_sign_up_type_fragment.xml
+++ b/app/src/main/res/layout/select_sign_up_type_fragment.xml
@@ -37,9 +37,8 @@
         app:layout_constraintVertical_bias="0.3"
         app:layout_constraintVertical_chainStyle="packed" />
 
-    <com.google.android.material.button.MaterialButton
+    <com.futo.circles.view.LoadingButton
         android:id="@+id/btnToken"
-        style="@style/AccentButtonStyle"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginTop="8dp"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2174648849ae1b8944ca882542f7a3170fe424b8..7ec8611f62fdbe875524ca8e85ba7093ebdfb969 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -63,6 +63,7 @@
     <string name="sign_up_token_format">abcd–efgh–1234–5678</string>
     <string name="sign_up_token">SignUp token</string>
     <string name="sign_up_token_explanation">In order to sign up for a service, every new user must present a valid registration token.\n\nIf you found out about the app from a friend or from the posting online, you should be able to get a signup token from the same source.</string>
+    <string name="sign_up_stage_subtitle_format">Stage %d of %d</string>
 
     <plurals name="member_plurals">
         <item quantity="one">%d member</item>