├── backend ├── test │ ├── .gitkeep │ └── Camps.test.js ├── migrations │ ├── 3_deploy_camps.js │ ├── 1_initial_migration.js │ └── 2_deploy_ctv.js ├── contracts │ ├── CollectiveToken.sol │ ├── Migrations.sol │ ├── Camps.sol │ └── ERC.sol ├── server.js ├── package.json ├── routes │ ├── auth │ │ └── UserAuth.js │ ├── collective │ │ ├── Collective.js │ │ └── Collab.js │ ├── router.js │ └── blockchain │ │ ├── CollectiveToken.js │ │ └── Camps.js ├── models │ ├── userAuthModel.js │ ├── userDetailsModel.js │ ├── collabModel.js │ └── campDetailsModel.js ├── truffle-config.js ├── middleware │ └── authHelper.js └── controllers │ ├── authControllers │ └── authController.js │ └── collectiveControllers │ └── collectiveController.js ├── .gitignore ├── frontend ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── .gitignore ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ └── Icon-512.png │ ├── manifest.json │ └── index.html ├── assets │ ├── images │ │ ├── pay.png │ │ ├── Logo.png │ │ ├── Create.png │ │ ├── LogoCrop.png │ │ ├── loading.png │ │ ├── notfound.png │ │ ├── search.png │ │ ├── CreateCamp.png │ │ ├── InvestInCamp.png │ │ ├── LauncherIcon.png │ │ ├── LoginScreen.png │ │ ├── LogoNoPadding.png │ │ ├── SignUpScreen.png │ │ ├── SupportEmail.png │ │ ├── placeholder.jpg │ │ ├── userDetails.png │ │ └── RegisterScreen.png │ ├── fonts │ │ └── AvenirNextRegular.ttf │ └── gpay.json ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── launcher_icon.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── launcher_icon.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── launcher_icon.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── launcher_icon.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── launcher_icon.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── collective │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── README.md ├── lib │ ├── widgets │ │ ├── keepAlivePage.dart │ │ ├── appBarGoBack.dart │ │ ├── SplashScreenWidget.dart │ │ ├── CampScreenWidgets │ │ │ ├── CollabWidget.dart │ │ │ ├── MoreCampDataWidget.dart │ │ │ └── CampCollaboratorsWidget.dart │ │ └── CampListViewWidget.dart │ ├── main.dart │ └── screens │ │ ├── CreateCampHomeScreen.dart │ │ ├── CollabMainScreen.dart │ │ ├── UsersCampScreen.dart │ │ ├── UserInvestmentScreen.dart │ │ ├── UsersCollabScreen.dart │ │ ├── HomeScreen.dart │ │ └── LoginScreen.dart ├── .gitignore ├── test │ └── widget_test.dart └── pubspec.yaml ├── assets ├── Logo.png ├── node.png ├── collab.png ├── 4204966.png ├── Flutter.png ├── ethereum.png ├── mongodb.png ├── Collective.png ├── Investment.png ├── Login Screen.png ├── systemdesign.PNG ├── Register Screen.png ├── ScreenShots │ ├── Home.png │ ├── Angels.png │ ├── BuyCTV.png │ ├── Collab.png │ ├── Cover.png │ ├── Invest.png │ ├── Login.png │ ├── MoreTab.png │ ├── Search.png │ ├── Support.png │ ├── CollabTab.png │ ├── CreateJob.png │ ├── Register.png │ ├── SideMenu.png │ ├── UserCamp.png │ ├── CampScreen.png │ ├── CreateHome.png │ ├── UserDetails.png │ ├── Collaborators.png │ └── UserInvestmnet.png ├── CollectivePitchDeck.ppt ├── CollectCrowdfundFlow.png ├── CollectCollaborationFlow.png ├── Collective Splash Screen.png ├── undraw_make_it_rain_iwk4.png ├── Collective system diagram.png ├── undraw_Credit_card_re_blml.png ├── undraw_Mobile_pay_re_sjb8.png ├── 634_SagarParker_Collective.docx ├── aeaf457175294cb482e5af2b36483e0f.png ├── business-partners-handshake_74855-5222.jpg ├── main-qimg-4640c6c0b41eccacb1be3c6e125383b0.jpg ├── connected-world-concept-illustration_114360-3027.jpg └── happy-woman-chatting-with-friends-online_74855-14073.jpg └── README.md /backend/test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/backend/node_modules 2 | **/backend/.env -------------------------------------------------------------------------------- /frontend/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /frontend/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /assets/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Logo.png -------------------------------------------------------------------------------- /assets/node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/node.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /assets/collab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/collab.png -------------------------------------------------------------------------------- /assets/4204966.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/4204966.png -------------------------------------------------------------------------------- /assets/Flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Flutter.png -------------------------------------------------------------------------------- /assets/ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ethereum.png -------------------------------------------------------------------------------- /assets/mongodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/mongodb.png -------------------------------------------------------------------------------- /assets/Collective.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Collective.png -------------------------------------------------------------------------------- /assets/Investment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Investment.png -------------------------------------------------------------------------------- /assets/Login Screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Login Screen.png -------------------------------------------------------------------------------- /assets/systemdesign.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/systemdesign.PNG -------------------------------------------------------------------------------- /frontend/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/web/favicon.png -------------------------------------------------------------------------------- /assets/Register Screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Register Screen.png -------------------------------------------------------------------------------- /assets/ScreenShots/Home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Home.png -------------------------------------------------------------------------------- /assets/CollectivePitchDeck.ppt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/CollectivePitchDeck.ppt -------------------------------------------------------------------------------- /assets/ScreenShots/Angels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Angels.png -------------------------------------------------------------------------------- /assets/ScreenShots/BuyCTV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/BuyCTV.png -------------------------------------------------------------------------------- /assets/ScreenShots/Collab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Collab.png -------------------------------------------------------------------------------- /assets/ScreenShots/Cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Cover.png -------------------------------------------------------------------------------- /assets/ScreenShots/Invest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Invest.png -------------------------------------------------------------------------------- /assets/ScreenShots/Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Login.png -------------------------------------------------------------------------------- /assets/ScreenShots/MoreTab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/MoreTab.png -------------------------------------------------------------------------------- /assets/ScreenShots/Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Search.png -------------------------------------------------------------------------------- /assets/ScreenShots/Support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Support.png -------------------------------------------------------------------------------- /frontend/assets/images/pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/pay.png -------------------------------------------------------------------------------- /assets/CollectCrowdfundFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/CollectCrowdfundFlow.png -------------------------------------------------------------------------------- /assets/ScreenShots/CollabTab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/CollabTab.png -------------------------------------------------------------------------------- /assets/ScreenShots/CreateJob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/CreateJob.png -------------------------------------------------------------------------------- /assets/ScreenShots/Register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Register.png -------------------------------------------------------------------------------- /assets/ScreenShots/SideMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/SideMenu.png -------------------------------------------------------------------------------- /assets/ScreenShots/UserCamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/UserCamp.png -------------------------------------------------------------------------------- /frontend/assets/images/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/Logo.png -------------------------------------------------------------------------------- /frontend/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/web/icons/Icon-192.png -------------------------------------------------------------------------------- /frontend/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/CollectCollaborationFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/CollectCollaborationFlow.png -------------------------------------------------------------------------------- /assets/Collective Splash Screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Collective Splash Screen.png -------------------------------------------------------------------------------- /assets/ScreenShots/CampScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/CampScreen.png -------------------------------------------------------------------------------- /assets/ScreenShots/CreateHome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/CreateHome.png -------------------------------------------------------------------------------- /assets/ScreenShots/UserDetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/UserDetails.png -------------------------------------------------------------------------------- /assets/undraw_make_it_rain_iwk4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/undraw_make_it_rain_iwk4.png -------------------------------------------------------------------------------- /frontend/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /frontend/assets/images/Create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/Create.png -------------------------------------------------------------------------------- /frontend/assets/images/LogoCrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/LogoCrop.png -------------------------------------------------------------------------------- /frontend/assets/images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/loading.png -------------------------------------------------------------------------------- /frontend/assets/images/notfound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/notfound.png -------------------------------------------------------------------------------- /frontend/assets/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/search.png -------------------------------------------------------------------------------- /assets/Collective system diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/Collective system diagram.png -------------------------------------------------------------------------------- /assets/ScreenShots/Collaborators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/Collaborators.png -------------------------------------------------------------------------------- /assets/ScreenShots/UserInvestmnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/ScreenShots/UserInvestmnet.png -------------------------------------------------------------------------------- /assets/undraw_Credit_card_re_blml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/undraw_Credit_card_re_blml.png -------------------------------------------------------------------------------- /assets/undraw_Mobile_pay_re_sjb8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/undraw_Mobile_pay_re_sjb8.png -------------------------------------------------------------------------------- /frontend/assets/images/CreateCamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/CreateCamp.png -------------------------------------------------------------------------------- /assets/634_SagarParker_Collective.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/634_SagarParker_Collective.docx -------------------------------------------------------------------------------- /frontend/assets/images/InvestInCamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/InvestInCamp.png -------------------------------------------------------------------------------- /frontend/assets/images/LauncherIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/LauncherIcon.png -------------------------------------------------------------------------------- /frontend/assets/images/LoginScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/LoginScreen.png -------------------------------------------------------------------------------- /frontend/assets/images/LogoNoPadding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/LogoNoPadding.png -------------------------------------------------------------------------------- /frontend/assets/images/SignUpScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/SignUpScreen.png -------------------------------------------------------------------------------- /frontend/assets/images/SupportEmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/SupportEmail.png -------------------------------------------------------------------------------- /frontend/assets/images/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/placeholder.jpg -------------------------------------------------------------------------------- /frontend/assets/images/userDetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/userDetails.png -------------------------------------------------------------------------------- /frontend/assets/images/RegisterScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/images/RegisterScreen.png -------------------------------------------------------------------------------- /assets/aeaf457175294cb482e5af2b36483e0f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/aeaf457175294cb482e5af2b36483e0f.png -------------------------------------------------------------------------------- /frontend/assets/fonts/AvenirNextRegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/assets/fonts/AvenirNextRegular.ttf -------------------------------------------------------------------------------- /assets/business-partners-handshake_74855-5222.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/business-partners-handshake_74855-5222.jpg -------------------------------------------------------------------------------- /assets/main-qimg-4640c6c0b41eccacb1be3c6e125383b0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/main-qimg-4640c6c0b41eccacb1be3c6e125383b0.jpg -------------------------------------------------------------------------------- /backend/migrations/3_deploy_camps.js: -------------------------------------------------------------------------------- 1 | var Camps = artifacts.require('./Camps.sol'); 2 | 3 | module.exports = function(deployer){ 4 | deployer.deploy(Camps); 5 | } -------------------------------------------------------------------------------- /assets/connected-world-concept-illustration_114360-3027.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/connected-world-concept-illustration_114360-3027.jpg -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/happy-woman-chatting-with-friends-online_74855-14073.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/assets/happy-woman-chatting-with-friends-online_74855-14073.jpg -------------------------------------------------------------------------------- /backend/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /backend/migrations/2_deploy_ctv.js: -------------------------------------------------------------------------------- 1 | var CollectiveToken = artifacts.require("./CollectiveToken.sol"); 2 | 3 | module.exports = function(deployer){ 4 | deployer.deploy(CollectiveToken); 5 | } -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagarparker/Collective/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/kotlin/com/example/collective/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.collective 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /backend/contracts/CollectiveToken.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./ERC.sol"; 5 | 6 | contract CollectiveToken is ERC{ 7 | constructor() ERC("CollectiveToken","CTV"){ 8 | _mint(msg.sender, 1000000000000); 9 | } 10 | 11 | function decimals() public view virtual override returns (uint8){ 12 | return 0; 13 | } 14 | } -------------------------------------------------------------------------------- /frontend/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /frontend/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # collective 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /frontend/lib/widgets/keepAlivePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class KeepAlivePage extends StatefulWidget { 4 | KeepAlivePage({ 5 | Key key, 6 | @required this.child, 7 | }) : super(key: key); 8 | 9 | final Widget child; 10 | 11 | @override 12 | _KeepAlivePageState createState() => _KeepAlivePageState(); 13 | } 14 | 15 | class _KeepAlivePageState extends State 16 | with AutomaticKeepAliveClientMixin { 17 | @override 18 | Widget build(BuildContext context) { 19 | super.build(context); 20 | 21 | return widget.child; 22 | } 23 | 24 | @override 25 | bool get wantKeepAlive => true; 26 | } 27 | -------------------------------------------------------------------------------- /frontend/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collective", 3 | "short_name": "collective", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /frontend/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /frontend/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.4.20' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./routes/router"); 2 | const http = require("http"); 3 | const mongoose = require("mongoose"); 4 | 5 | 6 | // MongoDB connection 7 | 8 | mongoose.connect("mongodb+srv://sagarparker:hihellohi8@sagarparker.ccy2e.mongodb.net/collectiveDB?retryWrites=true&w=majority", 9 | { useNewUrlParser: true, 10 | useCreateIndex:true, 11 | useFindAndModify:false, 12 | useUnifiedTopology: true 13 | }).then(() => { 14 | console.log("Connected to MongoDB"); 15 | }).catch(err => { 16 | console.log("Error connecting to Collective Database",err.message); 17 | }); 18 | 19 | 20 | 21 | // Server setup 22 | 23 | const server = http.createServer(app); 24 | 25 | server.listen(8080, () => { 26 | console.log("Collective server started on port 8080"); 27 | }); 28 | -------------------------------------------------------------------------------- /frontend/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend_collective", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "mocha" 11 | }, 12 | "author": "Sagar Parker", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@openzeppelin/contracts": "^4.1.0", 16 | "axios": "^0.21.1", 17 | "bcryptjs": "^2.4.3", 18 | "cors": "^2.8.5", 19 | "crypto-js": "^4.0.0", 20 | "dotenv": "^9.0.1", 21 | "express": "^4.17.1", 22 | "express-validator": "^6.11.1", 23 | "ganache-cli": "^6.12.2", 24 | "http": "0.0.1-security", 25 | "jsonwebtoken": "^8.5.1", 26 | "lodash": "^4.17.21", 27 | "mocha": "^9.0.3", 28 | "moment-timezone": "^0.5.33", 29 | "mongoose": "^5.12.7", 30 | "multer": "^1.4.2", 31 | "nodemailer": "^6.6.3", 32 | "nodemon": "^2.0.15", 33 | "path": "^0.12.7", 34 | "solc": "^0.8.7-fixed", 35 | "truffle-hdwallet-provider-privkey": "^0.3.0", 36 | "uuid": "^8.3.2", 37 | "web3": "^1.3.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/assets/gpay.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google_pay", 3 | "data": { 4 | "environment": "TEST", 5 | "apiVersion": 2, 6 | "apiVersionMinor": 0, 7 | "allowedPaymentMethods": [ 8 | { 9 | "type": "CARD", 10 | "tokenizationSpecification": { 11 | "type": "PAYMENT_GATEWAY", 12 | "parameters": { 13 | "gateway": "example", 14 | "gatewayMerchantId": "gatewayMerchantId" 15 | } 16 | }, 17 | "parameters": { 18 | "allowedCardNetworks": ["VISA", "MASTERCARD"], 19 | "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], 20 | "billingAddressRequired": true, 21 | "billingAddressParameters": { 22 | "format": "FULL", 23 | "phoneNumberRequired": true 24 | } 25 | } 26 | } 27 | ], 28 | "merchantInfo": { 29 | "merchantId": "01234567890123456789", 30 | "merchantName": "Example Merchant Name" 31 | }, 32 | "transactionInfo": { 33 | "countryCode": "US", 34 | "currencyCode": "USD" 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /backend/routes/auth/UserAuth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const userAuthController = require('../../controllers/authControllers/authController'); 4 | 5 | const { body } = require("express-validator"); 6 | const {validateApiSecret,isAuthenticated} = require("../../middleware/authHelper"); 7 | 8 | 9 | // REGISTER new user on collective 10 | 11 | router.post('/userRegister',[ 12 | body('email').isEmail(), 13 | body('email').not().isEmpty(), 14 | body('username').not().isEmpty(), 15 | body('password').not().isEmpty()], 16 | validateApiSecret, 17 | userAuthController.userRegister 18 | ); 19 | 20 | 21 | // Login api for Collective 22 | 23 | router.post('/userLogin',[ 24 | body('email_username').not().isEmpty(), 25 | body('password').not().isEmpty()], 26 | validateApiSecret, 27 | userAuthController.userLogin 28 | ); 29 | 30 | 31 | //Verify if the user is a registered user with a valid token. 32 | 33 | router.post('/verifyUser', 34 | validateApiSecret, 35 | isAuthenticated, 36 | userAuthController.userVerify 37 | ); 38 | 39 | 40 | module.exports = router; 41 | -------------------------------------------------------------------------------- /frontend/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:collective/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /backend/routes/collective/Collective.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { body } = require("express-validator"); 5 | const { validateApiSecret,isAuthenticated }=require("../../middleware/authHelper"); 6 | require('dotenv').config(); 7 | 8 | const collectiveController = require('../../controllers/collectiveControllers/collectiveController'); 9 | 10 | 11 | // Fetch user data 12 | 13 | router.post('/getUserDetails', 14 | validateApiSecret, 15 | isAuthenticated, 16 | collectiveController.getUserDetails 17 | ); 18 | 19 | 20 | // Withdraw camp amount raised 21 | 22 | router.post('/withdrawAmount', 23 | validateApiSecret, 24 | isAuthenticated, 25 | [body('owner_address').not().isEmpty(), 26 | body('owner_private_key').not().isEmpty(), 27 | body('amount').not().isEmpty()], 28 | collectiveController.withdrawAmount 29 | ); 30 | 31 | 32 | 33 | router.post('/supportEmail', 34 | validateApiSecret, 35 | isAuthenticated, 36 | [ 37 | body('email_subject').not().isEmpty(), 38 | body('email_message').not().isEmpty() 39 | ], 40 | collectiveController.supportEmail 41 | ); 42 | 43 | 44 | 45 | 46 | module.exports = router; -------------------------------------------------------------------------------- /backend/models/userAuthModel.js: -------------------------------------------------------------------------------- 1 | var mongoose = require("mongoose"); 2 | 3 | var userAuthSchema = new mongoose.Schema({ 4 | email : { 5 | type:String, 6 | unique:true 7 | }, 8 | username : { 9 | type:String, 10 | unique:true 11 | }, 12 | password : { 13 | type:String, 14 | required:true 15 | }, 16 | timestamp : { 17 | type:String, 18 | required:true 19 | }, 20 | eth_address : { 21 | type:String, 22 | required:true 23 | }, 24 | eth_private_key : { 25 | type:String, 26 | required:true 27 | } 28 | }); 29 | 30 | module.exports = mongoose.model("UserAuth",userAuthSchema); 31 | -------------------------------------------------------------------------------- /frontend/lib/widgets/appBarGoBack.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppBarGoBack extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return AppBar( 7 | backgroundColor: Colors.white, 8 | leading: IconButton( 9 | icon: const Icon(Icons.navigate_before), 10 | color: Colors.black, 11 | iconSize: 25, 12 | onPressed: () { 13 | Navigator.of(context).pop(); 14 | }, 15 | ), 16 | elevation: 2, 17 | centerTitle: true, 18 | title: Container( 19 | width: 155, 20 | child: Row( 21 | children: [ 22 | Image.asset( 23 | 'assets/images/Logo.png', 24 | width: 32, 25 | height: 32, 26 | ), 27 | Padding( 28 | padding: const EdgeInsets.only(top: 4.5, left: 3), 29 | child: Text( 30 | 'Collective', 31 | style: TextStyle( 32 | color: Theme.of(context).primaryColor, 33 | fontWeight: FontWeight.bold, 34 | fontSize: 25, 35 | ), 36 | ), 37 | ), 38 | ], 39 | ), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /backend/models/userDetailsModel.js: -------------------------------------------------------------------------------- 1 | var mongoose = require("mongoose"); 2 | 3 | var userDetailsSchema = new mongoose.Schema({ 4 | email : { 5 | type:String, 6 | unique:true 7 | }, 8 | username : { 9 | type:String, 10 | unique:true 11 | }, 12 | profile_picture : { 13 | type:String, 14 | default:'NA' 15 | }, 16 | camps_owned : [{ 17 | type:mongoose.Schema.Types.ObjectId, 18 | ref:'CampDetails' 19 | }], 20 | camps_invested : [{ 21 | type:mongoose.Schema.Types.ObjectId, 22 | ref:'CampDetails' 23 | }], 24 | camps_collaborated : [{ 25 | type:mongoose.Schema.Types.ObjectId, 26 | ref:'Collab' 27 | }], 28 | }); 29 | 30 | module.exports = mongoose.model("UserDetails",userDetailsSchema); 31 | -------------------------------------------------------------------------------- /backend/models/collabModel.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | var collabSchema = new mongoose.Schema({ 4 | 5 | campID : { 6 | type:String, 7 | required:true, 8 | default:'NA' 9 | }, 10 | campOwnerUsername : { 11 | type:String, 12 | default:'NA' 13 | }, 14 | collabTitle : { 15 | type:String, 16 | default:'NA' 17 | }, 18 | collabAmount : { 19 | type:Number, 20 | default:0 21 | }, 22 | collabDescription : { 23 | type:Array, 24 | default:[] 25 | }, 26 | collaboratorSearchActive : { 27 | type:Boolean, 28 | default:true 29 | }, 30 | collabRequests : [{ 31 | username : String, 32 | address : String, 33 | }] 34 | }); 35 | 36 | module.exports = mongoose.model("Collab",collabSchema) 37 | -------------------------------------------------------------------------------- /backend/truffle-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const HDWalletProvider = require('truffle-hdwallet-provider-privkey'); 3 | const privateKeys = process.env.privatekey_1; 4 | 5 | module.exports = { 6 | 7 | networks: { 8 | 9 | development: { 10 | host: "127.0.0.1", // Localhost (default: none) 11 | port: 7545, // Standard Ethereum port (default: none) 12 | network_id: "*", // Any network (default: none) 13 | }, 14 | ropsten: { 15 | provider: function() { 16 | return new HDWalletProvider( 17 | privateKeys.split(','), 18 | `https://ropsten.infura.io/v3/7a0de82adffe468d8f3c1e2183b37c39`// Url to an Ethereum Node 19 | ) 20 | }, 21 | network_id: 3, 22 | gas: 5500000, 23 | confirmations: 2, 24 | timeoutBlocks: 200, 25 | skipDryRun: true 26 | } 27 | 28 | }, 29 | 30 | // Set default mocha options here, use special reporters etc. 31 | mocha: { 32 | timeout: 100000 33 | }, 34 | 35 | // Configure your compilers 36 | compilers: { 37 | solc: { 38 | version: "0.8.0", // Fetch exact version from solc-bin (default: truffle's version) 39 | docker: false, // Use "0.5.1" you've installed locally with docker (default: false) 40 | settings: { // See the solidity docs for advice about optimization and evmVersion 41 | optimizer: { 42 | enabled: false, 43 | runs: 200 44 | }, 45 | evmVersion: "byzantium" 46 | } 47 | }, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /frontend/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | collective 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | collective 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /backend/routes/router.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const cors = require("cors"); 4 | const path = require('path'); 5 | const moment = require('moment-timezone'); 6 | 7 | 8 | const CollectiveToken = require('./blockchain/CollectiveToken'); 9 | const Camps = require('./blockchain/Camps'); 10 | const Auth = require('./auth/UserAuth'); 11 | const Collective = require('./collective/Collective'); 12 | const Collab = require('./collective/Collab'); 13 | 14 | // Timezone setup 15 | 16 | moment.tz.setDefault("Asia/Kolkata"); 17 | 18 | 19 | //EXPRESS PRESET 20 | 21 | app.use(express.json()); 22 | 23 | app.use(express.urlencoded({ 24 | extended: true 25 | })); 26 | 27 | app.use(express.text({ limit: '200mb' })); 28 | 29 | 30 | // EXPRESS STATIC FILE SERVER 31 | 32 | // Media uploaded by the users 33 | app.use('/media',express.static(path.join(__dirname,'/../../../CollectiveMedia'))) 34 | 35 | 36 | // CORS PRESETS 37 | 38 | app.use(cors()); 39 | 40 | app.use(express.static("docs")); 41 | app.use((req, res, next) => { 42 | res.setHeader('Access-Control-Allow-Origin', '*'); 43 | res.setHeader('Access-Control-Allow-Headers', 'Origin,X-Requested-With,Content-Type,Accept,Authorization'); 44 | res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PATCH,DELETE,PUT,OPTIONS'); 45 | if (req.method === 'OPTIONS') { 46 | res.sendStatus(200); 47 | } else { 48 | next(); 49 | } 50 | }); 51 | 52 | 53 | // User auth 54 | 55 | app.use('/api',Auth); 56 | 57 | 58 | // Blockchain APIs 59 | 60 | app.use('/api',CollectiveToken); 61 | 62 | 63 | // Camps APIs 64 | 65 | app.use('/api',Camps); 66 | 67 | 68 | // Collective APIs 69 | 70 | app.use('/api',Collective); 71 | 72 | 73 | // Collab APIs 74 | 75 | app.use('/api',Collab); 76 | 77 | 78 | 79 | module.exports = app; -------------------------------------------------------------------------------- /frontend/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.example.collective" 38 | minSdkVersion 19 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /backend/middleware/authHelper.js: -------------------------------------------------------------------------------- 1 | const UserDetailsModel = require("../models/userDetailsModel"); 2 | const jwt = require("jsonwebtoken"); 3 | const bcrypt = require("bcryptjs"); 4 | require("dotenv").config(); 5 | 6 | 7 | //Generate token 8 | 9 | exports.generateToken = (payload) => { 10 | return jwt.sign(payload, process.env.TOKEN_SECRET, { 11 | expiresIn: "90d", 12 | }); 13 | }; 14 | 15 | 16 | //Validate Collective API secret key 17 | 18 | exports.validateApiSecret = (req,res,next) =>{ 19 | 20 | // Checking if the API secret key is provided 21 | 22 | if(req.headers["x-api-key"] == "" || req.headers["x-api-key"] == undefined){ 23 | return res.status(401).json({error:"API secret key is not provided"}); 24 | } 25 | 26 | const api_secret_key = req.headers["x-api-key"]; 27 | 28 | //Checking if the API secret key is valid 29 | 30 | bcrypt.compare(api_secret_key,process.env.api_secret_key, function(err, result) { 31 | if(err){ 32 | return res.status(401).json({error:err,msg:"Invalid Collective API secret key"}); 33 | } 34 | if(!result){ 35 | return res.status(401).json({error:"Invalid Collective API secret key"}); 36 | } 37 | else{ 38 | next(); 39 | } 40 | }); 41 | } 42 | 43 | 44 | 45 | //Check for authentication 46 | 47 | exports.isAuthenticated=(req,res,next)=>{ 48 | var authHeader = 49 | req.body.token || 50 | req.query.token || 51 | req.headers["authorization"]; 52 | if (authHeader) { 53 | let token = authHeader.split(" "); 54 | jwt.verify(token[0], process.env.TOKEN_SECRET, function (err, decoded) { 55 | if (err) { 56 | return res 57 | .status(401) 58 | .send({ success: false, message: "Failed to authenticate token." }); 59 | } else { 60 | req.decoded = decoded; 61 | next(); 62 | } 63 | }); 64 | } else { 65 | return res.status(401).send({ 66 | success: false, 67 | message: "No token provided.", 68 | }); 69 | } 70 | } 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /backend/models/campDetailsModel.js: -------------------------------------------------------------------------------- 1 | var mongoose = require("mongoose"); 2 | 3 | var campDetailsSchema = new mongoose.Schema({ 4 | name : { 5 | type:String, 6 | default:'NA', 7 | required:true, 8 | unique:true, 9 | index:true 10 | }, 11 | owner : { 12 | type:String, 13 | default:'NA', 14 | required:true 15 | }, 16 | camp_image : { 17 | type:String, 18 | default:'NA', 19 | }, 20 | camp_description: { 21 | type:String, 22 | default:'NA', 23 | }, 24 | long_description: { 25 | type:String, 26 | default:'NA', 27 | }, 28 | createdOn : { 29 | type:String, 30 | default:'NA', 31 | required:true 32 | }, 33 | target : { 34 | type:Number, 35 | default:'NA', 36 | required:true 37 | }, 38 | equity : { 39 | type:Number, 40 | default:'NA', 41 | required:true 42 | }, 43 | address : { 44 | type:String, 45 | default:'NA', 46 | required:true 47 | }, 48 | privatekey : { 49 | type:String, 50 | default:'NA', 51 | required:true 52 | }, 53 | category : { 54 | type:String, 55 | default:'NA', 56 | required:true 57 | }, 58 | targetReachedDB : { 59 | type:Boolean, 60 | default:false 61 | }, 62 | amountWithdrawn : { 63 | type:Boolean, 64 | default:false 65 | }, 66 | 67 | }); 68 | 69 | module.exports = mongoose.model("CampDetails",campDetailsSchema); 70 | -------------------------------------------------------------------------------- /backend/routes/blockchain/CollectiveToken.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { body } = require("express-validator"); 4 | const { validateApiSecret,isAuthenticated } = require("../../middleware/authHelper"); 5 | require('dotenv').config(); 6 | 7 | const collectiveTokenController = require('../../controllers/blockchainControllers/collectiveTokenController'); 8 | 9 | 10 | // Get users account balance 11 | 12 | router.get('/getAccountBalance', 13 | validateApiSecret, 14 | body('account_address').not().isEmpty(), 15 | collectiveTokenController.getAccountBalance 16 | ); 17 | 18 | 19 | // // Get users account balance 20 | 21 | router.get('/getUsersAccountBalance', 22 | validateApiSecret, 23 | isAuthenticated, 24 | collectiveTokenController.getUsersAccountBalance 25 | ); 26 | 27 | 28 | // Sending ETH to users 29 | 30 | router.post('/transferETH', 31 | [body('transfer_address').not().isEmpty(), 32 | body('amount').not().isEmpty()], 33 | collectiveTokenController.transferETH 34 | ); 35 | 36 | 37 | // Sending CTV - Collective token to users 38 | 39 | router.post('/transferCTV', 40 | [body('transfer_address').not().isEmpty(), 41 | body('amount').not().isEmpty()], 42 | collectiveTokenController.transferCTV 43 | ); 44 | 45 | 46 | // Transfer CTV to use - with AUTH 47 | 48 | router.post('/transferCTVToUser', 49 | validateApiSecret, 50 | isAuthenticated, 51 | body('amount').not().isEmpty(), 52 | collectiveTokenController.transferCTVToUser 53 | ); 54 | 55 | 56 | // Get the allowance for a account; 57 | 58 | router.post('/getAllowance', 59 | [body('owner_address').not().isEmpty(), 60 | body('spender_address').not().isEmpty()], 61 | collectiveTokenController.getAllowance 62 | ); 63 | 64 | 65 | // Transfer CTV between user accounts 66 | 67 | /// How the API works 68 | 69 | // 1. Start with sending required ETH (gas) from the master account to CTV owner account 70 | // 2. Approve the transaction amount 71 | // 3. Send the amount to the transfer address from the CTV owner address 72 | 73 | router.post('/transferCTVbetweenUsers', 74 | [body('owner_address').not().isEmpty(), 75 | body('owner_private_key').not().isEmpty(), 76 | body('transfer_address').not().isEmpty(), 77 | body('amount').not().isEmpty()], 78 | collectiveTokenController.transferCTVbetweenUsers 79 | ); 80 | 81 | 82 | module.exports = router; -------------------------------------------------------------------------------- /frontend/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 17 | 21 | 25 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/lib/widgets/SplashScreenWidget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:collective/screens/HomeScreen.dart'; 4 | import 'package:collective/screens/LoginScreen.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:splashscreen/splashscreen.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:shared_preferences/shared_preferences.dart'; 9 | 10 | class SplashScreenWidget extends StatefulWidget { 11 | @override 12 | _SplashScreenWidgetState createState() => _SplashScreenWidgetState(); 13 | } 14 | 15 | class _SplashScreenWidgetState extends State { 16 | bool _isLogedIn = false; 17 | 18 | splashScreenCheck() { 19 | return Future.delayed(Duration(milliseconds: 1500), () async { 20 | try { 21 | final result = await InternetAddress.lookup('google.com'); 22 | if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { 23 | if (_isLogedIn == true) { 24 | return HomeScreen(); 25 | } else { 26 | return LoginScreen(); 27 | } 28 | } 29 | return LoginScreen(); 30 | } on SocketException catch (_) { 31 | return Scaffold( 32 | body: Center( 33 | child: Container( 34 | padding: EdgeInsets.all(50), 35 | child: Text( 36 | 'No internet connection, please check your network and restart the app!', 37 | textAlign: TextAlign.center, 38 | ), 39 | ), 40 | ), 41 | ); 42 | } 43 | }); 44 | } 45 | 46 | @override 47 | void initState() { 48 | super.initState(); 49 | SharedPreferences.getInstance().then( 50 | (prefValue) { 51 | if (prefValue.containsKey('token')) { 52 | setState(() { 53 | _isLogedIn = true; 54 | }); 55 | } 56 | }, 57 | ); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 63 | statusBarColor: Colors.white, 64 | statusBarIconBrightness: Brightness.dark, 65 | )); 66 | return Scaffold( 67 | backgroundColor: Colors.white, 68 | body: Container( 69 | padding: EdgeInsets.only(top: 230), 70 | child: SplashScreen( 71 | navigateAfterFuture: splashScreenCheck(), 72 | image: Image.asset('assets/images/Logo.png'), 73 | backgroundColor: Theme.of(context).backgroundColor, 74 | photoSize: 50.0, 75 | useLoader: false, 76 | ), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /frontend/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/screens/BuyCtvScreen.dart'; 2 | import 'package:collective/screens/CampScreen.dart'; 3 | import 'package:collective/screens/CollabMainScreen.dart'; 4 | import 'package:collective/screens/CreateCampCollabJobScreen.dart'; 5 | import 'package:collective/screens/CreateCampHomeScreen.dart'; 6 | import 'package:collective/screens/CreateCampScreen.dart'; 7 | import 'package:collective/screens/HomeScreen.dart'; 8 | import 'package:collective/screens/InvestInCamp.dart'; 9 | import 'package:collective/screens/LoginScreen.dart'; 10 | import 'package:collective/screens/RegisterScreen.dart'; 11 | import 'package:collective/screens/SupportEmailScreen.dart'; 12 | import 'package:collective/screens/UserDetailsScreen.dart'; 13 | import 'package:collective/screens/UserInvestmentScreen.dart'; 14 | import 'package:collective/screens/UsersCampScreen.dart'; 15 | import 'package:collective/screens/UsersCollabScreen.dart'; 16 | import 'package:collective/widgets/SplashScreenWidget.dart'; 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter/services.dart'; 19 | 20 | void main() { 21 | runApp(MyApp()); 22 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 23 | systemNavigationBarColor: Colors.white, 24 | systemNavigationBarDividerColor: Color.fromRGBO(240, 240, 240, 1), 25 | systemNavigationBarIconBrightness: Brightness.dark, 26 | )); 27 | } 28 | 29 | class MyApp extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | debugShowCheckedModeBanner: false, 34 | theme: ThemeData( 35 | primaryColor: Color.fromRGBO(24, 119, 242, 1.0), 36 | backgroundColor: Colors.white, 37 | fontFamily: 'Avenir'), 38 | home: SplashScreenWidget(), 39 | routes: { 40 | LoginScreen.routeName: (ctx) => LoginScreen(), 41 | RegisterScreen.routeName: (ctx) => RegisterScreen(), 42 | HomeScreen.routeName: (ctx) => HomeScreen(), 43 | BuyCtvScreen.routeName: (ctx) => BuyCtvScreen(), 44 | CreateCampScreen.routeName: (ctx) => CreateCampScreen(), 45 | CampScreen.routeName: (ctx) => CampScreen(), 46 | InvestInCamp.routeName: (ctx) => InvestInCamp(), 47 | UserDetailsScreen.routeName: (ctx) => UserDetailsScreen(), 48 | UserInvestmentScreen.routeName: (ctx) => UserInvestmentScreen(), 49 | UsersCollabScreen.routeName: (ctx) => UsersCollabScreen(), 50 | UsersCampScreen.routeName: (ctx) => UsersCampScreen(), 51 | SupportEmailScreen.routeName: (ctx) => SupportEmailScreen(), 52 | CreateCampHomeScreen.routeName: (ctx) => CreateCampHomeScreen(), 53 | CollabMainScreen.routeName: (ctx) => CollabMainScreen(), 54 | CreateCampCollabJobScreen.routeName: (ctx) => 55 | CreateCampCollabJobScreen(), 56 | }, 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /backend/routes/collective/Collab.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { body } = require("express-validator"); 5 | const { validateApiSecret,isAuthenticated } = require("../../middleware/authHelper"); 6 | 7 | const collabController = require('../../controllers/collectiveControllers/collabController'); 8 | 9 | require('dotenv').config(); 10 | 11 | 12 | 13 | // Create a new collab job for a camp 14 | 15 | router.post('/newCollabJobForCamp', 16 | validateApiSecret, 17 | isAuthenticated, 18 | [ 19 | body('camp_id').not().isEmpty(), 20 | body('camp_owner_username').not().isEmpty(), 21 | body('collab_title').not().isEmpty(), 22 | body('collab_amount').not().isEmpty(), 23 | body('collab_description').not().isEmpty() 24 | ], 25 | collabController.newCollabJobForCamp 26 | ); 27 | 28 | 29 | // Fetch all the collab jobs for a particular camp 30 | 31 | router.get('/getAllCollabJobForACamp/:id', 32 | validateApiSecret, 33 | isAuthenticated, 34 | collabController.getAllCollabJobForACamp 35 | ); 36 | 37 | 38 | // Send a request to a camp to join as a collab for a particular position 39 | 40 | router.post('/sendRequestToCollab', 41 | validateApiSecret, 42 | isAuthenticated, 43 | [body('collab_job_id').not().isEmpty()], 44 | collabController.sendRequestToCollab 45 | ); 46 | 47 | 48 | // Fetch Collab Request for a particular camp 49 | 50 | 51 | router.get('/getAllCollabRequestForACampJob/:id', 52 | validateApiSecret, 53 | isAuthenticated, 54 | collabController.getAllCollabRequestForACampJob 55 | ); 56 | 57 | 58 | 59 | // Accept a request for a collab job 60 | 61 | router.post('/acceptUsersRequest', 62 | validateApiSecret, 63 | isAuthenticated, 64 | [ body('collab_job_id').not().isEmpty(), 65 | body('camp_address').not().isEmpty(), 66 | body('col_username').not().isEmpty(), 67 | body('col_address').not().isEmpty(), 68 | body('collab_title').not().isEmpty(), 69 | body('collab_amount').not().isEmpty(), 70 | ], 71 | collabController.acceptUsersRequest 72 | ); 73 | 74 | 75 | 76 | // Reject a collab request for a collab 77 | 78 | router.put('/rejectCollabRequestForACampJob', 79 | validateApiSecret, 80 | isAuthenticated, 81 | [ 82 | body('collab_job_id').not().isEmpty(), 83 | body('col_req_username').not().isEmpty(), 84 | ], 85 | collabController.rejectCollabRequestForACampJob 86 | ); 87 | 88 | 89 | 90 | // Get camps accepted collabs 91 | 92 | router.get('/getCollabAcceptedRequest/:campaddress', 93 | validateApiSecret, 94 | isAuthenticated, 95 | collabController.getCollabAcceptedRequest 96 | ); 97 | 98 | 99 | 100 | module.exports = router; -------------------------------------------------------------------------------- /frontend/lib/widgets/CampScreenWidgets/CollabWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/screens/CollabMainScreen.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CollabWidget extends StatelessWidget { 5 | final AsyncSnapshot snapshot; 6 | final String username; 7 | final Map selectedCamp; 8 | final String campId; 9 | 10 | CollabWidget(this.snapshot, this.username, this.selectedCamp, this.campId); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Column( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | SizedBox( 18 | height: 8, 19 | ), 20 | Container( 21 | width: MediaQuery.of(context).size.width - 40, 22 | height: 60, 23 | margin: EdgeInsets.only( 24 | bottom: 7, 25 | ), 26 | child: Text( 27 | snapshot.data["details"]["camp_description"], 28 | textAlign: TextAlign.start, 29 | style: TextStyle( 30 | fontSize: 17, 31 | color: Colors.black, 32 | ), 33 | ), 34 | ), 35 | Divider(), 36 | Row( 37 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 38 | children: [ 39 | Text( 40 | 'Total collabs', 41 | style: TextStyle( 42 | fontWeight: FontWeight.w600, 43 | ), 44 | ), 45 | Text( 46 | snapshot.data["details"]["campCollabCount"].toString(), 47 | style: TextStyle( 48 | color: Theme.of(context).primaryColor, 49 | ), 50 | ), 51 | ], 52 | ), 53 | Divider(), 54 | SizedBox( 55 | height: 10, 56 | ), 57 | ElevatedButton( 58 | child: Row( 59 | mainAxisAlignment: MainAxisAlignment.center, 60 | children: [ 61 | Padding( 62 | padding: const EdgeInsets.only( 63 | top: 3, 64 | bottom: 0, 65 | ), 66 | child: Text( 67 | 'Collab with ' + snapshot.data['details']['name'], 68 | style: TextStyle( 69 | fontSize: 19, color: Theme.of(context).primaryColor), 70 | ), 71 | ), 72 | ], 73 | ), 74 | style: ElevatedButton.styleFrom( 75 | elevation: 0, 76 | primary: Color.fromRGBO(230, 230, 230, 1), 77 | shape: RoundedRectangleBorder( 78 | borderRadius: BorderRadius.circular(50), 79 | ), 80 | minimumSize: Size(100, 45), 81 | ), 82 | onPressed: () { 83 | Navigator.of(context) 84 | .pushNamed(CollabMainScreen.routeName, arguments: { 85 | "username": username, 86 | "selectedCamp": selectedCamp, 87 | "campId": campId, 88 | "campOwner": snapshot.data['details']['owner'] 89 | }); 90 | }, 91 | ) 92 | ], 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /frontend/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: collective 2 | description: A equity crowdfunding platform. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | flutter_launcher_icons: ^0.9.0 27 | splashscreen: ^1.3.5 28 | http: ^0.13.3 29 | flutter_spinkit: "^4.1.2" 30 | form_field_validator: ^1.1.0 31 | shared_preferences: ^2.0.6 32 | progressive_image: ^2.0.0 33 | flutter_svg: ^0.22.0 34 | pay: ^1.0.3 35 | image_picker: ^0.7.5+3 36 | percent_indicator: ^3.0.1 37 | bottom_navy_bar: ^6.0.0 38 | 39 | 40 | # The following adds the Cupertino Icons font to your application. 41 | # Use with the CupertinoIcons class for iOS style icons. 42 | cupertino_icons: ^1.0.2 43 | 44 | dev_dependencies: 45 | flutter_test: 46 | sdk: flutter 47 | 48 | flutter_icons: 49 | android: "launcher_icon" 50 | image_path: "assets/images/LauncherIcon.png" 51 | 52 | # For information on the generic Dart part of this file, see the 53 | # following page: https://dart.dev/tools/pub/pubspec 54 | 55 | # The following section is specific to Flutter. 56 | flutter: 57 | 58 | # The following line ensures that the Material Icons font is 59 | # included with your application, so that you can use the icons in 60 | # the material Icons class. 61 | uses-material-design: true 62 | 63 | # To add assets to your application, add an assets section, like this: 64 | assets: 65 | - assets/images/ 66 | - assets/ 67 | 68 | # An image asset can refer to one or more resolution-specific "variants", see 69 | # https://flutter.dev/assets-and-images/#resolution-aware. 70 | 71 | # For details regarding adding assets from package dependencies, see 72 | # https://flutter.dev/assets-and-images/#from-packages 73 | 74 | # To add custom fonts to your application, add a fonts section here, 75 | # in this "flutter" section. Each entry in this list should have a 76 | # "family" key with the font family name, and a "fonts" key with a 77 | # list giving the asset and other descriptors for the font. For 78 | # example: 79 | fonts: 80 | - family: Avenir 81 | fonts: 82 | - asset: assets/fonts/AvenirNextRegular.ttf 83 | -------------------------------------------------------------------------------- /backend/routes/blockchain/Camps.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const multer = require('multer'); 4 | const path = require('path'); 5 | const { v4: uuidv4 } = require('uuid'); 6 | const { body} = require("express-validator"); 7 | 8 | const campsController = require('../../controllers/blockchainControllers/campsController'); 9 | const { validateApiSecret,isAuthenticated } = require("../../middleware/authHelper"); 10 | 11 | require('dotenv').config(); 12 | 13 | 14 | // Multer setup for Camp image upload 15 | 16 | const storage = multer.diskStorage({ 17 | destination: function(req,file,cb){ 18 | cb(null,(path.join(__dirname,'../../../../CollectiveMedia/camp'))); 19 | }, 20 | filename:function(req,file,cb){ 21 | const file_name = uuidv4() +".jpg"; 22 | cb(null,file_name) 23 | } 24 | }); 25 | 26 | const upload = multer({storage:storage}); 27 | 28 | 29 | // CREATE A NEW CROWFUNDING CAMP ON COLLECTIVE 30 | 31 | router.post('/createCamp', 32 | validateApiSecret, 33 | isAuthenticated, 34 | upload.single('image'), 35 | campsController.createCamp 36 | ); 37 | 38 | 39 | // GET CAMP LIST 40 | 41 | router.post('/getCampList', 42 | validateApiSecret, 43 | isAuthenticated, 44 | body('sort_by').not().isEmpty(), 45 | campsController.getCampList 46 | ); 47 | 48 | 49 | // BUY EQUITY IN CAMP 50 | 51 | router.post('/buyEquity', 52 | validateApiSecret, 53 | isAuthenticated, 54 | body('camp_address').not().isEmpty(), 55 | body('amount').not().isEmpty(), 56 | campsController.buyEquity 57 | ); 58 | 59 | 60 | 61 | // GET CAMP DETAILS 62 | 63 | router.post('/getCampDetails', 64 | body('camp_address').not().isEmpty(), 65 | campsController.getCampDetails 66 | ); 67 | 68 | 69 | // GET CAMP MASTER DETAILS 70 | 71 | router.post('/getCampMasterDetails', 72 | body('camp_address').not().isEmpty(), 73 | campsController.getCampMasterDetails 74 | ); 75 | 76 | 77 | // GET FUNDING DETAILS 78 | 79 | router.post('/getFundingDetails', 80 | body('camp_address').not().isEmpty(), 81 | body('angel_address').not().isEmpty(), 82 | campsController.getFundingDetails 83 | ); 84 | 85 | 86 | 87 | // GET NUMBERS OF ANGELS WHO INVESTED IN A CAMP 88 | 89 | router.post('/getCampsAngelInvestorsCount', 90 | body('camp_address').not().isEmpty(), 91 | campsController.getCampsAngelInvestorsCount 92 | ); 93 | 94 | 95 | // GET LIST OF ANGEL INVESTORS FOR A CAMP 96 | 97 | router.post('/getCampsAngelInvestors', 98 | body('camp_address').not().isEmpty(), 99 | campsController.getCampsAngelInvestors 100 | ); 101 | 102 | 103 | // GET CAMPS CREATED BY A USER 104 | 105 | router.get('/getCampsCreatedByUser', 106 | validateApiSecret, 107 | isAuthenticated, 108 | campsController.getCampsCreatedByUser 109 | ); 110 | 111 | 112 | // GET CAMPS CREATED BY A USER 113 | 114 | router.get('/getCampsInvestedByUser', 115 | validateApiSecret, 116 | isAuthenticated, 117 | campsController.getCampsInvestedByUser 118 | ); 119 | 120 | 121 | // GET COLLAB JOBS OF A USER 122 | 123 | router.get('/getUsersCollabs', 124 | validateApiSecret, 125 | isAuthenticated, 126 | campsController.getUsersCollabs 127 | ); 128 | 129 | 130 | 131 | // Search for a camp - Search API 132 | 133 | router.post('/searchCamp', 134 | validateApiSecret, 135 | isAuthenticated, 136 | [body('camp_name').not().isEmpty()], 137 | campsController.searchCamp 138 | ); 139 | 140 | 141 | module.exports = router; 142 | 143 | 144 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /backend/contracts/Camps.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | // Camp - A camp that is created by a Creative ( OP - Original Posters ) on Collective to raise funding 6 | 7 | // Angel - Angels are the users/investors who buy equity in exchange of CTV - CollectiveToken 8 | 9 | // Col - Collaborator who collaborates in a camp in exchange of CTV 10 | 11 | contract Camps { 12 | 13 | // m => (camp_address => m (angel_address => investment_amount)) 14 | mapping(address => mapping(address => uint)) public funding; 15 | 16 | 17 | mapping(address => CampDetails) public camps; 18 | 19 | 20 | // Struct to save collaborator details 21 | 22 | struct CollaboratorsList { 23 | address colAddress; 24 | uint amount; 25 | string position; 26 | } 27 | 28 | 29 | // Camp details struct 30 | 31 | struct CampDetails{ 32 | bool campExists; 33 | uint fundingRaised; 34 | address[] angelList; 35 | CollaboratorsList[] colList; 36 | uint target; 37 | uint equity; 38 | bool targetReached; 39 | } 40 | 41 | 42 | 43 | 44 | // Create a new camp on Collective 45 | 46 | function createCamp(address _camp, uint _target, uint _equity) public { 47 | require(camps[_camp].campExists == false,"Camp already exists"); 48 | camps[_camp].campExists = true; 49 | camps[_camp].target = _target; 50 | camps[_camp].equity = _equity; 51 | camps[_camp].targetReached = false; 52 | } 53 | 54 | 55 | // Emit event when the camp target is reached 56 | 57 | event targetReachedForCamp(address _camp); 58 | 59 | 60 | // Buying equity in the camp 61 | 62 | function buyEquity(address _angel,address _camp,uint _amount) public { 63 | require(camps[_camp].campExists == true && camps[_camp].targetReached == false,'Camp not found'); 64 | if(camps[_camp].fundingRaised + _amount >= camps[_camp].target){ 65 | camps[_camp].targetReached = true; 66 | } 67 | funding[_camp][_angel] = funding[_camp][_angel] + _amount; 68 | camps[_camp].fundingRaised = camps[_camp].fundingRaised + _amount; 69 | camps[_camp].angelList.push(_angel); 70 | 71 | 72 | if(camps[_camp].targetReached){ 73 | emit targetReachedForCamp( _camp); 74 | } 75 | 76 | } 77 | 78 | 79 | // get the total number of Angels who bought equity in a camp 80 | // can also be used for fetching the total number of investments 81 | 82 | function getAngelListLength(address _camp) public view returns(uint){ 83 | return camps[_camp].angelList.length; 84 | } 85 | 86 | 87 | // get the list of Angels who bought equity in a camp 88 | 89 | function getAngelList(address _camp) public view returns(address[] memory){ 90 | return camps[_camp].angelList; 91 | } 92 | 93 | 94 | // Collaborate in a camp 95 | 96 | function collab(address _col,address _camp,string memory _position,uint _amount) public { 97 | require(camps[_camp].campExists == true,'Camp not found'); 98 | camps[_camp].colList.push(CollaboratorsList({ 99 | colAddress : _col, 100 | amount : _amount, 101 | position : _position 102 | })); 103 | } 104 | 105 | 106 | // Get collab details for a camp 107 | 108 | function getCollabDetails(address _camp) public view returns (CollaboratorsList[] memory){ 109 | return camps[_camp].colList; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /frontend/lib/screens/CreateCampHomeScreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/screens/CreateCampScreen.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CreateCampHomeScreen extends StatelessWidget { 5 | static String routeName = '/createCampHomeScreen'; 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | height: MediaQuery.of(context).size.height, 11 | width: MediaQuery.of(context).size.width, 12 | child: Column( 13 | mainAxisAlignment: MainAxisAlignment.center, 14 | children: [ 15 | Row( 16 | mainAxisAlignment: MainAxisAlignment.spaceAround, 17 | children: [ 18 | Image.asset( 19 | 'assets/images/CreateCamp.png', 20 | height: 355, 21 | width: MediaQuery.of(context).size.width, 22 | ) 23 | ], 24 | ), 25 | Row( 26 | mainAxisAlignment: MainAxisAlignment.spaceAround, 27 | children: [ 28 | Padding( 29 | padding: const EdgeInsets.only(top: 33.0), 30 | child: Text( 31 | 'Find fundings for your dreams', 32 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), 33 | ), 34 | ) 35 | ], 36 | ), 37 | Row( 38 | mainAxisAlignment: MainAxisAlignment.spaceAround, 39 | children: [ 40 | Container( 41 | height: 65, 42 | width: MediaQuery.of(context).size.width, 43 | padding: const EdgeInsets.only(top: 8.0, left: 57, right: 57), 44 | child: Text( 45 | 'Collective will help you find people who are as passionate about your idea as you are', 46 | style: TextStyle( 47 | fontWeight: FontWeight.normal, 48 | fontSize: 15, 49 | color: Colors.grey[500]), 50 | textAlign: TextAlign.center, 51 | ), 52 | ) 53 | ], 54 | ), 55 | Row( 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | children: [ 58 | Container( 59 | margin: EdgeInsets.only(top: 28), 60 | child: ElevatedButton( 61 | child: Row( 62 | mainAxisAlignment: MainAxisAlignment.center, 63 | children: [ 64 | Padding( 65 | padding: const EdgeInsets.only( 66 | top: 3, 67 | bottom: 0, 68 | ), 69 | child: Text( 70 | 'Create a new camp', 71 | style: TextStyle( 72 | fontSize: 18, 73 | ), 74 | ), 75 | ), 76 | ], 77 | ), 78 | style: ElevatedButton.styleFrom( 79 | elevation: 0, 80 | primary: Theme.of(context).primaryColor, 81 | shape: RoundedRectangleBorder( 82 | borderRadius: BorderRadius.circular(50), 83 | ), 84 | minimumSize: Size(252, 42), 85 | ), 86 | onPressed: () { 87 | Navigator.of(context) 88 | .pushNamed(CreateCampScreen.routeName); 89 | }), 90 | ) 91 | ], 92 | ) 93 | ], 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /frontend/lib/widgets/CampScreenWidgets/MoreCampDataWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MoreCampDataWidget extends StatelessWidget { 4 | final AsyncSnapshot snapshot; 5 | 6 | MoreCampDataWidget(this.snapshot); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return SingleChildScrollView( 11 | child: Container( 12 | width: MediaQuery.of(context).size.width - 40, 13 | height: 180, 14 | child: Column( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | Container( 18 | height: 35, 19 | padding: EdgeInsets.all(8), 20 | child: Row( 21 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 22 | children: [ 23 | Text( 24 | 'Camp owner', 25 | textAlign: TextAlign.start, 26 | style: TextStyle( 27 | fontSize: 16, 28 | color: Colors.black, 29 | fontWeight: FontWeight.bold, 30 | ), 31 | ), 32 | Text( 33 | snapshot.data["details"]["owner"], 34 | textAlign: TextAlign.start, 35 | style: TextStyle( 36 | fontSize: 16, 37 | color: Theme.of(context).primaryColor, 38 | ), 39 | ) 40 | ], 41 | ), 42 | ), 43 | Divider(), 44 | Container( 45 | height: 35, 46 | margin: EdgeInsets.only( 47 | top: 5, 48 | ), 49 | padding: EdgeInsets.all(8), 50 | child: Row( 51 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 52 | children: [ 53 | Text( 54 | 'Created on ', 55 | textAlign: TextAlign.start, 56 | style: TextStyle( 57 | fontSize: 16, 58 | color: Colors.black, 59 | fontWeight: FontWeight.bold, 60 | ), 61 | ), 62 | Text( 63 | snapshot.data["details"]["createdOn"].split(',')[0], 64 | textAlign: TextAlign.start, 65 | style: TextStyle( 66 | fontSize: 16, 67 | color: Theme.of(context).primaryColor, 68 | ), 69 | ), 70 | ], 71 | ), 72 | ), 73 | Divider(), 74 | Container( 75 | height: 35, 76 | margin: EdgeInsets.only( 77 | top: 5, 78 | ), 79 | padding: EdgeInsets.all(8), 80 | child: Row( 81 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 82 | children: [ 83 | Text( 84 | 'Camp status', 85 | textAlign: TextAlign.start, 86 | style: TextStyle( 87 | fontSize: 16, 88 | color: Colors.black, 89 | fontWeight: FontWeight.bold), 90 | ), 91 | Text( 92 | snapshot.data["details"]["targetReachedDB"] 93 | ? 'Target reached' 94 | : 'Raising funding', 95 | textAlign: TextAlign.start, 96 | style: TextStyle( 97 | fontSize: 16, 98 | color: Theme.of(context).primaryColor, 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ], 105 | ), 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /frontend/lib/screens/CollabMainScreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/screens/CreateCampCollabJobScreen.dart'; 2 | import 'package:collective/widgets/CampScreenWidgets/CampCollaboratorsWidget.dart'; 3 | import 'package:collective/widgets/CampScreenWidgets/OpportunitiesListWidget.dart'; 4 | import 'package:collective/widgets/appBarGoBack.dart'; 5 | import 'package:collective/widgets/keepAlivePage.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | import 'dart:async'; 9 | 10 | class CollabMainScreen extends StatefulWidget { 11 | static String routeName = '/collabMainScreen'; 12 | 13 | @override 14 | _CollabMainScreenState createState() => _CollabMainScreenState(); 15 | } 16 | 17 | class _CollabMainScreenState extends State 18 | with TickerProviderStateMixin { 19 | Map selectedCamp = {}; 20 | String token; 21 | String username; 22 | TabController _tabController; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | setToken(); 28 | _tabController = new TabController(length: 2, vsync: this); 29 | } 30 | 31 | Future setToken() async { 32 | SharedPreferences.getInstance().then((prefValue) { 33 | token = prefValue.getString('token'); 34 | username = prefValue.getString('username'); 35 | setState(() {}); 36 | }); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | _tabController.dispose(); 42 | super.dispose(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | selectedCamp = ModalRoute.of(context).settings.arguments; 48 | return Scaffold( 49 | appBar: PreferredSize( 50 | preferredSize: const Size.fromHeight(55), 51 | child: AppBarGoBack(), 52 | ), 53 | body: Column( 54 | children: [ 55 | Container( 56 | color: Colors.white, 57 | child: Container( 58 | height: 50, 59 | child: TabBar( 60 | labelPadding: EdgeInsets.only(top: 5), 61 | indicatorPadding: EdgeInsets.only(left: 15, right: 15), 62 | indicatorColor: Theme.of(context).primaryColor, 63 | unselectedLabelColor: Colors.black38, 64 | labelColor: Theme.of(context).primaryColor, 65 | labelStyle: TextStyle( 66 | fontWeight: FontWeight.bold, 67 | fontSize: 15, 68 | fontFamily: 'Avenir', 69 | ), 70 | unselectedLabelStyle: TextStyle( 71 | fontWeight: FontWeight.normal, 72 | fontSize: 15, 73 | fontFamily: 'Avenir', 74 | ), 75 | tabs: [ 76 | Tab( 77 | child: Text( 78 | "Opportunities", 79 | ), 80 | ), 81 | Tab( 82 | child: Text( 83 | "Collaborators", 84 | ), 85 | ), 86 | ], 87 | controller: _tabController, 88 | ), 89 | ), 90 | ), 91 | Container( 92 | padding: EdgeInsets.only(top: 22), 93 | color: Colors.white, 94 | height: MediaQuery.of(context).size.height - 95 | AppBar().preferredSize.height - 96 | 88, 97 | width: MediaQuery.of(context).size.width, 98 | child: TabBarView( 99 | controller: _tabController, 100 | children: [ 101 | KeepAlivePage(child: OpportunitiesListWidget(selectedCamp)), 102 | KeepAlivePage(child: CampCollaboratorsWidget(selectedCamp)), 103 | ], 104 | ), 105 | ) 106 | ], 107 | ), 108 | floatingActionButton: selectedCamp['campOwner'] == this.username 109 | ? FloatingActionButton( 110 | onPressed: () { 111 | Navigator.of(context) 112 | .pushNamed(CreateCampCollabJobScreen.routeName, arguments: { 113 | "campId": selectedCamp['campId'], 114 | "campOwner": selectedCamp['campOwner'] 115 | }); 116 | }, 117 | elevation: 4, 118 | child: const Icon( 119 | Icons.add, 120 | color: Color.fromRGBO(24, 119, 242, 1.0), 121 | ), 122 | backgroundColor: Color.fromRGBO(230, 230, 230, 1), 123 | ) 124 | : null, 125 | floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat, 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /frontend/lib/screens/UsersCampScreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/widgets/CampListViewWidget.dart'; 2 | import 'package:collective/widgets/appBarGoBack.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | import 'dart:async'; 7 | import 'dart:convert'; 8 | import 'package:http/http.dart' as http; 9 | 10 | class UsersCampScreen extends StatefulWidget { 11 | static String routeName = '/UsersCampScreen'; 12 | 13 | @override 14 | _UsersCampScreenState createState() => _UsersCampScreenState(); 15 | } 16 | 17 | class _UsersCampScreenState extends State { 18 | String token; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | setToken(); 24 | } 25 | 26 | Future setToken() async { 27 | SharedPreferences.getInstance().then((prefValue) { 28 | token = prefValue.getString('token'); 29 | setState(() {}); 30 | }); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | backgroundColor: Colors.white, 37 | appBar: PreferredSize( 38 | preferredSize: const Size.fromHeight(55), 39 | child: AppBarGoBack(), 40 | ), 41 | body: SingleChildScrollView( 42 | child: Column( 43 | children: [ 44 | Row( 45 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 46 | children: [ 47 | Container( 48 | padding: EdgeInsets.only(left: 17, top: 25, bottom: 5), 49 | child: Text( 50 | "Camps that you own", 51 | style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), 52 | ), 53 | ), 54 | Container( 55 | child: TextButton( 56 | style: TextButton.styleFrom( 57 | padding: EdgeInsets.only( 58 | left: 0, 59 | top: 15, 60 | ), 61 | minimumSize: Size(0, 0)), 62 | child: Icon( 63 | Icons.restart_alt, 64 | color: Theme.of(context).primaryColor, 65 | size: 22, 66 | ), 67 | onPressed: () { 68 | setState(() {}); 69 | }, 70 | ), 71 | ), 72 | ], 73 | ), 74 | Container( 75 | height: MediaQuery.of(context).size.height, 76 | width: MediaQuery.of(context).size.width, 77 | color: Colors.white, 78 | padding: EdgeInsets.only(left: 10, right: 10, top: 5), 79 | child: FutureBuilder( 80 | future: getCampsCreatedByUser(token), 81 | builder: 82 | (BuildContext context, AsyncSnapshot snapshot) { 83 | if (snapshot.connectionState == ConnectionState.waiting) { 84 | return Padding( 85 | padding: const EdgeInsets.only(top: 190.0), 86 | child: Column( 87 | children: [ 88 | SpinKitThreeBounce( 89 | color: Theme.of(context).primaryColor, 90 | size: 35.0, 91 | ), 92 | ], 93 | ), 94 | ); 95 | } else { 96 | if (snapshot.data['result'] == false) { 97 | return Center( 98 | child: 99 | Text('There was a problem fetching balance')); 100 | } 101 | if (snapshot.data['details'].length == 0) { 102 | return Center( 103 | child: Padding( 104 | padding: const EdgeInsets.only( 105 | bottom: 150, 106 | ), 107 | child: Text('No camps found'), 108 | ), 109 | ); 110 | } else { 111 | return CampListViewWidget(snapshot); 112 | } 113 | } 114 | }), 115 | ), 116 | ], 117 | ), 118 | ), 119 | ); 120 | } 121 | } 122 | 123 | Future getCampsCreatedByUser(String token) async { 124 | var headers = { 125 | 'x-api-key': 126 | '8\$dsfsfgreb6&4w5fsdjdjkje#\$54757jdskjrekrm@#\$@\$%&8fdddg*&*ffdsds', 127 | 'Content-Type': 'application/json', 128 | 'Authorization': token 129 | }; 130 | var request = http.Request( 131 | 'GET', Uri.parse('http://3.135.1.141/api/getCampsCreatedByUser')); 132 | 133 | request.headers.addAll(headers); 134 | 135 | http.StreamedResponse response = await request.send(); 136 | 137 | return await jsonDecode(await response.stream.bytesToString()); 138 | } 139 | -------------------------------------------------------------------------------- /frontend/lib/screens/UserInvestmentScreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/widgets/CampListViewWidget.dart'; 2 | import 'package:collective/widgets/appBarGoBack.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | import 'dart:async'; 7 | import 'dart:convert'; 8 | import 'package:http/http.dart' as http; 9 | 10 | class UserInvestmentScreen extends StatefulWidget { 11 | @override 12 | _UserInvestmentScreenState createState() => _UserInvestmentScreenState(); 13 | 14 | static String routeName = "/UserInvestmentScreen"; 15 | } 16 | 17 | class _UserInvestmentScreenState extends State { 18 | String token; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | setToken(); 24 | } 25 | 26 | Future setToken() async { 27 | SharedPreferences.getInstance().then((prefValue) { 28 | token = prefValue.getString('token'); 29 | setState(() {}); 30 | }); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | backgroundColor: Colors.white, 37 | appBar: PreferredSize( 38 | preferredSize: const Size.fromHeight(55), 39 | child: AppBarGoBack(), 40 | ), 41 | body: SingleChildScrollView( 42 | child: Column( 43 | children: [ 44 | Row( 45 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 46 | children: [ 47 | Container( 48 | padding: EdgeInsets.only(left: 17, top: 25, bottom: 5), 49 | child: Text( 50 | "Your camp investments", 51 | style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), 52 | ), 53 | ), 54 | Container( 55 | child: TextButton( 56 | style: TextButton.styleFrom( 57 | padding: EdgeInsets.only( 58 | left: 0, 59 | top: 15, 60 | ), 61 | minimumSize: Size(0, 0)), 62 | child: Icon( 63 | Icons.restart_alt, 64 | color: Theme.of(context).primaryColor, 65 | size: 22, 66 | ), 67 | onPressed: () { 68 | setState(() {}); 69 | }, 70 | ), 71 | ), 72 | ], 73 | ), 74 | Container( 75 | height: MediaQuery.of(context).size.height, 76 | width: MediaQuery.of(context).size.width, 77 | color: Colors.white, 78 | padding: EdgeInsets.only(left: 10, right: 10, top: 5), 79 | child: FutureBuilder( 80 | future: getCampsInvestedByUser(token), 81 | builder: 82 | (BuildContext context, AsyncSnapshot snapshot) { 83 | if (snapshot.connectionState == ConnectionState.waiting) { 84 | return Padding( 85 | padding: const EdgeInsets.only(top: 190.0), 86 | child: Column( 87 | children: [ 88 | SpinKitThreeBounce( 89 | color: Theme.of(context).primaryColor, 90 | size: 35.0, 91 | ), 92 | ], 93 | ), 94 | ); 95 | } else { 96 | if (snapshot.data['result'] == false) { 97 | return Center( 98 | child: 99 | Text('There was a problem fetching balance')); 100 | } 101 | if (snapshot.data['details'].length == 0) { 102 | return Center( 103 | child: Padding( 104 | padding: const EdgeInsets.only( 105 | bottom: 150, 106 | ), 107 | child: Text('No investments found'), 108 | ), 109 | ); 110 | } else { 111 | return CampListViewWidget(snapshot); 112 | } 113 | } 114 | }), 115 | ), 116 | ], 117 | ), 118 | ), 119 | ); 120 | } 121 | } 122 | 123 | Future getCampsInvestedByUser(String token) async { 124 | var headers = { 125 | 'x-api-key': 126 | '8\$dsfsfgreb6&4w5fsdjdjkje#\$54757jdskjrekrm@#\$@\$%&8fdddg*&*ffdsds', 127 | 'Content-Type': 'application/json', 128 | 'Authorization': token 129 | }; 130 | 131 | var request = http.Request( 132 | 'GET', Uri.parse('http://3.135.1.141/api/getCampsInvestedByUser')); 133 | 134 | request.headers.addAll(headers); 135 | 136 | http.StreamedResponse response = await request.send(); 137 | 138 | return await jsonDecode(await response.stream.bytesToString()); 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Collective

