diff --git a/features/wallet/api/build.gradle.kts b/features/wallet/api/build.gradle.kts
new file mode 100644
index 0000000000..351d8373c2
--- /dev/null
+++ b/features/wallet/api/build.gradle.kts
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+plugins {
+ id("io.element.android-library")
+}
+
+android {
+ namespace = "io.element.android.features.wallet.api"
+}
+
+dependencies {
+ implementation(projects.libraries.architecture)
+ implementation(projects.libraries.matrix.api)
+}
diff --git a/features/wallet/api/src/main/AndroidManifest.xml b/features/wallet/api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..0baf68a8a8
--- /dev/null
+++ b/features/wallet/api/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/WalletEntryPoint.kt b/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/WalletEntryPoint.kt
new file mode 100644
index 0000000000..c8d5595dcc
--- /dev/null
+++ b/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/WalletEntryPoint.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package io.element.android.features.wallet.api
+
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import io.element.android.libraries.architecture.FeatureEntryPoint
+import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.UserId
+
+/**
+ * Entry point for the Cardano wallet feature.
+ * Provides navigation to payment flows and wallet management.
+ */
+interface WalletEntryPoint : FeatureEntryPoint {
+ /**
+ * Builder for creating wallet flow nodes.
+ */
+ interface Builder {
+ fun setRoomId(roomId: RoomId): Builder
+ fun setRecipientUserId(userId: UserId?): Builder
+ fun setRecipientAddress(address: String?): Builder
+ fun setAmount(amount: String?): Builder
+ fun build(): Node
+ }
+
+ /**
+ * Creates a builder for the payment flow.
+ */
+ fun paymentFlowBuilder(
+ parentNode: Node,
+ buildContext: BuildContext,
+ callback: Callback,
+ ): Builder
+
+ /**
+ * Callback for wallet flow events.
+ */
+ interface Callback : Plugin {
+ fun onPaymentSent(txHash: String)
+ fun onPaymentCancelled()
+ }
+}
diff --git a/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/WalletState.kt b/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/WalletState.kt
new file mode 100644
index 0000000000..42e4f134a9
--- /dev/null
+++ b/features/wallet/api/src/main/kotlin/io/element/android/features/wallet/api/WalletState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package io.element.android.features.wallet.api
+
+/**
+ * Represents the current state of the Cardano wallet.
+ */
+data class WalletState(
+ val hasWallet: Boolean,
+ val address: String?,
+ val balanceLovelace: Long?,
+ val balanceAda: String?,
+ val isLoading: Boolean,
+ val error: String?,
+) {
+ companion object {
+ val Initial = WalletState(
+ hasWallet = false,
+ address = null,
+ balanceLovelace = null,
+ balanceAda = null,
+ isLoading = true,
+ error = null,
+ )
+ }
+}
diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts
new file mode 100644
index 0000000000..5ea24bb2ff
--- /dev/null
+++ b/features/wallet/impl/build.gradle.kts
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import extension.setupDependencyInjection
+
+plugins {
+ id("io.element.android-compose-library")
+ alias(libs.plugins.kotlin.serialization)
+}
+
+android {
+ namespace = "io.element.android.features.wallet.impl"
+
+ testOptions {
+ unitTests {
+ isIncludeAndroidResources = true
+ }
+ }
+}
+
+setupDependencyInjection()
+
+dependencies {
+ api(projects.features.wallet.api)
+ implementation(projects.libraries.architecture)
+ implementation(projects.libraries.core)
+ implementation(projects.libraries.matrix.api)
+ implementation(projects.libraries.matrix.impl)
+ implementation(projects.libraries.designsystem)
+ implementation(projects.libraries.cryptography.api)
+ implementation(projects.libraries.uiStrings)
+
+ // Cardano - using Koios backend (no API key required)
+ implementation("com.bloxbean.cardano:cardano-client-lib:0.7.1")
+ implementation("com.bloxbean.cardano:cardano-client-backend-koios:0.7.1")
+ implementation("com.bloxbean.cardano:cardano-client-crypto:0.7.1")
+
+ // Biometric
+ implementation(libs.androidx.biometric)
+
+ // JSON
+ implementation(libs.serialization.json)
+
+ // Coroutines
+ implementation(libs.coroutines.core)
+
+ // Testing
+ testImplementation(projects.features.wallet.test)
+ testImplementation(libs.test.junit)
+ testImplementation(libs.test.truth)
+ testImplementation(libs.coroutines.test)
+}
diff --git a/features/wallet/impl/proguard-rules.pro b/features/wallet/impl/proguard-rules.pro
new file mode 100644
index 0000000000..a520813972
--- /dev/null
+++ b/features/wallet/impl/proguard-rules.pro
@@ -0,0 +1,10 @@
+# Cardano client library uses reflection for CBOR serialization
+-keep class com.bloxbean.cardano.** { *; }
+-keepclassmembers class * {
+ @com.fasterxml.jackson.annotation.* *;
+}
+
+# Keep the Cardano model classes
+-keep class com.bloxbean.cardano.client.api.model.** { *; }
+-keep class com.bloxbean.cardano.client.backend.model.** { *; }
+-keep class com.bloxbean.cardano.client.transaction.spec.** { *; }
diff --git a/features/wallet/impl/src/main/AndroidManifest.xml b/features/wallet/impl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..0baf68a8a8
--- /dev/null
+++ b/features/wallet/impl/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/DefaultWalletEntryPoint.kt b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/DefaultWalletEntryPoint.kt
new file mode 100644
index 0000000000..07a8726c23
--- /dev/null
+++ b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/DefaultWalletEntryPoint.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package io.element.android.features.wallet.impl
+
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import dev.zacsweers.metro.ContributesBinding
+import io.element.android.features.wallet.api.WalletEntryPoint
+import io.element.android.libraries.architecture.createNode
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.UserId
+import javax.inject.Inject
+
+@ContributesBinding(SessionScope::class)
+class DefaultWalletEntryPoint @Inject constructor() : WalletEntryPoint {
+ class Builder(
+ private val parentNode: Node,
+ private val buildContext: BuildContext,
+ private val callback: WalletEntryPoint.Callback,
+ ) : WalletEntryPoint.Builder {
+ private var roomId: RoomId? = null
+ private var recipientUserId: UserId? = null
+ private var recipientAddress: String? = null
+ private var amount: String? = null
+
+ override fun setRoomId(roomId: RoomId): Builder {
+ this.roomId = roomId
+ return this
+ }
+
+ override fun setRecipientUserId(userId: UserId?): Builder {
+ this.recipientUserId = userId
+ return this
+ }
+
+ override fun setRecipientAddress(address: String?): Builder {
+ this.recipientAddress = address
+ return this
+ }
+
+ override fun setAmount(amount: String?): Builder {
+ this.amount = amount
+ return this
+ }
+
+ override fun build(): Node {
+ val inputs = PaymentFlowNode.Inputs(
+ roomId = requireNotNull(roomId) { "roomId must be set" },
+ recipientUserId = recipientUserId,
+ recipientAddress = recipientAddress,
+ amount = amount,
+ )
+ return parentNode.createNode(buildContext, listOf(inputs, callback))
+ }
+ }
+
+ override fun paymentFlowBuilder(
+ parentNode: Node,
+ buildContext: BuildContext,
+ callback: WalletEntryPoint.Callback,
+ ): WalletEntryPoint.Builder {
+ return Builder(parentNode, buildContext, callback)
+ }
+}
diff --git a/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/PaymentFlowNode.kt b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/PaymentFlowNode.kt
new file mode 100644
index 0000000000..29a39149c3
--- /dev/null
+++ b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/PaymentFlowNode.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package io.element.android.features.wallet.impl
+
+import android.os.Parcelable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import com.bumble.appyx.core.plugin.Plugin
+import com.bumble.appyx.navmodel.backstack.BackStack
+import dev.zacsweers.metro.Assisted
+import dev.zacsweers.metro.AssistedInject
+import io.element.android.annotations.ContributesNode
+import io.element.android.features.wallet.api.WalletEntryPoint
+import io.element.android.libraries.architecture.BackstackView
+import io.element.android.libraries.architecture.BaseFlowNode
+import io.element.android.libraries.architecture.NodeInputs
+import io.element.android.libraries.architecture.callback
+import io.element.android.libraries.architecture.createNode
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.UserId
+import kotlinx.parcelize.Parcelize
+
+@ContributesNode(SessionScope::class)
+@AssistedInject
+class PaymentFlowNode(
+ @Assisted buildContext: BuildContext,
+ @Assisted plugins: List,
+) : BaseFlowNode(
+ backstack = BackStack(
+ initialElement = NavTarget.Confirm,
+ savedStateMap = buildContext.savedStateMap,
+ ),
+ buildContext = buildContext,
+ plugins = plugins
+) {
+ @Parcelize
+ data class Inputs(
+ val roomId: RoomId,
+ val recipientUserId: UserId?,
+ val recipientAddress: String?,
+ val amount: String?,
+ ) : NodeInputs, Parcelable
+
+ private val callback: WalletEntryPoint.Callback = callback()
+ private val inputs: Inputs = plugins.filterIsInstance().first()
+
+ sealed interface NavTarget : Parcelable {
+ @Parcelize
+ data object Confirm : NavTarget
+
+ @Parcelize
+ data object Success : NavTarget
+ }
+
+ override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
+ return when (navTarget) {
+ NavTarget.Confirm -> {
+ // TODO: Implement PaymentConfirmNode
+ createNode(buildContext, listOf(PlaceholderNode.Inputs("Payment Confirm")))
+ }
+ NavTarget.Success -> {
+ // TODO: Implement PaymentSuccessNode
+ createNode(buildContext, listOf(PlaceholderNode.Inputs("Payment Success")))
+ }
+ }
+ }
+
+ @Composable
+ override fun View(modifier: Modifier) {
+ BackstackView()
+ }
+}
+
+/**
+ * Placeholder node for development. Will be replaced with actual implementations.
+ */
+@ContributesNode(SessionScope::class)
+@AssistedInject
+class PlaceholderNode(
+ @Assisted buildContext: BuildContext,
+ @Assisted plugins: List,
+) : Node(buildContext, plugins = plugins) {
+ @Parcelize
+ data class Inputs(val label: String) : NodeInputs, Parcelable
+
+ private val inputs: Inputs = plugins.filterIsInstance().first()
+
+ @Composable
+ override fun View(modifier: Modifier) {
+ Box(
+ modifier = modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(text = "Placeholder: ${inputs.label}")
+ }
+ }
+}
diff --git a/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/di/WalletModule.kt b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/di/WalletModule.kt
new file mode 100644
index 0000000000..66663b34e0
--- /dev/null
+++ b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/di/WalletModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package io.element.android.features.wallet.impl.di
+
+import dev.zacsweers.metro.AppScope
+import dev.zacsweers.metro.ContributesTo
+import dev.zacsweers.metro.ObjectFactory
+import dev.zacsweers.metro.Provides
+import dev.zacsweers.metro.SingleIn
+import kotlinx.serialization.json.Json
+
+/**
+ * DI module providing wallet-related dependencies.
+ */
+@ContributesTo(AppScope::class)
+@ObjectFactory
+interface WalletModule {
+ companion object {
+ @Provides
+ @SingleIn(AppScope::class)
+ fun provideWalletJson(): Json = Json {
+ ignoreUnknownKeys = true
+ isLenient = true
+ encodeDefaults = true
+ }
+ }
+}
diff --git a/features/wallet/impl/src/test/kotlin/io/element/android/features/wallet/impl/WalletStateTest.kt b/features/wallet/impl/src/test/kotlin/io/element/android/features/wallet/impl/WalletStateTest.kt
new file mode 100644
index 0000000000..b533ef333b
--- /dev/null
+++ b/features/wallet/impl/src/test/kotlin/io/element/android/features/wallet/impl/WalletStateTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package io.element.android.features.wallet.impl
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.features.wallet.api.WalletState
+import org.junit.Test
+
+class WalletStateTest {
+ @Test
+ fun `initial state has correct defaults`() {
+ val state = WalletState.Initial
+
+ assertThat(state.hasWallet).isFalse()
+ assertThat(state.address).isNull()
+ assertThat(state.balanceLovelace).isNull()
+ assertThat(state.balanceAda).isNull()
+ assertThat(state.isLoading).isTrue()
+ assertThat(state.error).isNull()
+ }
+
+ @Test
+ fun `state can be updated`() {
+ val state = WalletState(
+ hasWallet = true,
+ address = "addr1test",
+ balanceLovelace = 10_000_000L,
+ balanceAda = "10",
+ isLoading = false,
+ error = null,
+ )
+
+ assertThat(state.hasWallet).isTrue()
+ assertThat(state.address).isEqualTo("addr1test")
+ assertThat(state.balanceLovelace).isEqualTo(10_000_000L)
+ assertThat(state.balanceAda).isEqualTo("10")
+ assertThat(state.isLoading).isFalse()
+ }
+}
diff --git a/features/wallet/test/build.gradle.kts b/features/wallet/test/build.gradle.kts
new file mode 100644
index 0000000000..dbaef4b5b9
--- /dev/null
+++ b/features/wallet/test/build.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+plugins {
+ id("io.element.android-library")
+}
+
+android {
+ namespace = "io.element.android.features.wallet.test"
+}
+
+dependencies {
+ api(projects.features.wallet.api)
+ implementation(projects.tests.testutils)
+ implementation(libs.coroutines.core)
+}
diff --git a/features/wallet/test/src/main/AndroidManifest.xml b/features/wallet/test/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..0baf68a8a8
--- /dev/null
+++ b/features/wallet/test/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/features/wallet/test/src/main/kotlin/io/element/android/features/wallet/test/FakeWalletEntryPoint.kt b/features/wallet/test/src/main/kotlin/io/element/android/features/wallet/test/FakeWalletEntryPoint.kt
new file mode 100644
index 0000000000..1af86ff25f
--- /dev/null
+++ b/features/wallet/test/src/main/kotlin/io/element/android/features/wallet/test/FakeWalletEntryPoint.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2026 Sulkta Coop.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package io.element.android.features.wallet.test
+
+import com.bumble.appyx.core.modality.BuildContext
+import com.bumble.appyx.core.node.Node
+import io.element.android.features.wallet.api.WalletEntryPoint
+import io.element.android.libraries.matrix.api.core.RoomId
+import io.element.android.libraries.matrix.api.core.UserId
+import io.element.android.tests.testutils.lambda.lambdaError
+
+class FakeWalletEntryPoint : WalletEntryPoint {
+ class Builder : WalletEntryPoint.Builder {
+ override fun setRoomId(roomId: RoomId): Builder = this
+ override fun setRecipientUserId(userId: UserId?): Builder = this
+ override fun setRecipientAddress(address: String?): Builder = this
+ override fun setAmount(amount: String?): Builder = this
+ override fun build(): Node = lambdaError()
+ }
+
+ override fun paymentFlowBuilder(
+ parentNode: Node,
+ buildContext: BuildContext,
+ callback: WalletEntryPoint.Callback,
+ ): WalletEntryPoint.Builder {
+ return Builder()
+ }
+}