diff --git a/README.md b/README.md index 1738246..0ba5f4c 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,96 @@ -This is a Kotlin Multiplatform project targeting Android, Web, Desktop, Server. +# Multiplatform Crypto Wallet (SmartPesa) -* `/composeApp` is for code that will be shared across your Compose Multiplatform applications. - It contains several subfolders: - - `commonMain` is for code that’s common for all targets. - - Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. - For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, - `iosMain` would be the right folder for such calls. +## Overview +**SmartPesa** is an innovative, Kotlin Multiplatform (KMP)-based open-source project designed to revolutionize cryptocurrency wallet management across multiple platforms. By leveraging advanced KMP technology, SmartPesa ensures a seamless and secure experience for users, enabling effortless transactions by simply scanning a QR code. -* `/server` is for the Ktor server application. +### Key Features +- **Error-Free Transactions**: Avoid wallet address errors with QR code-based transactions. +- **Enhanced Security**: Mitigate wallet poisoning attacks in crypto markets. +- **User-Friendly**: Designed for both crypto newcomers and seasoned users. +- **Financial Inclusion**: Focused on empowering unbanked and underbanked populations, particularly in Sub-Saharan Africa. -* `/shared` is for the code that will be shared between all targets in the project. - The most important subfolder is `commonMain`. If preferred, you can add code to the platform-specific folders here too. +### Components +SmartPesa comprises: +1. A **Ktor-based server** integrating with **Circle.io** for: + - Programmable wallets + - Transaction processing + - Wallet management +2. A **Kotlin Multiplatform Android application** for seamless mobile wallet management. +3. A **desktop application** offering extended functionality and accessibility. +--- -Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html), -[Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/#compose-multiplatform), -[Kotlin/Wasm](https://kotl.in/wasm/)… +## Ktor Server Setup -We would appreciate your feedback on Compose/Web and Kotlin/Wasm in the public Slack channel [#compose-web](https://slack-chats.kotlinlang.org/c/compose-web). -If you face any issues, please report them on [GitHub](https://github.com/JetBrains/compose-multiplatform/issues). +### Local Environment Setup +To configure the Ktor server locally: + +1. Create a `.env` file in the root directory of the server module and add the following configurations: + +```env +# Circle Programmable Wallets Configuration +API_KEY=TEST_API_KEY # Replace with Circle's test API key +ENTITY_SECRETE=circle_entity_secret + +# JWT Configuration +JWT_SECRETE=your_own_jwt_secret + +# MongoDB Configuration +MONGODBUSERNAME=your_mongodb_username +MONGODBPASSWORD=your_mongodb_password +DATABASENAME=your_mongodb_database_name +``` +2. Add the .env file variables to your development environment (e.g., IntelliJ IDEA or Android Studio). +3. Run the server application. The server will start on port 8081. +4. Create mongodb database from mongodb atlas for free and quick setup or modify the database url build to work with your local mongodb. + +## Docker Setup +You can also deploy the server using Docker for faster and more consistent setup. + +Docker Compose File +Create a docker-compose.yml file with the following content: +```yaml +version: '3.8' + +services: + smartpesa: + image: pascarl/smartpesa:latest + container_name: smartpesa-app + ports: + - "8080:8081" + env_file: + - .env + restart: unless-stopped + networks: + - smartpesa-network + volumes: + - ./app_data:/app_data + +networks: + smartpesa-network: + driver: bridge + +``` +## Live Test +To test the server, a live deployment has been made on an AWS EC2 instance for free testing. +You can access the server using the following base URL + +Base URL: http://52.57.41.193 + +You can also get the api documentation [here](https://documenter.getpostman.com/view/27366427/2sAYJ7hKUB) + +## CI/CD +To save time managing deployments i wanted to automate that part so with docker, +docker hub and github actions i was able to automate that part. This saved me lots +of time a nd helped me reduce deployments errors and bugs. +How ever i faced challenge when i tried to use ktor in build docker plugin. for some +reason i was anable to use the docker plugin to build and genrate the docker images. but the funny thing is +i only experience this problem withing kmp project but as ktor stand alone everything was working well +so i revolted to building my own dockerfile which you can find it at the root of the project. + +I tweeted the issue on x maybet @jetbrains team might follow it up. here is the [tweet](https://x.com/pasaka254/status/1867994507658379280) + +Then the github workflows are in the .github directory in the root folder. you will find build-image.yml +for building the docker image and publishing it to dockerhub, and deploy-to-ec2.yml for deploying the image +to aws ec2 instance. make sure you server is configured with docker and docker-compose. -You can open the web application by running the `:composeApp:wasmJsBrowserDevelopmentRun` Gradle task.# MultipltformCryptoWallet diff --git a/composeApp/src/commonMain/kotlin/org/example/di/viewModelModule.kt b/composeApp/src/commonMain/kotlin/org/example/di/viewModelModule.kt index d9ea9c4..3257c91 100644 --- a/composeApp/src/commonMain/kotlin/org/example/di/viewModelModule.kt +++ b/composeApp/src/commonMain/kotlin/org/example/di/viewModelModule.kt @@ -1,7 +1,5 @@ package org.example.di -import androidx.lifecycle.viewmodel.compose.viewModel -import org.example.presentation.screens.AuthScreen.AuthScreenViewModel import org.example.presentation.screens.dashboardScreen.DashboardScreenViewModel import org.example.presentation.screens.onBoarding.OnBoardingScreenViewModel import org.example.presentation.screens.splashScreen.SplashScreenViewModel @@ -10,7 +8,6 @@ import org.koin.dsl.module val viewModelsModule = module { single{SplashScreenViewModel(get())} - single{AuthScreenViewModel()} single{OnBoardingScreenViewModel(get(), get())} single{TransferScreenViewModel(get())} single{DashboardScreenViewModel(get(), get(), get())} diff --git a/composeApp/src/commonMain/kotlin/org/example/presentation/navigation/NavGraph.kt b/composeApp/src/commonMain/kotlin/org/example/presentation/navigation/NavGraph.kt index 8b8d4db..d26b98a 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presentation/navigation/NavGraph.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presentation/navigation/NavGraph.kt @@ -5,7 +5,6 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.toRoute -import org.example.presentation.screens.AuthScreen.AuthScreen import org.example.presentation.screens.QrCodeScannerScreen import org.example.presentation.screens.dashboardScreen.DashBoardScreen import org.example.presentation.screens.explorer.ExplorerScreen @@ -54,9 +53,6 @@ fun NavGraph( } ) } - composable { - AuthScreen() - } composable { OnBoardingScreen( onNavigateToDashboard = { diff --git a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/AuthScreen/AuthScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presentation/screens/AuthScreen/AuthScreen.kt deleted file mode 100644 index 0528a2d..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/AuthScreen/AuthScreen.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.example.presentation.screens.AuthScreen - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import org.koin.compose.viewmodel.koinViewModel - -@Composable -fun AuthScreen() { - val viewModel: AuthScreenViewModel = koinViewModel() - -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/AuthScreen/AuthScreenViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/presentation/screens/AuthScreen/AuthScreenViewModel.kt deleted file mode 100644 index 26a7702..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/AuthScreen/AuthScreenViewModel.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.example.presentation.screens.AuthScreen - -import androidx.lifecycle.ViewModel - -class AuthScreenViewModel(): ViewModel() { - -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/dashboardScreen/DashboardScreenViewModel.kt b/composeApp/src/commonMain/kotlin/org/example/presentation/screens/dashboardScreen/DashboardScreenViewModel.kt index 57d42f6..62fc75c 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/dashboardScreen/DashboardScreenViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presentation/screens/dashboardScreen/DashboardScreenViewModel.kt @@ -124,24 +124,7 @@ class DashboardScreenViewModel( transactionsState = transactionsState.copy( isLoading = false, isSuccessful = true, - transactions = result.data?.map{transaction-> - var transactionData = transaction - walletState.wallets.forEach{wallet-> - transactionData = if (wallet.address == transaction.senderAddress){ - transaction.copy( - transactionType = "OUT" - ) - } - else if (wallet.address == transaction.receiverAddress){ - transaction.copy( - transactionType = "IN" - ) - } - else transaction - } - transactionData - - } ?: emptyList() + transactions = result.data ?: emptyList() ) println("Success: ${result.data}") } diff --git a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/onBoarding/OnBoardingScreen.kt b/composeApp/src/commonMain/kotlin/org/example/presentation/screens/onBoarding/OnBoardingScreen.kt index 9043fd7..17162c3 100644 --- a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/onBoarding/OnBoardingScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/presentation/screens/onBoarding/OnBoardingScreen.kt @@ -28,7 +28,6 @@ import kotlinx.coroutines.launch import org.example.domain.model.WalletSecrete import org.example.presentation.screens.onBoarding.components.CreateWalletBottomSheetContent import org.example.presentation.screens.onBoarding.components.ImportWalletBottomSheet -import org.example.presentation.screens.onBoarding.components.OnBoardingScreenUpperSection @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -100,14 +99,6 @@ fun OnBoardingScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top ) { - OnBoardingScreenUpperSection( - actionRegister = { - - }, - actionSignIn = { - - } - ) OnBoardingScreenMiddleSection( items = viewModel.onBoardingData ) diff --git a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/onBoarding/components/OnBoardingScreenUpperSection.kt b/composeApp/src/commonMain/kotlin/org/example/presentation/screens/onBoarding/components/OnBoardingScreenUpperSection.kt deleted file mode 100644 index 319f408..0000000 --- a/composeApp/src/commonMain/kotlin/org/example/presentation/screens/onBoarding/components/OnBoardingScreenUpperSection.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.example.presentation.screens.onBoarding.components - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TextButton - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.unit.dp - -@Composable -fun OnBoardingScreenUpperSection( - actionRegister: () -> Unit, - actionSignIn: () -> Unit -) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(top = 16.dp) - ) { - TextButton( - onClick = actionRegister, - colors = ButtonDefaults.textButtonColors( - containerColor = Color.Transparent - ) - - ) { - Text( - text = "Register", - color = MaterialTheme.colorScheme.primary, - ) - } - TextButton( - onClick = actionSignIn, - colors = ButtonDefaults.textButtonColors( - containerColor = Color.Transparent - ) - - ) { - Text( - text = "Sign In", - color = MaterialTheme.colorScheme.tertiary, - textDecoration = TextDecoration.Underline - ) - } - } -} \ No newline at end of file diff --git a/screenshots/generating crypto payment request.mp4 b/screenshots/generating crypto payment request.mp4 new file mode 100644 index 0000000..d866777 Binary files /dev/null and b/screenshots/generating crypto payment request.mp4 differ diff --git a/screenshots/qr-code.png b/screenshots/qr-code.png new file mode 100644 index 0000000..5255bd7 Binary files /dev/null and b/screenshots/qr-code.png differ diff --git a/server/src/main/kotlin/org/example/data/repository/TransactionRepositoryImpl.kt b/server/src/main/kotlin/org/example/data/repository/TransactionRepositoryImpl.kt index cb22149..b34aa0e 100644 --- a/server/src/main/kotlin/org/example/data/repository/TransactionRepositoryImpl.kt +++ b/server/src/main/kotlin/org/example/data/repository/TransactionRepositoryImpl.kt @@ -131,7 +131,7 @@ class TransactionRepositoryImpl( * */ val transaction = Transaction( senderId = "", - walletId = wallet.walletId, + walletId = wallet.id, tokenId = walletBalance.token.id, amounts = listOf(body.amount.toString()), senderAddress = wallet.address, diff --git a/server/src/main/resources/application.conf b/server/src/main/resources/application.conf index a9a138e..bfbd22f 100644 --- a/server/src/main/resources/application.conf +++ b/server/src/main/resources/application.conf @@ -1,4 +1,5 @@ -ktor{ +ktor { + server { logger = "crypto-wallet" apiVersion = "/api/v1"