2 | 3 | Collective 4 | 5 |

An equity crowdfunding app managed with smart contracts that allows users to invest in projects with crypto in return of equity.

6 | 7 | 8 |
9 | 10 |

Abstract

11 |

At present, Crowdfunding source of raising funds typically for startups or projects has gained popularity with most startups resorting to the use of Crowdfunding platforms to raise funds in exchange of equity because it is relatively inexpensive and uncomplicated in nature. In the existing model, Pool of people contribute small amounts of money towards a project or cause and expect some financial returns. The call for a solution to issues related to security, investor abuse and, illegal transactions that could plague crowdfunding has lead me to investigate the implications of blockchain in Crowdfunding. 12 |

13 | 14 | 15 |
16 | 17 |

Introduction

18 |
    19 |
  • A global challenge facing start-ups is the raising of the required funds. Although there are many sources of funds available to entrepreneurs who wish to start new businesses or expand existing ones, the challenge of getting inexpensive funding at the right time still remains a challenge in the small business domain. The emergence of crowdfunding as a brainchild of crowd-sourcing provides an alternative form of funding for start-ups. 20 |
  • 21 |
    22 |
  • Collective is an online equity crowdfunding platform managed by smart contracts on Ethereum. The platform enables start-ups and projects to raise funding in return of equity. Individual can invest a relatively small amount of money in-order to receive stake in a company at an early stage hoping to get good returns in the long-term as the startup/project grows. Along with funding, Collective also enables startups/projects to find the required talent for their projects.
  • 23 |
    24 |
  • What makes Collective truly unique is its decentralized and autonomous approach towards crowdfunding using smart contract that are deployed on the Ethereum blockchain and a mobile app created using Google’s Flutter which can be used on both Android and IOS.
  • 25 |
    26 |
  • Startups on the platform can create camps in which normal users can purchase equity by investing CTV ( Collective token ) a ERC20 fungible token exclusive only to the Collective platform. This platform exclusive ERC20 token along with smart contracts enables Collective to tackle the issue of trust and security that plagues all the existing crowdfunding platforms. 27 |
  • 28 |
29 | 30 |
31 | 32 |

Tech Stack

33 |

Flutter - Android / IOS app

34 |

Node.js - Backend server with MVC architecture hosted on AWS EC2 along with NGINX reverse proxy

35 |

MongoDB - Database hosted on cloud atlas

36 |

Blockchain - Ethereum

37 |

CTV token - ERC20 token

38 |

Mocha - Testing

39 | 40 |
41 | 42 |

App walkthrough video

43 | 44 | https://youtu.be/dtBWU9CF_cI 45 | 46 | 47 |
48 | 49 |

Collective Screens

50 | 51 | CollectiveCollective 52 | 53 | 54 | CollectiveCollective 55 | 56 | CollectiveCollective 57 | 58 | CollectiveCollective 59 | 60 | CollectiveCollective 61 | 62 | CollectiveCollective 63 | 64 | CollectiveCollective 65 | 66 | CollectiveCollective 67 | 68 |
69 | 70 |

High level system design

71 | 72 | Collective 73 | 74 |
75 | 76 |

How equity crowdfunding works

77 | 78 | Collective 79 | 80 |
81 | 82 | 83 |

How collaboration works

84 | 85 | Collective 86 | 87 |
88 | 89 |

Future enhancement

90 | 91 |
    92 |
  • Migrating to a micro-service based architecture for the backend using RabbitMQ.
  • 93 |
  • Moving all the camp images to IPFS or AWS S3 bucket.
  • 94 |
  • Improving the transaction speeds by migrating to Hedera hashgraph and implementing HTS and HSC 2.0.
  • 95 |
96 | 97 |
98 | 99 |

Thank you for your interest !!

100 | -------------------------------------------------------------------------------- /frontend/lib/screens/UsersCollabScreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/widgets/appBarGoBack.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'package:http/http.dart' as http; 8 | 9 | class UsersCollabScreen extends StatefulWidget { 10 | static String routeName = '/userCollabScreen'; 11 | 12 | @override 13 | _UsersCollabScreenState createState() => _UsersCollabScreenState(); 14 | } 15 | 16 | class _UsersCollabScreenState extends State { 17 | String token; 18 | String username; 19 | Future collabsList; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | 25 | setToken(); 26 | } 27 | 28 | Future setToken() async { 29 | SharedPreferences.getInstance().then((prefValue) { 30 | token = prefValue.getString('token'); 31 | username = prefValue.getString('username'); 32 | collabsList = getUsersCollaboration(token); 33 | setState(() {}); 34 | }); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return SafeArea( 40 | child: Scaffold( 41 | appBar: PreferredSize( 42 | preferredSize: const Size.fromHeight(55), 43 | child: AppBarGoBack(), 44 | ), 45 | body: Container( 46 | color: Colors.white, 47 | padding: EdgeInsets.all(16), 48 | width: MediaQuery.of(context).size.width, 49 | height: MediaQuery.of(context).size.height - 50 | AppBar().preferredSize.height - 51 | 35, 52 | child: FutureBuilder( 53 | future: collabsList, 54 | builder: (BuildContext context, AsyncSnapshot snapshot) { 55 | if (snapshot.connectionState == ConnectionState.waiting || 56 | snapshot.connectionState == ConnectionState.none) { 57 | return Padding( 58 | padding: const EdgeInsets.only(top: 270), 59 | child: Column( 60 | children: [ 61 | SpinKitThreeBounce( 62 | color: Theme.of(context).primaryColor, 63 | size: 35.0, 64 | ), 65 | ], 66 | ), 67 | ); 68 | } 69 | if (snapshot.data['result'] == false) { 70 | return Center( 71 | child: Padding( 72 | padding: const EdgeInsets.only( 73 | left: 60.0, 74 | right: 60.0, 75 | top: 0, 76 | ), 77 | child: Text( 78 | 'No collaborations found', 79 | textAlign: TextAlign.center, 80 | style: TextStyle(fontSize: 16), 81 | ), 82 | ), 83 | ); 84 | } 85 | if (snapshot.data['result'] == true && 86 | snapshot.data['details'].length == 0) { 87 | return Center( 88 | child: Padding( 89 | padding: const EdgeInsets.only( 90 | left: 60.0, 91 | right: 60.0, 92 | top: 0, 93 | ), 94 | child: Text( 95 | 'No collaborations found', 96 | textAlign: TextAlign.center, 97 | style: TextStyle(fontSize: 16), 98 | ), 99 | ), 100 | ); 101 | } 102 | return ListView.builder( 103 | itemCount: snapshot.data['details'].length, 104 | itemBuilder: (context, index) { 105 | return Container( 106 | margin: EdgeInsets.only(bottom: 25), 107 | padding: EdgeInsets.all(15), 108 | decoration: BoxDecoration( 109 | borderRadius: BorderRadius.circular(15), 110 | color: Color.fromRGBO(240, 240, 240, 1), 111 | ), 112 | child: Column( 113 | children: [ 114 | Row( 115 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 116 | children: [ 117 | Text('Role', 118 | style: 119 | TextStyle(fontWeight: FontWeight.bold)), 120 | Text( 121 | snapshot.data['details'][index] 122 | ['collabTitle'], 123 | style: TextStyle( 124 | color: Theme.of(context).primaryColor)) 125 | ], 126 | ), 127 | Divider(), 128 | Row( 129 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 130 | children: [ 131 | Text('Budget', 132 | style: 133 | TextStyle(fontWeight: FontWeight.bold)), 134 | Text( 135 | snapshot.data['details'][index] 136 | ['collabAmount'] 137 | .toString() + 138 | " CTV", 139 | style: TextStyle( 140 | color: Theme.of(context).primaryColor)) 141 | ], 142 | ), 143 | ], 144 | ), 145 | ); 146 | }); 147 | }, 148 | ), 149 | ), 150 | ), 151 | ); 152 | } 153 | } 154 | 155 | Future getUsersCollaboration(String token) async { 156 | var headers = { 157 | 'x-api-key': 158 | '8\$dsfsfgreb6&4w5fsdjdjkje#\$54757jdskjrekrm@#\$@\$%&8fdddg*&*ffdsds', 159 | 'Content-Type': 'application/json', 160 | 'Authorization': token 161 | }; 162 | var request = 163 | http.Request('GET', Uri.parse('http://3.135.1.141/api/getUsersCollabs')); 164 | 165 | request.headers.addAll(headers); 166 | 167 | http.StreamedResponse response = await request.send(); 168 | 169 | return await jsonDecode(await response.stream.bytesToString()); 170 | } 171 | -------------------------------------------------------------------------------- /frontend/lib/widgets/CampScreenWidgets/CampCollaboratorsWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'dart:async'; 5 | import 'dart:convert'; 6 | import 'package:http/http.dart' as http; 7 | 8 | class CampCollaboratorsWidget extends StatefulWidget { 9 | final Map selectedCamp; 10 | 11 | CampCollaboratorsWidget(this.selectedCamp); 12 | @override 13 | _CampCollaboratorsWidgetState createState() => 14 | _CampCollaboratorsWidgetState(); 15 | } 16 | 17 | class _CampCollaboratorsWidgetState extends State { 18 | String token; 19 | String username; 20 | Future collabJobsList; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | 26 | setToken(); 27 | } 28 | 29 | Future setToken() async { 30 | SharedPreferences.getInstance().then((prefValue) { 31 | token = prefValue.getString('token'); 32 | username = prefValue.getString('username'); 33 | collabJobsList = getAllAcceptedCollabJobs( 34 | token, widget.selectedCamp['selectedCamp']['campAddress']); 35 | setState(() {}); 36 | }); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Container( 42 | width: MediaQuery.of(context).size.width, 43 | child: FutureBuilder( 44 | future: collabJobsList, 45 | builder: (BuildContext context, AsyncSnapshot snapshot) { 46 | if (snapshot.connectionState == ConnectionState.waiting || 47 | snapshot.connectionState == ConnectionState.none) { 48 | return Padding( 49 | padding: const EdgeInsets.only(top: 270), 50 | child: Column( 51 | children: [ 52 | SpinKitThreeBounce( 53 | color: Theme.of(context).primaryColor, 54 | size: 35.0, 55 | ), 56 | ], 57 | ), 58 | ); 59 | } 60 | if (snapshot.data['result'] == false) { 61 | return Center( 62 | child: Padding( 63 | padding: const EdgeInsets.only( 64 | left: 60.0, 65 | right: 60.0, 66 | top: 0, 67 | ), 68 | child: Text( 69 | 'No collaborators found', 70 | textAlign: TextAlign.center, 71 | style: TextStyle(fontSize: 16), 72 | ), 73 | ), 74 | ); 75 | } 76 | if (snapshot.data['result'] == true && 77 | snapshot.data['curatedCollabJobDetails'].length == 0) { 78 | return Center( 79 | child: Padding( 80 | padding: const EdgeInsets.only( 81 | left: 60.0, 82 | right: 60.0, 83 | top: 0, 84 | ), 85 | child: Text( 86 | 'No collaborators found', 87 | textAlign: TextAlign.center, 88 | style: TextStyle(fontSize: 16), 89 | ), 90 | ), 91 | ); 92 | } 93 | 94 | return ListView.builder( 95 | itemCount: snapshot.data['curatedCollabJobDetails'].length, 96 | itemBuilder: (context, index) { 97 | return Container( 98 | margin: EdgeInsets.only(bottom: 25, left: 16, right: 16), 99 | padding: EdgeInsets.all(15), 100 | decoration: BoxDecoration( 101 | borderRadius: BorderRadius.circular(15), 102 | color: Color.fromRGBO(240, 240, 240, 1), 103 | ), 104 | child: Column( 105 | children: [ 106 | Row( 107 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 108 | children: [ 109 | Text( 110 | 'Username', 111 | style: TextStyle(fontWeight: FontWeight.bold), 112 | ), 113 | Text( 114 | snapshot.data['curatedCollabJobDetails'][index] 115 | ['username'], 116 | style: TextStyle( 117 | color: Theme.of(context).primaryColor)) 118 | ], 119 | ), 120 | Divider(), 121 | Row( 122 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 123 | children: [ 124 | Text('Email', 125 | style: TextStyle(fontWeight: FontWeight.bold)), 126 | Text( 127 | snapshot.data['curatedCollabJobDetails'][index] 128 | ['email'], 129 | style: TextStyle( 130 | color: Theme.of(context).primaryColor)) 131 | ], 132 | ), 133 | Divider(), 134 | Row( 135 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 136 | children: [ 137 | Text('Role', 138 | style: TextStyle(fontWeight: FontWeight.bold)), 139 | Text( 140 | snapshot.data['curatedCollabJobDetails'][index] 141 | ['position'], 142 | style: TextStyle( 143 | color: Theme.of(context).primaryColor)) 144 | ], 145 | ), 146 | Divider(), 147 | Row( 148 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 149 | children: [ 150 | Text('Budget', 151 | style: TextStyle(fontWeight: FontWeight.bold)), 152 | Text( 153 | snapshot.data['curatedCollabJobDetails'][index] 154 | ['amount'], 155 | style: TextStyle( 156 | color: Theme.of(context).primaryColor)) 157 | ], 158 | ), 159 | ], 160 | ), 161 | ); 162 | }); 163 | }), 164 | ); 165 | } 166 | } 167 | 168 | Future getAllAcceptedCollabJobs( 169 | String token, String campAddress) async { 170 | var headers = { 171 | 'x-api-key': 172 | '8\$dsfsfgreb6&4w5fsdjdjkje#\$54757jdskjrekrm@#\$@\$%&8fdddg*&*ffdsds', 173 | 'Content-Type': 'application/json', 174 | 'Authorization': token 175 | }; 176 | var request = http.Request( 177 | 'GET', 178 | Uri.parse( 179 | 'http://3.135.1.141/api/getCollabAcceptedRequest/' + campAddress)); 180 | request.headers.addAll(headers); 181 | 182 | http.StreamedResponse response = await request.send(); 183 | 184 | return await jsonDecode(await response.stream.bytesToString()); 185 | } 186 | -------------------------------------------------------------------------------- /backend/controllers/authControllers/authController.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcryptjs"); 2 | const Web3 = require('web3'); 3 | const moment = require('moment-timezone'); 4 | const CryptoJS = require("crypto-js"); 5 | const UserAuthModel = require("../../models/userAuthModel") 6 | const UserDetailsModel = require("../../models/userDetailsModel"); 7 | const { validationResult } = require("express-validator"); 8 | const {generateToken} = require("../../middleware/authHelper"); 9 | 10 | 11 | require('dotenv').config(); 12 | 13 | const rpcURL = 'https://ropsten.infura.io/v3/7a0de82adffe468d8f3c1e2183b37c39'; 14 | 15 | const web3 = new Web3(rpcURL); 16 | 17 | 18 | const userRegister = async(req,res) => { 19 | try{ 20 | const errors = validationResult(req); 21 | if (!errors.isEmpty()) { 22 | return res.status(422).json({ 23 | error: errors.array()[0], 24 | result:false 25 | }); 26 | } 27 | 28 | //Check if the username is within the length bracket 29 | if(req.body.username.length>50 || req.body.username.length<5){ 30 | return res.status(401).json({ 31 | error:'Username cannot be more than 50 characters and smaller than 5 characters', 32 | result:false 33 | }); 34 | } 35 | 36 | //Check if the password is smaller than 5 37 | if(req.body.password.length<5){ 38 | return res.status(401).json({ 39 | error:'Password cannot be smaller than 5 characters', 40 | result:false 41 | }); 42 | } 43 | 44 | //Hashing password 45 | const salt = await bcrypt.genSalt(10); 46 | const hash = await bcrypt.hash(req.body.password, salt); 47 | 48 | 49 | const ethAccount = await web3.eth.accounts.create(); 50 | 51 | if(!ethAccount){ 52 | return res.status(400).json({ 53 | msg:"There was a problem creating ETH account for the user", 54 | result:false 55 | }); 56 | } 57 | 58 | // Using AES to encrypt the Ethereum private key 59 | 60 | let ciphertext = CryptoJS.AES.encrypt(ethAccount.privateKey,process.env.master_key).toString(); 61 | 62 | //Saving user's auth details 63 | 64 | const userDetails = { 65 | email:req.body.email, 66 | username:req.body.username, 67 | password:hash, 68 | timestamp:moment().format('MMMM Do YYYY, h:mm:ss a'), 69 | eth_address:ethAccount.address, 70 | eth_private_key:ciphertext 71 | } 72 | 73 | const newUser = await UserAuthModel.create(userDetails) 74 | 75 | //Saving user's extra details 76 | 77 | const userPersonalDetails = {email:req.body.email,username:req.body.username} 78 | 79 | const newUserDetails = await UserDetailsModel.create(userPersonalDetails) 80 | 81 | if(newUser && newUserDetails){ 82 | 83 | // Generating Token on signup 84 | let payload = { 85 | email : newUserDetails.email, 86 | username : newUserDetails.username, 87 | id : newUserDetails._id, 88 | eth_address:ethAccount.address, 89 | eth_private_key:ciphertext 90 | }; 91 | 92 | const token = generateToken(payload); 93 | 94 | if(token.length>0){ 95 | res.status(200).json({ 96 | result:true, 97 | msg: "User registered successfully", 98 | details:newUserDetails, 99 | token:token 100 | }); 101 | } 102 | else{ 103 | res.status(500).json({ 104 | msg:"There was a problem creating token for the new user", 105 | result:false 106 | }) 107 | } 108 | 109 | } 110 | else{ 111 | res.status(400).json( 112 | { 113 | result:false, 114 | msg: "There was a problem creating a new user", 115 | }); 116 | } 117 | } 118 | catch(err){ 119 | console.log(err); 120 | if(err.name === 'MongoError' && err.code === 11000){ 121 | console.log(err); 122 | res.status(401).json({ 123 | result:false, 124 | msg:"Duplicate field", 125 | field:err.keyValue 126 | }); 127 | } 128 | else{ 129 | res.status(401).json(err); 130 | } 131 | } 132 | 133 | } 134 | 135 | 136 | 137 | const userLogin = async (req,res)=>{ 138 | try{ 139 | 140 | const errors = validationResult(req); 141 | if (!errors.isEmpty()) { 142 | return res.status(422).json({ 143 | error: errors.array()[0], 144 | result:false 145 | }); 146 | } 147 | 148 | const {email_username,password} = req.body; 149 | 150 | const userAuth = await UserAuthModel.find({ $or:[ {email:email_username},{username:email_username} ]}); 151 | if(userAuth.length == 0){ 152 | return res.status(401).json({ 153 | msg:"User doesn't exist", 154 | result:false 155 | }); 156 | } 157 | 158 | const passwordCheck = await bcrypt.compare(req.body.password,userAuth[0].password); 159 | if(!passwordCheck){ 160 | return res.status(401).json({ 161 | msg:"Wrong password", 162 | result:false 163 | }) 164 | } 165 | 166 | const userDetails = await UserDetailsModel.find({$or:[ {email:email_username},{username:email_username} ]}); 167 | if(!userDetails){ 168 | res.status(404).json({ 169 | msg:"UserDetails not found", 170 | result:false 171 | }) 172 | } 173 | 174 | // Create a payload for JWT 175 | 176 | let payload = { 177 | email: userDetails[0].email, 178 | username:userDetails[0].username, 179 | id:userDetails[0]._id, 180 | eth_address:userAuth[0].eth_address, 181 | eth_private_key:userAuth[0].eth_private_key 182 | }; 183 | 184 | // Create a JWT token 185 | 186 | const token = generateToken(payload); 187 | 188 | return res.status(200).json({ 189 | email: userDetails[0].email, 190 | username:userDetails[0].username, 191 | id:userDetails[0]._id, 192 | token:token, 193 | result:true 194 | }); 195 | 196 | } 197 | catch(err){ 198 | console.log(err); 199 | res.status(500).json({ 200 | msg:"There was an issue in login", 201 | result:false 202 | }) 203 | } 204 | 205 | } 206 | 207 | 208 | const userVerify = async (req,res)=>{ 209 | try{ 210 | const userData = await UserAuthModel.find({ $or:[ {email:req.decoded.email},{username:req.decoded.username}]}); 211 | if(userData.length>0){ 212 | console.log(req.decoded); 213 | res.status(200).json({ 214 | msg:"User is a valid registered Collective user", 215 | result:true 216 | }) 217 | } 218 | else{ 219 | res.status(404).json({ 220 | error:"User doesn't exist", 221 | result:false 222 | }) 223 | } 224 | }catch(err){ 225 | return res.status(500).json({ 226 | msg:"Failed to verify user.", 227 | result:false, 228 | err 229 | }); 230 | } 231 | } 232 | 233 | module.exports = { 234 | userRegister, 235 | userLogin, 236 | userVerify 237 | } -------------------------------------------------------------------------------- /frontend/lib/widgets/CampListViewWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:collective/screens/CampScreen.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:progressive_image/progressive_image.dart'; 4 | 5 | class CampListViewWidget extends StatelessWidget { 6 | final AsyncSnapshot snapshot; 7 | 8 | CampListViewWidget(this.snapshot); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ListView.builder( 13 | padding: EdgeInsets.only(top: 10, left: 6, right: 6, bottom: 200), 14 | itemBuilder: (context, index) { 15 | return Container( 16 | margin: EdgeInsets.only(bottom: 25), 17 | decoration: BoxDecoration( 18 | borderRadius: BorderRadius.circular(15), 19 | color: Color.fromRGBO(240, 240, 240, 1), 20 | boxShadow: [ 21 | BoxShadow( 22 | color: Colors.grey, 23 | blurRadius: 0.0, 24 | ), 25 | ], 26 | ), 27 | height: 360, 28 | width: MediaQuery.of(context).size.width, 29 | child: Column( 30 | children: [ 31 | Row( 32 | children: [ 33 | Container( 34 | height: 180, 35 | width: MediaQuery.of(context).size.width - 32, 36 | child: ClipRRect( 37 | borderRadius: BorderRadius.only( 38 | topLeft: Radius.circular(15), 39 | topRight: Radius.circular(15)), 40 | child: ProgressiveImage( 41 | placeholder: 42 | AssetImage('assets/images/placeholder.jpg'), 43 | thumbnail: AssetImage('assets/images/placeholder.jpg'), 44 | image: NetworkImage( 45 | snapshot.data['details'][index]['camp_image']), 46 | fit: BoxFit.cover, 47 | height: 180, 48 | width: MediaQuery.of(context).size.width - 32, 49 | ), 50 | ), 51 | ), 52 | ], 53 | ), 54 | Row( 55 | children: [ 56 | Container( 57 | padding: EdgeInsets.all(15), 58 | height: 180, 59 | width: MediaQuery.of(context).size.width - 32, 60 | decoration: BoxDecoration( 61 | borderRadius: BorderRadius.only( 62 | bottomLeft: Radius.circular(15), 63 | bottomRight: Radius.circular(15), 64 | ), 65 | color: Color.fromRGBO(250, 250, 250, 1), 66 | ), 67 | child: Column( 68 | children: [ 69 | Container( 70 | height: 30, 71 | width: 326, 72 | child: Row( 73 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 74 | children: [ 75 | Text( 76 | snapshot.data['details'][index]['name'], 77 | style: TextStyle( 78 | fontWeight: FontWeight.bold, 79 | fontSize: 20, 80 | ), 81 | ), 82 | Padding( 83 | padding: const EdgeInsets.only(right: 0), 84 | child: Text( 85 | '- ' + 86 | snapshot.data['details'][index] 87 | ['category'], 88 | style: TextStyle( 89 | fontSize: 14, 90 | color: Theme.of(context).primaryColor), 91 | ), 92 | ), 93 | ], 94 | ), 95 | ), 96 | Container( 97 | height: 64, 98 | width: 326, 99 | child: Padding( 100 | padding: const EdgeInsets.only(top: 2.0), 101 | child: Text( 102 | snapshot.data['details'][index] 103 | ['camp_description'], 104 | textAlign: TextAlign.start, 105 | style: TextStyle( 106 | fontSize: 15.5, 107 | color: Colors.grey[600], 108 | ), 109 | ), 110 | ), 111 | ), 112 | Divider(), 113 | Container( 114 | padding: EdgeInsets.only(top: 6), 115 | height: 40, 116 | child: Row( 117 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 118 | children: [ 119 | Row( 120 | children: [ 121 | Image.asset( 122 | 'assets/images/LogoNoPadding.png', 123 | width: 27, 124 | height: 27, 125 | ), 126 | Padding( 127 | padding: 128 | const EdgeInsets.only(top: 4, left: 3), 129 | child: Text( 130 | snapshot.data['details'][index]['target'] 131 | .toString() 132 | .replaceAllMapped( 133 | new RegExp( 134 | r'(\d{1,3})(?=(\d{3})+(?!\d))'), 135 | (Match m) => '${m[1]},'), 136 | style: TextStyle( 137 | fontSize: 20, 138 | fontWeight: FontWeight.bold, 139 | color: 140 | Theme.of(context).primaryColor), 141 | ), 142 | ), 143 | ], 144 | ), 145 | ElevatedButton( 146 | style: ElevatedButton.styleFrom( 147 | elevation: 0, 148 | primary: Theme.of(context).primaryColor, 149 | shape: RoundedRectangleBorder( 150 | borderRadius: 151 | BorderRadius.circular(60)), 152 | minimumSize: Size(100, 45)), 153 | onPressed: () { 154 | Navigator.of(context).pushNamed( 155 | CampScreen.routeName, 156 | arguments: { 157 | 'campAddress': snapshot.data['details'] 158 | [index]['address'] 159 | }); 160 | }, 161 | child: Padding( 162 | padding: const EdgeInsets.only(top: 2), 163 | child: Text( 164 | 'Checkout', 165 | style: TextStyle( 166 | fontWeight: FontWeight.normal, 167 | fontSize: 16, 168 | ), 169 | ), 170 | ), 171 | ), 172 | ], 173 | ), 174 | ) 175 | ], 176 | ), 177 | ), 178 | ], 179 | ), 180 | ], 181 | ), 182 | ); 183 | }, 184 | itemCount: snapshot.data['details'].length, 185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /frontend/lib/screens/HomeScreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:bottom_navy_bar/bottom_navy_bar.dart'; 2 | import 'package:collective/screens/CreateCampHomeScreen.dart'; 3 | import 'package:collective/screens/SearchCampScreen.dart'; 4 | import 'package:collective/screens/SupportEmailScreen.dart'; 5 | import 'package:collective/screens/UserDetailsScreen.dart'; 6 | import 'package:collective/screens/UserInvestmentScreen.dart'; 7 | import 'package:collective/screens/UsersCampScreen.dart'; 8 | import 'package:collective/screens/UsersCollabScreen.dart'; 9 | import 'package:collective/widgets/HomeScreenWidget.dart'; 10 | import 'package:collective/widgets/keepAlivePage.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter/services.dart'; 13 | 14 | class HomeScreen extends StatefulWidget { 15 | static const routeName = '/homeScreen'; 16 | 17 | @override 18 | _HomeScreenState createState() => _HomeScreenState(); 19 | } 20 | 21 | class _HomeScreenState extends State { 22 | int _currentIndex = 1; 23 | PageController _pageController; 24 | 25 | final GlobalKey _scaffoldKey = GlobalKey(); 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _pageController = PageController(initialPage: 1); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | _pageController.dispose(); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 42 | statusBarColor: Colors.white, 43 | statusBarIconBrightness: Brightness.light, 44 | )); 45 | return Scaffold( 46 | key: _scaffoldKey, 47 | endDrawerEnableOpenDragGesture: false, 48 | backgroundColor: Colors.white, 49 | appBar: AppBar( 50 | backgroundColor: Colors.white, 51 | leading: IconButton( 52 | icon: const Icon(Icons.menu), 53 | color: Colors.black, 54 | iconSize: 25, 55 | onPressed: () { 56 | _scaffoldKey.currentState.openDrawer(); 57 | }, 58 | ), 59 | elevation: 2, 60 | centerTitle: true, 61 | title: Container( 62 | width: 155, 63 | child: Row( 64 | children: [ 65 | Image.asset( 66 | 'assets/images/Logo.png', 67 | width: 32, 68 | height: 32, 69 | ), 70 | Padding( 71 | padding: const EdgeInsets.only(top: 4.5, left: 3), 72 | child: Text( 73 | 'Collective', 74 | style: TextStyle( 75 | color: Theme.of(context).primaryColor, 76 | fontWeight: FontWeight.bold, 77 | fontSize: 25, 78 | ), 79 | ), 80 | ), 81 | ], 82 | ), 83 | ), 84 | actions: [ 85 | IconButton( 86 | icon: Icon( 87 | Icons.person, 88 | color: Colors.black, 89 | ), 90 | onPressed: () { 91 | Navigator.of(context).pushNamed(UserDetailsScreen.routeName); 92 | }) 93 | ], 94 | ), 95 | drawer: Drawer( 96 | child: Container( 97 | child: ListView( 98 | children: [ 99 | SizedBox( 100 | height: 64, 101 | child: DrawerHeader( 102 | decoration: BoxDecoration( 103 | color: Color.fromRGBO(240, 240, 240, 1), 104 | ), 105 | child: Row( 106 | children: [ 107 | Padding( 108 | padding: const EdgeInsets.only(top: 0.0, left: 0.0), 109 | child: Text( 110 | 'Collective', 111 | style: TextStyle( 112 | color: Theme.of(context).primaryColor, 113 | fontSize: 24, 114 | fontWeight: FontWeight.bold, 115 | ), 116 | ), 117 | ), 118 | ], 119 | ), 120 | ), 121 | ), 122 | ListTile( 123 | leading: Icon( 124 | Icons.person, 125 | color: Theme.of(context).primaryColor, 126 | ), 127 | title: Text('Account'), 128 | onTap: () { 129 | Navigator.of(context).pushNamed(UserDetailsScreen.routeName); 130 | }, 131 | ), 132 | Divider(), 133 | ListTile( 134 | leading: Icon( 135 | Icons.campaign, 136 | color: Theme.of(context).primaryColor, 137 | ), 138 | title: Text('Camps owned'), 139 | onTap: () { 140 | Navigator.of(context).pushNamed(UsersCampScreen.routeName); 141 | }, 142 | ), 143 | Divider(), 144 | ListTile( 145 | leading: Icon( 146 | Icons.monetization_on, 147 | color: Theme.of(context).primaryColor, 148 | ), 149 | title: Text('Investments'), 150 | onTap: () { 151 | Navigator.of(context) 152 | .pushNamed(UserInvestmentScreen.routeName); 153 | }), 154 | Divider(), 155 | ListTile( 156 | leading: Icon( 157 | Icons.group, 158 | color: Theme.of(context).primaryColor, 159 | ), 160 | title: Text('Collaborations'), 161 | onTap: () { 162 | Navigator.of(context).pushNamed(UsersCollabScreen.routeName); 163 | }, 164 | ), 165 | Divider(), 166 | ListTile( 167 | leading: Icon( 168 | Icons.help_rounded, 169 | color: Theme.of(context).primaryColor, 170 | ), 171 | title: Text('Support'), 172 | onTap: () { 173 | Navigator.of(context).pushNamed(SupportEmailScreen.routeName); 174 | }, 175 | ), 176 | Divider() 177 | ], 178 | ), 179 | ), 180 | ), 181 | body: PageView( 182 | controller: _pageController, 183 | onPageChanged: (index) { 184 | setState(() => _currentIndex = index); 185 | }, 186 | children: [ 187 | KeepAlivePage(child: SearchCampScreen()), 188 | KeepAlivePage(child: HomeScreenWidget()), 189 | CreateCampHomeScreen(), 190 | ], 191 | ), 192 | bottomNavigationBar: BottomNavyBar( 193 | selectedIndex: _currentIndex, 194 | showElevation: true, 195 | itemCornerRadius: 24, 196 | containerHeight: 47, 197 | mainAxisAlignment: MainAxisAlignment.spaceAround, 198 | curve: Curves.easeIn, 199 | backgroundColor: Colors.white, 200 | onItemSelected: (index) { 201 | setState(() => _currentIndex = index); 202 | _pageController.jumpToPage(index); 203 | }, 204 | items: [ 205 | BottomNavyBarItem( 206 | icon: Icon( 207 | Icons.search, 208 | color: Theme.of(context).primaryColor, 209 | ), 210 | title: Padding( 211 | padding: const EdgeInsets.only(top: 1.0), 212 | child: Text( 213 | 'Search', 214 | style: TextStyle( 215 | fontSize: 16, color: Theme.of(context).primaryColor), 216 | ), 217 | ), 218 | inactiveColor: Colors.black26, 219 | activeColor: Colors.grey[400], 220 | textAlign: TextAlign.center, 221 | ), 222 | BottomNavyBarItem( 223 | icon: Icon( 224 | Icons.campaign, 225 | color: Theme.of(context).primaryColor, 226 | ), 227 | title: Padding( 228 | padding: const EdgeInsets.only(top: 1.0), 229 | child: Text( 230 | 'Camps', 231 | style: TextStyle( 232 | fontSize: 16, color: Theme.of(context).primaryColor), 233 | ), 234 | ), 235 | inactiveColor: Colors.black26, 236 | activeColor: Colors.grey[400], 237 | textAlign: TextAlign.center, 238 | ), 239 | BottomNavyBarItem( 240 | icon: Icon( 241 | Icons.add_circle_rounded, 242 | color: Theme.of(context).primaryColor, 243 | ), 244 | title: Padding( 245 | padding: const EdgeInsets.only(top: 1.0), 246 | child: Text( 247 | 'Create', 248 | style: TextStyle( 249 | fontSize: 16, color: Theme.of(context).primaryColor), 250 | ), 251 | ), 252 | inactiveColor: Colors.black26, 253 | activeColor: Colors.grey[400], 254 | textAlign: TextAlign.center, 255 | ), 256 | ], 257 | ), 258 | ); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /backend/test/Camps.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const ganache = require('ganache-cli'); 3 | const Web3 = require('web3'); 4 | 5 | const web3 = new Web3(ganache.provider()); 6 | 7 | 8 | /////////////////////////// 9 | // Contract setup 10 | /////////////////////////// 11 | 12 | 13 | const Camps = require('../../backend/build/contracts/Camps.json'); 14 | 15 | const abi = Camps.abi; 16 | 17 | const bytecode = Camps.bytecode; 18 | 19 | let accounts; 20 | 21 | let contract; 22 | 23 | 24 | // A normal test for creating a new camp and user investing in it 25 | 26 | describe('Create camp,buy equity and get details', ()=>{ 27 | before(async()=>{ 28 | accounts = await web3.eth.getAccounts(); 29 | contract = await new web3.eth.Contract(abi) 30 | .deploy({data:bytecode}) 31 | .send({ 32 | from:accounts[0], 33 | gas:'2500000', 34 | }); 35 | 36 | }) 37 | 38 | it('Create a new camp',async()=>{ 39 | const camp = await contract.methods.createCamp(accounts[2],100,10).send({ 40 | from:accounts[1], 41 | to:contract.options.address, 42 | gas:'2500000', 43 | }) 44 | assert.ok(camp); 45 | }) 46 | 47 | it('Buy equity',async()=>{ 48 | const camp = await contract.methods.buyEquity(accounts[1],accounts[2],10).send({ 49 | from:accounts[1], 50 | gas:'2500000', 51 | }) 52 | assert.ok(camp); 53 | }) 54 | 55 | it('Get camp details',async()=>{ 56 | const campDetails = await contract.methods.camps(accounts[2]).call({}); 57 | assert.strictEqual( 58 | '10',campDetails[1] 59 | ); 60 | }) 61 | }); 62 | 63 | 64 | // Third transaction should not succeed and throw error 65 | 66 | describe('Buying over the target', ()=>{ 67 | before(async()=>{ 68 | accounts = await web3.eth.getAccounts(); 69 | contract = await new web3.eth.Contract(abi) 70 | .deploy({data:bytecode}) 71 | .send({ 72 | from:accounts[0], 73 | gas:'2500000', 74 | }); 75 | 76 | }) 77 | 78 | it('Create a new camp',async()=>{ 79 | const camp = await contract.methods.createCamp(accounts[2],50,10).send({ 80 | from:accounts[1], 81 | to:contract.options.address, 82 | gas:'2500000', 83 | }) 84 | assert.ok(camp); 85 | }) 86 | 87 | it('Buy equity - 30',async()=>{ 88 | const camp = await contract.methods.buyEquity(accounts[1],accounts[2],30).send({ 89 | from:accounts[1], 90 | gas:'2500000', 91 | }) 92 | assert.ok(camp); 93 | }); 94 | 95 | 96 | it('Buy equity - 20',async()=>{ 97 | const camp = await contract.methods.buyEquity(accounts[3],accounts[2],20).send({ 98 | from:accounts[3], 99 | gas:'2500000', 100 | }) 101 | assert.ok(camp); 102 | }); 103 | 104 | it('Buy equity - 10 (Should not go through)',async()=>{ 105 | try{ 106 | await contract.methods.buyEquity(accounts[4],accounts[2],10).send({ 107 | from:accounts[4], 108 | gas:'2500000', 109 | }) 110 | } 111 | catch(e){ 112 | assert.strictEqual(e.message,'VM Exception while processing transaction: revert Camp not found'); 113 | } 114 | }); 115 | 116 | 117 | it('Checking the amount raised',async()=>{ 118 | const campDetails = await contract.methods.camps(accounts[2]).call({}); 119 | assert.strictEqual( 120 | '50',campDetails[1] 121 | ); 122 | }) 123 | }); 124 | 125 | 126 | 127 | // Checking the Angels and the numbers of Angels in the AngelList 128 | 129 | describe('Checking the Angels and the numbers of Angels in the AngelList', ()=>{ 130 | before(async()=>{ 131 | accounts = await web3.eth.getAccounts(); 132 | contract = await new web3.eth.Contract(abi) 133 | .deploy({data:bytecode}) 134 | .send({ 135 | from:accounts[0], 136 | gas:'2500000', 137 | }); 138 | 139 | }) 140 | 141 | it('Create a new camp',async()=>{ 142 | const camp = await contract.methods.createCamp(accounts[2],50,10).send({ 143 | from:accounts[1], 144 | to:contract.options.address, 145 | gas:'2500000', 146 | }) 147 | assert.ok(camp); 148 | }) 149 | 150 | it('Buy equity - 20',async()=>{ 151 | const camp = await contract.methods.buyEquity(accounts[1],accounts[2],20).send({ 152 | from:accounts[1], 153 | gas:'2500000', 154 | }) 155 | assert.ok(camp); 156 | }); 157 | 158 | 159 | it('Buy equity - 20',async()=>{ 160 | const camp = await contract.methods.buyEquity(accounts[3],accounts[2],20).send({ 161 | from:accounts[3], 162 | gas:'2500000', 163 | }) 164 | assert.ok(camp); 165 | }); 166 | 167 | it('Buy equity - 10',async()=>{ 168 | 169 | const camp = await contract.methods.buyEquity(accounts[4],accounts[2],10).send({ 170 | from:accounts[4], 171 | gas:'2500000', 172 | }) 173 | 174 | assert.ok(camp); 175 | 176 | }); 177 | 178 | 179 | it('Checking the amount raised',async()=>{ 180 | const campDetails = await contract.methods.camps(accounts[2]).call({}); 181 | assert.strictEqual( 182 | '50',campDetails[1] 183 | ); 184 | }) 185 | 186 | it('Checking the angel list length',async()=>{ 187 | const angelListLength = await contract.methods.getAngelListLength(accounts[2]).call({}); 188 | assert.notStrictEqual(2,angelListLength); 189 | }); 190 | 191 | 192 | it('Checking the angel address in the angel list',async()=>{ 193 | const angelList = await contract.methods.getAngelList(accounts[2]).call({}); 194 | assert.notStrictEqual([accounts[1],accounts[3],accounts[4]],angelList); 195 | }); 196 | }); 197 | 198 | 199 | 200 | // Checking the funding amount for a angel 201 | 202 | describe('Checking the funding amount for a angel', ()=>{ 203 | before(async()=>{ 204 | accounts = await web3.eth.getAccounts(); 205 | contract = await new web3.eth.Contract(abi) 206 | .deploy({data:bytecode}) 207 | .send({ 208 | from:accounts[0], 209 | gas:'2500000', 210 | }); 211 | 212 | }) 213 | 214 | it('Create a new camp',async()=>{ 215 | const camp = await contract.methods.createCamp(accounts[2],50,10).send({ 216 | from:accounts[1], 217 | to:contract.options.address, 218 | gas:'2500000', 219 | }) 220 | assert.ok(camp); 221 | }) 222 | 223 | it('Buy equity - 20',async()=>{ 224 | const camp = await contract.methods.buyEquity(accounts[1],accounts[2],20).send({ 225 | from:accounts[1], 226 | gas:'2500000', 227 | }) 228 | assert.ok(camp); 229 | }); 230 | 231 | 232 | it('Buy equity - 25',async()=>{ 233 | const camp = await contract.methods.buyEquity(accounts[3],accounts[2],25).send({ 234 | from:accounts[3], 235 | gas:'2500000', 236 | }) 237 | assert.ok(camp); 238 | }); 239 | 240 | 241 | it('Checking the amount raised',async()=>{ 242 | const campDetails = await contract.methods.camps(accounts[2]).call({}); 243 | assert.strictEqual( 244 | '45',campDetails[1] 245 | ); 246 | }); 247 | 248 | it('Checking the funding amount for a angel',async()=>{ 249 | const fundingDetails = await contract.methods.funding(accounts[2],accounts[3]).call(); 250 | assert.strictEqual('25',fundingDetails) 251 | }); 252 | 253 | }); 254 | 255 | 256 | // Collaboration in camp 257 | 258 | describe('Camp Collaboration', ()=>{ 259 | before(async()=>{ 260 | accounts = await web3.eth.getAccounts(); 261 | contract = await new web3.eth.Contract(abi) 262 | .deploy({data:bytecode}) 263 | .send({ 264 | from:accounts[0], 265 | gas:'2500000', 266 | }); 267 | 268 | }) 269 | 270 | it('Create a new camp',async()=>{ 271 | const camp = await contract.methods.createCamp(accounts[2],50,10).send({ 272 | from:accounts[1], 273 | to:contract.options.address, 274 | gas:'2500000', 275 | }) 276 | assert.ok(camp); 277 | }) 278 | 279 | it('Buy equity - 20',async()=>{ 280 | const camp = await contract.methods.buyEquity(accounts[1],accounts[2],20).send({ 281 | from:accounts[1], 282 | gas:'2500000', 283 | }) 284 | assert.ok(camp); 285 | }); 286 | 287 | 288 | 289 | 290 | it('Checking the amount raised',async()=>{ 291 | const campDetails = await contract.methods.camps(accounts[2]).call({}); 292 | assert.strictEqual( 293 | '20',campDetails[1] 294 | ); 295 | }); 296 | 297 | it('Adding First collaborator with amount',async()=>{ 298 | const collab = await contract.methods.collab(accounts[5],accounts[2],'Software developer',10).send({ 299 | from:accounts[5], 300 | gas:'2500000', 301 | }) 302 | 303 | assert.ok(collab); 304 | }); 305 | 306 | 307 | it('Adding Second collaborator with amount',async()=>{ 308 | const collab = await contract.methods.collab(accounts[6],accounts[2],'Blockchain developer',20).send({ 309 | from:accounts[6], 310 | gas:'2500000', 311 | }) 312 | 313 | assert.ok(collab); 314 | }); 315 | 316 | it('Fetching and checking camp collaborator',async()=>{ 317 | const collabDetails = await contract.methods.getCollabDetails(accounts[2]).call(); 318 | assert.strictEqual(accounts[6],collabDetails[1][0]); 319 | }) 320 | 321 | }); 322 | -------------------------------------------------------------------------------- /backend/controllers/collectiveControllers/collectiveController.js: -------------------------------------------------------------------------------- 1 | const Tx = require('ethereumjs-tx').Transaction; 2 | const Web3 = require('web3'); 3 | const moment = require('moment-timezone'); 4 | const CryptoJS = require("crypto-js"); 5 | const axios = require("axios"); 6 | const nodemailer = require('nodemailer'); 7 | 8 | const CampModel = require("../../models/campDetailsModel"); 9 | const UserDetailsModel = require("../../models/userDetailsModel"); 10 | 11 | const { validationResult } = require("express-validator"); 12 | require('dotenv').config(); 13 | 14 | 15 | /////////////////////////// 16 | //Web3 and contract setup 17 | /////////////////////////// 18 | 19 | const rpcURL = 'https://ropsten.infura.io/v3/7a0de82adffe468d8f3c1e2183b37c39'; 20 | 21 | const web3 = new Web3(rpcURL); 22 | 23 | const CTVToken = require('../../build/contracts/CollectiveToken.json'); 24 | 25 | const ctv_contract_address = process.env.ctv_contract_address; 26 | 27 | const ctvabi = CTVToken.abi; 28 | 29 | const ctv_contract = new web3.eth.Contract(ctvabi,ctv_contract_address); 30 | 31 | //////////////////////////////////// 32 | // Account addresses & Private keys 33 | //////////////////////////////////// 34 | 35 | //Main account with which contract is deployed 36 | 37 | const account_address_1 = process.env.account_1; 38 | 39 | // Main private key - token generation 40 | 41 | const privateKey1 = Buffer.from(process.env.privateKey_1,'hex'); 42 | 43 | 44 | const getUserDetails = async (req,res)=>{ 45 | try{ 46 | 47 | const userData = await UserDetailsModel.find({username:req.decoded.username}); 48 | if(userData.length>0){ 49 | res.status(200).json({ 50 | msg:"User is a valid registered Collective user", 51 | result:true, 52 | userData:userData[0], 53 | userAuthData:req.decoded 54 | }) 55 | } 56 | else{ 57 | res.status(404).json({ 58 | error:"User doesn't exist", 59 | result:false 60 | }) 61 | } 62 | }catch(err){ 63 | return res.status(500).json({ 64 | msg:"Failed to fetch user data.", 65 | result:false, 66 | err 67 | }); 68 | } 69 | } 70 | 71 | 72 | const withdrawAmount = async (req,res)=>{ 73 | try{ 74 | 75 | //Input field validation 76 | const errors = validationResult(req); 77 | if (!errors.isEmpty()) { 78 | return res.status(422).json({ 79 | error: errors.array()[0],result:false 80 | }); 81 | } 82 | 83 | const owner_address = req.body.owner_address; 84 | const camp_private_key = req.body.owner_private_key; 85 | const transfer_address = req.decoded.eth_address; 86 | const amount = req.body.amount; 87 | 88 | 89 | let bytes = CryptoJS.AES.decrypt(camp_private_key, process.env.master_key); 90 | let bytes_key = bytes.toString(CryptoJS.enc.Utf8).slice(2); 91 | let owner_private_key = Buffer.from(bytes_key,'hex'); 92 | const estGasPrice = await web3.eth.getGasPrice()*2; 93 | 94 | res.status(200).json({ 95 | msg:"Amount withdrawal in-progress", 96 | result:true, 97 | }); 98 | 99 | /////////////////////////////////////////////////////////////// 100 | // Transferring ETH(gas) required for the transaction to owner 101 | /////////////////////////////////////////////////////////////// 102 | 103 | const txCount = await web3.eth.getTransactionCount(account_address_1); 104 | if(!txCount){ 105 | return res.status(500).json({ 106 | result:false, 107 | msg:'There was a problem transferring ETH - gas for the transaction' 108 | }) 109 | } 110 | 111 | // Build the transaction 112 | const txObject1 = { 113 | nonce: web3.utils.toHex(txCount), 114 | to: owner_address, 115 | value: web3.utils.toHex(web3.utils.toWei('100000000', 'gwei')), 116 | gasLimit: web3.utils.toHex(500000), 117 | gasPrice: web3.utils.toHex(estGasPrice), 118 | } 119 | 120 | // Sign the transaction 121 | const tx1 = new Tx(txObject1,{chain:3}) 122 | tx1.sign(privateKey1) 123 | 124 | const serializedTx1 = tx1.serialize() 125 | const raw1 = '0x' + serializedTx1.toString('hex') 126 | 127 | // Broadcast the transaction 128 | const sendTransaction = await web3.eth.sendSignedTransaction(raw1); 129 | if(!sendTransaction){ 130 | return res.status(500).json({ 131 | result:false, 132 | msg:'There was a problem transferring ETH - gas for the transaction' 133 | }) 134 | } 135 | console.log('\nETH transfered for the transaction'); 136 | 137 | 138 | 139 | ////////////////////////////////////////////////////////////// 140 | // Getting approval for the transaction 141 | ////////////////////////////////////////////////////////////// 142 | 143 | const ownertxCount = await web3.eth.getTransactionCount(owner_address); 144 | console.log("Approval txCount : "+ownertxCount); 145 | 146 | // Build the transaction 147 | const txObject2 = { 148 | nonce: web3.utils.toHex(ownertxCount), 149 | to: ctv_contract_address, 150 | gasLimit: web3.utils.toHex(50000), 151 | gasPrice: web3.utils.toHex(estGasPrice), 152 | data: ctv_contract.methods.increaseAllowance(owner_address,amount).encodeABI() 153 | } 154 | 155 | // Sign the transaction 156 | const tx2 = new Tx(txObject2,{chain:3}) 157 | tx2.sign(owner_private_key) 158 | 159 | const serializedTx2 = tx2.serialize() 160 | const raw2 = '0x' + serializedTx2.toString('hex') 161 | 162 | // Broadcast the transaction 163 | const approvalHash = await web3.eth.sendSignedTransaction(raw2); 164 | 165 | if(!approvalHash){ 166 | return res.status(500).json({ 167 | result:false, 168 | msg:'There was a problem getting approval for transaction' 169 | }) 170 | } 171 | 172 | console.log("\nTransfer approved"); 173 | 174 | 175 | 176 | ///////////////////////////////// 177 | // Transfering CTV between users 178 | ///////////////////////////////// 179 | 180 | 181 | const ownertxCountUpdated = await web3.eth.getTransactionCount(owner_address); 182 | console.log("Transfer txCount : "+ownertxCountUpdated); 183 | 184 | 185 | // Build the transaction 186 | const txObject3 = { 187 | nonce: web3.utils.toHex(ownertxCountUpdated), 188 | to: ctv_contract_address, 189 | gasLimit: web3.utils.toHex(100000), 190 | gasPrice: web3.utils.toHex(estGasPrice), 191 | data: ctv_contract.methods.transferFrom(owner_address,transfer_address,amount).encodeABI() 192 | } 193 | 194 | 195 | // Sign the transaction2 196 | const tx3 = new Tx(txObject3,{chain:3}) 197 | tx3.sign(owner_private_key) 198 | 199 | const serializedTx3 = tx3.serialize() 200 | const raw3 = '0x' + serializedTx3.toString('hex') 201 | 202 | // Broadcast the transaction 203 | const finalTransactionHash = await web3.eth.sendSignedTransaction(raw3); 204 | if(!finalTransactionHash){ 205 | return res.status(500).json({ 206 | result:false, 207 | msg:'There was a problem transferring CTV between users.' 208 | }) 209 | } 210 | 211 | console.log(`\nCTV transfered between accounts : ${amount} CTV`); 212 | 213 | const userData = await CampModel.findOneAndUpdate({address:owner_address},{amountWithdrawn:true}); 214 | if(userData){ 215 | console.log("Amount withdrawal successful"); 216 | } 217 | else{ 218 | console.log("Camp doesnt exits!"); 219 | } 220 | 221 | 222 | 223 | }catch(err){ 224 | console.log(err); 225 | return res.status(500).json({ 226 | msg:"Failed to withdraw amount!", 227 | result:false, 228 | }); 229 | } 230 | } 231 | 232 | 233 | // Support/Help email API 234 | 235 | const transporter = nodemailer.createTransport({ 236 | service: 'gmail', 237 | auth: { 238 | user: process.env.email_id, 239 | pass: process.env.email_password 240 | }, 241 | tls: { 242 | rejectUnauthorized: false 243 | } 244 | }); 245 | 246 | 247 | const supportEmail = async(req,res)=>{ 248 | try{ 249 | //Input field validation 250 | const errors = validationResult(req); 251 | if (!errors.isEmpty()) { 252 | return res.status(422).json({ 253 | error: errors.array()[0],result:false 254 | }); 255 | } 256 | 257 | let { email_subject,email_message } = req.body; 258 | let email_sender = req.decoded.email; 259 | 260 | // Using nodemailer to send support email 261 | 262 | const mailOptions = { 263 | from: email_sender, 264 | to: 'collectivecfplatform@gmail.com', 265 | subject: email_sender+" - "+email_subject, 266 | text: email_sender+" - "+email_message 267 | }; 268 | 269 | transporter.sendMail(mailOptions, function(error, info){ 270 | if (error) { 271 | console.log(error); 272 | return res.status(500).json({ 273 | msg:"There was a problem sending the email", 274 | result:false, 275 | error 276 | }); 277 | } else { 278 | return res.status(200).json({ 279 | msg:"Email sent, please wait for the response from our support team", 280 | result:true 281 | }) 282 | } 283 | }); 284 | 285 | } 286 | catch(err){ 287 | console.log(err); 288 | return res.status(500).json({ 289 | msg:"There was an error sending a message to our team, Please try again.", 290 | result:false, 291 | }); 292 | } 293 | } 294 | 295 | 296 | module.exports = { 297 | getUserDetails, 298 | withdrawAmount, 299 | supportEmail 300 | } -------------------------------------------------------------------------------- /frontend/lib/screens/LoginScreen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:collective/screens/HomeScreen.dart'; 4 | import 'package:collective/screens/RegisterScreen.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | import 'dart:async'; 9 | import 'dart:convert'; 10 | import 'package:http/http.dart' as http; 11 | import 'package:form_field_validator/form_field_validator.dart'; 12 | import 'package:shared_preferences/shared_preferences.dart'; 13 | 14 | class LoginScreen extends StatefulWidget { 15 | static const routeName = '/loginScreen'; 16 | 17 | @override 18 | _LoginScreenState createState() => _LoginScreenState(); 19 | } 20 | 21 | class _LoginScreenState extends State { 22 | TextEditingController emailUsernameController = new TextEditingController(); 23 | TextEditingController passwordController = new TextEditingController(); 24 | 25 | final _formKey = GlobalKey(); 26 | 27 | @override 28 | void dispose() { 29 | emailUsernameController.dispose(); 30 | passwordController.dispose(); 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 37 | statusBarColor: Colors.white, 38 | statusBarIconBrightness: Brightness.dark, 39 | )); 40 | return Scaffold( 41 | backgroundColor: Theme.of(context).backgroundColor, 42 | body: SafeArea( 43 | child: SingleChildScrollView( 44 | child: Column( 45 | children: [ 46 | Container( 47 | height: 90, 48 | padding: EdgeInsets.only(top: 0), 49 | decoration: BoxDecoration( 50 | color: Colors.white, 51 | ), 52 | child: Row( 53 | mainAxisAlignment: MainAxisAlignment.center, 54 | children: [ 55 | Image.asset( 56 | 'assets/images/LogoNoPadding.png', 57 | height: 32, 58 | width: 32, 59 | ), 60 | Padding( 61 | padding: const EdgeInsets.only(left: 3, top: 5), 62 | child: Text( 63 | 'Collective', 64 | style: TextStyle( 65 | color: Theme.of(context).primaryColor, 66 | fontWeight: FontWeight.bold, 67 | fontSize: 30), 68 | ), 69 | ) 70 | ], 71 | ), 72 | ), 73 | Container( 74 | height: 300, 75 | padding: const EdgeInsets.only( 76 | top: 35, 77 | left: 35, 78 | right: 35, 79 | ), 80 | child: Form( 81 | key: _formKey, 82 | child: ListView( 83 | children: [ 84 | Container( 85 | child: TextFormField( 86 | decoration: InputDecoration( 87 | labelText: 'Email / Username', 88 | filled: true, 89 | fillColor: Colors.grey[50], 90 | focusedBorder: OutlineInputBorder( 91 | borderRadius: BorderRadius.circular(30), 92 | borderSide: BorderSide( 93 | color: Theme.of(context).primaryColor, 94 | width: 1.5), 95 | ), 96 | enabledBorder: OutlineInputBorder( 97 | borderRadius: BorderRadius.circular(30), 98 | borderSide: 99 | BorderSide(color: Colors.grey[300], width: 1), 100 | ), 101 | ), 102 | controller: emailUsernameController, 103 | validator: RequiredValidator( 104 | errorText: 'Please provide a username or email'), 105 | ), 106 | ), 107 | Container( 108 | margin: EdgeInsets.only(top: 20), 109 | child: TextFormField( 110 | decoration: InputDecoration( 111 | labelText: 'Password', 112 | filled: true, 113 | fillColor: Colors.grey[50], 114 | focusedBorder: OutlineInputBorder( 115 | borderRadius: BorderRadius.circular(30), 116 | borderSide: BorderSide( 117 | color: Theme.of(context).primaryColor, 118 | width: 1.5), 119 | ), 120 | enabledBorder: OutlineInputBorder( 121 | borderRadius: BorderRadius.circular(30), 122 | borderSide: 123 | BorderSide(color: Colors.grey[300], width: 1), 124 | ), 125 | ), 126 | obscureText: true, 127 | controller: passwordController, 128 | validator: RequiredValidator( 129 | errorText: 'Please provide a password'), 130 | ), 131 | ), 132 | Container( 133 | margin: EdgeInsets.only(top: 25), 134 | child: ElevatedButton( 135 | onPressed: () { 136 | _formKey.currentState.validate() 137 | ? userLogin(emailUsernameController.text, 138 | passwordController.text) 139 | .then( 140 | (data) async { 141 | if (data['result'] == false) { 142 | ScaffoldMessenger.of(context) 143 | .showSnackBar(SnackBar( 144 | content: Text( 145 | "Invalid credentials", 146 | textAlign: TextAlign.center, 147 | ), 148 | )); 149 | } else if (data['result'] == true) { 150 | SharedPreferences prefs = 151 | await SharedPreferences 152 | .getInstance(); 153 | prefs.setString('email', data['email']); 154 | prefs.setString( 155 | 'username', data['username']); 156 | prefs.setString('id', data['id']); 157 | prefs.setString('token', data['token']); 158 | Navigator.of(context) 159 | .pushReplacementNamed( 160 | HomeScreen.routeName); 161 | ScaffoldMessenger.of(context) 162 | .showSnackBar(SnackBar( 163 | backgroundColor: 164 | Theme.of(context).primaryColor, 165 | content: Text( 166 | "Welcome to Collective", 167 | textAlign: TextAlign.center, 168 | style: TextStyle( 169 | color: Colors.white, 170 | ), 171 | ), 172 | )); 173 | } 174 | }, 175 | ) 176 | : print('Invalid credentials in from field'); 177 | }, 178 | style: ElevatedButton.styleFrom( 179 | elevation: 0, 180 | primary: Theme.of(context).primaryColor, 181 | shape: RoundedRectangleBorder( 182 | borderRadius: BorderRadius.circular(60)), 183 | minimumSize: Size(double.infinity, 52), 184 | ), 185 | child: Text( 186 | 'LOGIN', 187 | style: TextStyle( 188 | fontWeight: FontWeight.bold, 189 | fontSize: 17, 190 | ), 191 | ), 192 | ), 193 | ) 194 | ], 195 | ), 196 | ), 197 | ), 198 | Container( 199 | child: TextButton( 200 | onPressed: () { 201 | Navigator.of(context) 202 | .pushReplacementNamed(RegisterScreen.routeName); 203 | }, 204 | child: Text( 205 | 'New User ? register here', 206 | style: TextStyle( 207 | color: Theme.of(context).primaryColor, 208 | fontSize: 16, 209 | ), 210 | ), 211 | ), 212 | ), 213 | Container( 214 | margin: EdgeInsets.only(top: 10), 215 | decoration: BoxDecoration( 216 | color: Colors.white, 217 | ), 218 | height: 270, 219 | child: Image.asset( 220 | 'assets/images/LoginScreen.png', 221 | fit: BoxFit.cover, 222 | ), 223 | ), 224 | ], 225 | ), 226 | ), 227 | ), 228 | ); 229 | } 230 | } 231 | 232 | Future userLogin(String emailusername, String password) async { 233 | var headers = { 234 | 'x-api-key': 235 | '8\$dsfsfgreb6&4w5fsdjdjkje#\$54757jdskjrekrm@#\$@\$%&8fdddg*&*ffdsds', 236 | 'Content-Type': 'application/json' 237 | }; 238 | var request = 239 | http.Request('POST', Uri.parse('http://3.135.1.141/api/userLogin')); 240 | request.body = 241 | json.encode({"email_username": emailusername, "password": password}); 242 | request.headers.addAll(headers); 243 | 244 | http.StreamedResponse response = await request.send(); 245 | 246 | return jsonDecode(await response.stream.bytesToString()); 247 | } 248 | -------------------------------------------------------------------------------- /backend/contracts/ERC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 6 | import "@openzeppelin/contracts/utils/Context.sol"; 7 | 8 | /** 9 | * @dev Implementation of the {IERC20} interface. 10 | * 11 | * This implementation is agnostic to the way tokens are created. This means 12 | * that a supply mechanism has to be added in a derived contract using {_mint}. 13 | * For a generic mechanism see {ERC20PresetMinterPauser}. 14 | * 15 | * TIP: For a detailed writeup see our guide 16 | * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How 17 | * to implement supply mechanisms]. 18 | * 19 | * We have followed general OpenZeppelin guidelines: functions revert instead 20 | * of returning `false` on failure. This behavior is nonetheless conventional 21 | * and does not conflict with the expectations of ERC20 applications. 22 | * 23 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 24 | * This allows applications to reconstruct the allowance for all accounts just 25 | * by listening to said events. Other implementations of the EIP may not emit 26 | * these events, as it isn't required by the specification. 27 | * 28 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 29 | * functions have been added to mitigate the well-known issues around setting 30 | * allowances. See {IERC20-approve}. 31 | */ 32 | contract ERC is Context, IERC20, IERC20Metadata { 33 | mapping (address => uint256) private _balances; 34 | 35 | mapping (address => mapping (address => uint256)) private _allowances; 36 | 37 | uint256 private _totalSupply; 38 | 39 | string private _name; 40 | string private _symbol; 41 | 42 | /** 43 | * @dev Sets the values for {name} and {symbol}. 44 | * 45 | * The defaut value of {decimals} is 18. To select a different value for 46 | * {decimals} you should overload it. 47 | * 48 | * All two of these values are immutable: they can only be set once during 49 | * construction. 50 | */ 51 | constructor (string memory name_, string memory symbol_) { 52 | _name = name_; 53 | _symbol = symbol_; 54 | } 55 | 56 | /** 57 | * @dev Returns the name of the token. 58 | */ 59 | function name() public view virtual override returns (string memory) { 60 | return _name; 61 | } 62 | 63 | /** 64 | * @dev Returns the symbol of the token, usually a shorter version of the 65 | * name. 66 | */ 67 | function symbol() public view virtual override returns (string memory) { 68 | return _symbol; 69 | } 70 | 71 | /** 72 | * @dev Returns the number of decimals used to get its user representation. 73 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 74 | * be displayed to a user as `5,05` (`505 / 10 ** 2`). 75 | * 76 | * Tokens usually opt for a value of 18, imitating the relationship between 77 | * Ether and Wei. This is the value {ERC20} uses, unless this function is 78 | * overridden; 79 | * 80 | * NOTE: This information is only used for _display_ purposes: it in 81 | * no way affects any of the arithmetic of the contract, including 82 | * {IERC20-balanceOf} and {IERC20-transfer}. 83 | */ 84 | function decimals() public view virtual override returns (uint8) { 85 | return 18; 86 | } 87 | 88 | /** 89 | * @dev See {IERC20-totalSupply}. 90 | */ 91 | function totalSupply() public view virtual override returns (uint256) { 92 | return _totalSupply; 93 | } 94 | 95 | /** 96 | * @dev See {IERC20-balanceOf}. 97 | */ 98 | function balanceOf(address account) public view virtual override returns (uint256) { 99 | return _balances[account]; 100 | } 101 | 102 | /** 103 | * @dev See {IERC20-transfer}. 104 | * 105 | * Requirements: 106 | * 107 | * - `recipient` cannot be the zero address. 108 | * - the caller must have a balance of at least `amount`. 109 | */ 110 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) { 111 | _transfer(_msgSender(), recipient, amount); 112 | return true; 113 | } 114 | 115 | /** 116 | * @dev See {IERC20-allowance}. 117 | */ 118 | function allowance(address owner, address spender) public view virtual override returns (uint256) { 119 | return _allowances[owner][spender]; 120 | } 121 | 122 | /** 123 | * @dev See {IERC20-approve}. 124 | * 125 | * Requirements: 126 | * 127 | * - `spender` cannot be the zero address. 128 | */ 129 | function approve(address spender, uint256 amount) public virtual override returns (bool) { 130 | _approve(_msgSender(), spender, amount); 131 | return true; 132 | } 133 | 134 | /** 135 | * @dev See {IERC20-transferFrom}. 136 | * 137 | * Emits an {Approval} event indicating the updated allowance. This is not 138 | * required by the EIP. See the note at the beginning of {ERC20}. 139 | * 140 | * Requirements: 141 | * 142 | * - `sender` and `recipient` cannot be the zero address. 143 | * - `sender` must have a balance of at least `amount`. 144 | * - the caller must have allowance for ``sender``'s tokens of at least 145 | * `amount`. 146 | */ 147 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 148 | _transfer(sender, recipient, amount); 149 | 150 | uint256 currentAllowance = _allowances[sender][_msgSender()]; 151 | require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); 152 | _approve(sender, _msgSender(), currentAllowance - amount); 153 | 154 | return true; 155 | } 156 | 157 | /** 158 | * @dev Atomically increases the allowance granted to `spender` by the caller. 159 | * 160 | * This is an alternative to {approve} that can be used as a mitigation for 161 | * problems described in {IERC20-approve}. 162 | * 163 | * Emits an {Approval} event indicating the updated allowance. 164 | * 165 | * Requirements: 166 | * 167 | * - `spender` cannot be the zero address. 168 | */ 169 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { 170 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue); 171 | return true; 172 | } 173 | 174 | /** 175 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 176 | * 177 | * This is an alternative to {approve} that can be used as a mitigation for 178 | * problems described in {IERC20-approve}. 179 | * 180 | * Emits an {Approval} event indicating the updated allowance. 181 | * 182 | * Requirements: 183 | * 184 | * - `spender` cannot be the zero address. 185 | * - `spender` must have allowance for the caller of at least 186 | * `subtractedValue`. 187 | */ 188 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { 189 | uint256 currentAllowance = _allowances[_msgSender()][spender]; 190 | require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); 191 | _approve(_msgSender(), spender, currentAllowance - subtractedValue); 192 | 193 | return true; 194 | } 195 | 196 | /** 197 | * @dev Moves tokens `amount` from `sender` to `recipient`. 198 | * 199 | * This is internal function is equivalent to {transfer}, and can be used to 200 | * e.g. implement automatic token fees, slashing mechanisms, etc. 201 | * 202 | * Emits a {Transfer} event. 203 | * 204 | * Requirements: 205 | * 206 | * - `sender` cannot be the zero address. 207 | * - `recipient` cannot be the zero address. 208 | * - `sender` must have a balance of at least `amount`. 209 | */ 210 | function _transfer(address sender, address recipient, uint256 amount) internal virtual { 211 | require(sender != address(0), "ERC20: transfer from the zero address"); 212 | require(recipient != address(0), "ERC20: transfer to the zero address"); 213 | 214 | _beforeTokenTransfer(sender, recipient, amount); 215 | 216 | uint256 senderBalance = _balances[sender]; 217 | require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); 218 | _balances[sender] = senderBalance - amount; 219 | _balances[recipient] += amount; 220 | 221 | emit Transfer(sender, recipient, amount); 222 | } 223 | 224 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 225 | * the total supply. 226 | * 227 | * Emits a {Transfer} event with `from` set to the zero address. 228 | * 229 | * Requirements: 230 | * 231 | * - `to` cannot be the zero address. 232 | */ 233 | function _mint(address account, uint256 amount) internal virtual { 234 | require(account != address(0), "ERC20: mint to the zero address"); 235 | 236 | _beforeTokenTransfer(address(0), account, amount); 237 | 238 | _totalSupply += amount; 239 | _balances[account] += amount; 240 | emit Transfer(address(0), account, amount); 241 | } 242 | 243 | /** 244 | * @dev Destroys `amount` tokens from `account`, reducing the 245 | * total supply. 246 | * 247 | * Emits a {Transfer} event with `to` set to the zero address. 248 | * 249 | * Requirements: 250 | * 251 | * - `account` cannot be the zero address. 252 | * - `account` must have at least `amount` tokens. 253 | */ 254 | function _burn(address account, uint256 amount) internal virtual { 255 | require(account != address(0), "ERC20: burn from the zero address"); 256 | 257 | _beforeTokenTransfer(account, address(0), amount); 258 | 259 | uint256 accountBalance = _balances[account]; 260 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 261 | _balances[account] = accountBalance - amount; 262 | _totalSupply -= amount; 263 | 264 | emit Transfer(account, address(0), amount); 265 | } 266 | 267 | /** 268 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. 269 | * 270 | * This internal function is equivalent to `approve`, and can be used to 271 | * e.g. set automatic allowances for certain subsystems, etc. 272 | * 273 | * Emits an {Approval} event. 274 | * 275 | * Requirements: 276 | * 277 | * - `owner` cannot be the zero address. 278 | * - `spender` cannot be the zero address. 279 | */ 280 | function _approve(address owner, address spender, uint256 amount) internal virtual { 281 | require(owner != address(0), "ERC20: approve from the zero address"); 282 | require(spender != address(0), "ERC20: approve to the zero address"); 283 | 284 | _allowances[owner][spender] = amount; 285 | emit Approval(owner, spender, amount); 286 | } 287 | 288 | /** 289 | * @dev Hook that is called before any transfer of tokens. This includes 290 | * minting and burning. 291 | * 292 | * Calling conditions: 293 | * 294 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 295 | * will be to transferred to `to`. 296 | * - when `from` is zero, `amount` tokens will be minted for `to`. 297 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 298 | * - `from` and `to` are never both zero. 299 | * 300 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 301 | */ 302 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } 303 | } 304 | --------------------------------------------------------------------------------