diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 26b058d7..f48d8c64 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -29,6 +29,7 @@ "@react-native-community/netinfo": "11.3.1", "@react-native-picker/picker": "2.7.5", "@react-navigation/bottom-tabs": "^6.5.20", + "@react-navigation/drawer": "^6.7.2", "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.9.26", "@react-navigation/stack": "^6.3.29", @@ -39,6 +40,7 @@ "@tanstack/react-query": "^5.40.0", "@uniswap/sdk-core": "^5.3.1", "@walletconnect/react-native-compat": "^2.13.3", + "afk_nostr_sdk": "workspace:*", "axios": "^1.7.2", "buffer": "^6.0.3", "crypto-es": "^2.1.0", @@ -77,11 +79,10 @@ "react-native-svg": "15.2.0", "react-native-tab-view": "^3.5.2", "react-native-web": "~0.19.6", + "react-native-webview": "^13.10.5", "starknet": "6.9.0", "starknetkit": "^1.1.9", - "zustand": "^4.5.2", - "afk_nostr_sdk":"workspace:*" - + "zustand": "^4.5.2" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/apps/mobile/src/app/Router.tsx b/apps/mobile/src/app/Router.tsx index fbc3caab..efaa46dd 100644 --- a/apps/mobile/src/app/Router.tsx +++ b/apps/mobile/src/app/Router.tsx @@ -1,8 +1,8 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { useEffect, useState } from 'react'; -import { Dimensions, Platform, StyleSheet, View } from 'react-native'; +import { useEffect, useMemo, useState } from 'react'; +import { Dimensions, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'; import { Icon } from '../components'; import { useStyles, useTheme } from '../hooks'; import { CreateAccount } from '../screens/Auth/CreateAccount'; @@ -20,8 +20,6 @@ import { PostDetail } from '../screens/PostDetail'; import { Profile } from '../screens/Profile'; import { Search } from '../screens/Search'; import { Tips } from '../screens/Tips'; -// import { useAuth } from '../store/auth'; -// import { useAuth } from '../store/auth'; import { ThemedStyleSheet } from '../styles'; import { AuthStackParams, HomeBottomStackParams, MainStackParams, RootStackParams } from '../types'; import { retrievePublicKey } from '../utils/storage'; @@ -29,7 +27,10 @@ import Sidebar from '../modules/Layout/sidebar'; import { Defi } from '../screens/Defi'; import { Games } from '../screens/Games'; import { useAuth } from 'afk_nostr_sdk'; +import { createDrawerNavigator } from '@react-navigation/drawer'; +import { Navbar } from '../components/Navbar'; +const DrawerStack = createDrawerNavigator(); const RootStack = createNativeStackNavigator(); const AuthStack = createNativeStackNavigator(); const MainStack = createNativeStackNavigator(); @@ -153,25 +154,80 @@ const AuthNavigator: React.FC = () => { }; const MainNavigator: React.FC = () => { + const dimensions = useWindowDimensions(); + const isDesktop = useMemo(() => { + return dimensions.width >= 1024 + }, [dimensions]); // Adjust based on your breakpoint for desktop + + const theme = useTheme() + return ( - } + screenOptions={({ navigation }) => ({ + // headerShown:false, + header: () => , + headerStyle: { + backgroundColor: theme.theme.colors.background + }, + drawerType: isDesktop ? "permanent" : "front", + // drawerType:"permanent", + headerTintColor: theme.theme.colors.text, + overlayColor: isDesktop ? 'transparent' : theme.theme.colors.background, // Make sure overlay settings are correct + // swipeEdgeWidth: 0 + // drawerStyle: { + // width: 240, // Adjust width or other styling as necessary + // } + })} > - - - - - - - - - - - - - + {!isDesktop ? + + : + + + } + + + + + + + + + + + + + ); }; +// const MainNavigator: React.FC = () => { +// const dimensions = useWindowDimensions(); +// const isDesktop = dimensions.width >= 768; // Adjust based on your breakpoint for desktop +// return ( +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ); +// }; + const linking = { prefixes: [ // "home","search", "profile/:publicKey", "details/:id" @@ -222,7 +278,7 @@ export const Router: React.FC = () => { - {shouldShowSidebar && } + {/* {shouldShowSidebar && } */} diff --git a/apps/mobile/src/assets/icons.tsx b/apps/mobile/src/assets/icons.tsx index d0d878ae..43e471bc 100644 --- a/apps/mobile/src/assets/icons.tsx +++ b/apps/mobile/src/assets/icons.tsx @@ -580,3 +580,85 @@ export const MoonIcon: React.FC = (props) => ( // /> // // ); +{/* + + + + + */} + +// export const GameIcon: React.FC = (props) => { +// return ( +// +// +// +// ) +// } + +{/* + + + + + + + + + + + + + + + */} + +export const MenuIcon: React.FC = (props) => ( + + + {/* d="M87.2,56.7c1.1-2.2,1.8-4.6,1.8-7.2c0-6.6-4.2-12.3-10-14.5c0,0,0,0,0,0c0-11.6-9.4-21-21-21 c-9.8,0-18,6.7-20.3,15.8c-1.5-0.5-3-0.8-4.7-0.8c-7.7,0-14,5.8-14.9,13.3C12.9,43.4,9,48,9,53.5C9,59.9,14.1,65,20.5,65 c0.2,0,0.4,0,0.5,0c0,0.2,0,0.3,0,0.5C21,76.8,30.2,86,41.5,86c6.4,0,12.2-3,15.9-7.6c2.2,2.2,5.2,3.6,8.6,3.6 c4.7,0,8.7-2.7,10.7-6.5c1.1,0.3,2.2,0.5,3.3,0.5c6.1,0,11-4.9,11-11C91,61.7,89.5,58.7,87.2,56.7z"> */} + + + + // + + /> + {/* d="M87.2,56.7c1.1-2.2,1.8-4.6,1.8-7.2c0-6.6-4.2-12.3-10-14.5c0,0,0,0,0,0c0-11.6-9.4-21-21-21 c-9.8,0-18,6.7-20.3,15.8c-1.5-0.5-3-0.8-4.7-0.8c-7.7,0-14,5.8-14.9,13.3C12.9,43.4,9,48,9,53.5C9,59.9,14.1,65,20.5,65 c0.2,0,0.4,0,0.5,0c0,0.2,0,0.3,0,0.5C21,76.8,30.2,86,41.5,86c6.4,0,12.2-3,15.9-7.6c2.2,2.2,5.2,3.6,8.6,3.6 c4.7,0,8.7-2.7,10.7-6.5c1.1,0.3,2.2,0.5,3.3,0.5c6.1,0,11-4.9,11-11C91,61.7,89.5,58.7,87.2,56.7z" */} + + {/* d="M87.2,56.7c1.1-2.2,1.8-4.6,1.8-7.2c0-6.6-4.2-12.3-10-14.5c0,0,0,0,0,0c0-11.6-9.4-21-21-21 c-9.8,0-18,6.7-20.3,15.8c-1.5-0.5-3-0.8-4.7-0.8c-7.7,0-14,5.8-14.9,13.3C12.9,43.4,9,48,9,53.5C9,59.9,14.1,65,20.5,65 c0.2,0,0.4,0,0.5,0c0,0.2,0,0.3,0,0.5C21,76.8,30.2,86,41.5,86c6.4,0,12.2-3,15.9-7.6c2.2,2.2,5.2,3.6,8.6,3.6 c4.7,0,8.7-2.7,10.7-6.5c1.1,0.3,2.2,0.5,3.3,0.5c6.1,0,11-4.9,11-11C91,61.7,89.5,58.7,87.2,56.7z" */} + + +); +// export const MenuIcon: React.FC = (props) => ( +// +// +// +// +// ); \ No newline at end of file diff --git a/apps/mobile/src/components/BubbleLive/index.tsx b/apps/mobile/src/components/BubbleLive/index.tsx new file mode 100644 index 00000000..282f3ce9 --- /dev/null +++ b/apps/mobile/src/components/BubbleLive/index.tsx @@ -0,0 +1,49 @@ +import {NDKEvent, NDKUserProfile} from '@nostr-dev-kit/ndk'; +import {useNavigation} from '@react-navigation/native'; +import {Image, ImageSourcePropType, Pressable, View} from 'react-native'; + +// import {useProfile} from '../../hooks'; +import {MainStackNavigationProps} from '../../types'; +import {Text} from '../Text'; +import styles from './styles'; +import {useProfile} from "afk_nostr_sdk" + +export type StoryProps = { + imageProps?: ImageSourcePropType; + name?: string; + event: NDKEvent; + profileProps?: NDKUserProfile; +}; + +export const BubbleLive: React.FC = ({imageProps, name, profileProps, event}) => { + const {data: profile} = useProfile({publicKey: event?.pubkey}); + const navigation = useNavigation(); + const handleNavigateToProfile = () => { + if (!event?.id) return; + navigation.navigate('Profile', {publicKey: event?.pubkey}); + }; + return ( + + + + {/* */} + + + + + + {profile?.name ?? profile?.nip05 ?? profile?.displayName ?? 'Anon AFK'} + + + + ); +}; diff --git a/apps/mobile/src/components/BubbleLive/styles.ts b/apps/mobile/src/components/BubbleLive/styles.ts new file mode 100644 index 00000000..4f0f1965 --- /dev/null +++ b/apps/mobile/src/components/BubbleLive/styles.ts @@ -0,0 +1,32 @@ +import {StyleSheet} from 'react-native'; +import {Spacing} from '../../styles'; + +export default StyleSheet.create({ + container: { + alignItems: 'center', + // width: 100, // Set a fixed width for each item + // height: 100, // Set a fixed height for each item + // justifyContent: 'center', + // // alignItems: 'center', + // marginHorizontal: 10, + // backgroundColor: '#ddd', + }, + + imageContainer: { + position: 'relative', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius:15 + }, + image: { + position: 'absolute', + width: 35, + height: 35, + borderRadius:15 + }, + + name: { + paddingTop: Spacing.xxsmall, + }, +}); diff --git a/apps/mobile/src/components/BubbleUser/index.tsx b/apps/mobile/src/components/BubbleUser/index.tsx index 57baedf8..86a262de 100644 --- a/apps/mobile/src/components/BubbleUser/index.tsx +++ b/apps/mobile/src/components/BubbleUser/index.tsx @@ -41,7 +41,7 @@ export const BubbleUser: React.FC = ({imageProps, name, profileProps - {profile?.name ?? profile?.nip05 ?? profile?.displayName ?? 'Anon AFK'} + {profile?.name ?? profile?.nip05 ?? profile?.displayName ?? 'Anon'} diff --git a/apps/mobile/src/components/Embed/EmbedWebsite.tsx b/apps/mobile/src/components/Embed/EmbedWebsite.tsx new file mode 100644 index 00000000..160ccc17 --- /dev/null +++ b/apps/mobile/src/components/Embed/EmbedWebsite.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Platform, StyleSheet, View } from 'react-native'; +import WebView from 'react-native-webview'; + +interface EmbedWebsiteInterface { + uri?: string +} +const EmbedWebsite = ({ uri }: EmbedWebsiteInterface) => { + + const isWeb = Platform.OS == "web" + + if (isWeb) { + return + } + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, +}); + +export default EmbedWebsite; diff --git a/apps/mobile/src/components/Embed/index.tsx b/apps/mobile/src/components/Embed/index.tsx new file mode 100644 index 00000000..b3d347c9 --- /dev/null +++ b/apps/mobile/src/components/Embed/index.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { Platform, StyleSheet, View, Text, Pressable, Image } from 'react-native'; +import WebView from 'react-native-webview'; +import { Button } from '../Button'; +import EmbedWebsite from './EmbedWebsite'; +import stylesheet from "./styles" +import { useStyles } from '../../hooks'; +import { Link } from '@react-navigation/native'; +import * as Linking from 'expo-linking'; +import { Avatar } from '../Avatar'; + +interface EmbedWebsiteInterface { + uri?: string + title?: string; + twitter?: string; + description?: string; + img?: string; +} +const EmbedCard = ({ uri, title, twitter, description, img }: EmbedWebsiteInterface) => { + const [isOpen, setIsOpen] = useState(false) + const styles = useStyles(stylesheet) + const handleOpen = () => { + setIsOpen(!isOpen) + } + + const handleGoTo = () => { + if(uri) { + Linking.openURL(uri) + } + } + return ( + + + + {img && + + } + + + {title && + {title} + } + + {description && + {description} + } + + + + + {/* */} + + + + + {isOpen && + + + + } + + + + ) +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, +}); + +export default EmbedCard; \ No newline at end of file diff --git a/apps/mobile/src/components/Embed/styles.ts b/apps/mobile/src/components/Embed/styles.ts new file mode 100644 index 00000000..885078b9 --- /dev/null +++ b/apps/mobile/src/components/Embed/styles.ts @@ -0,0 +1,49 @@ +import {StyleSheet} from 'react-native'; + +import {Spacing, ThemedStyleSheet} from '../../styles'; + +export default ThemedStyleSheet((theme) => ({ + container: { + backgroundColor: theme.colors.surface, + color: theme.colors.text, + padding:Spacing.medium, + marginHorizontal: Spacing.medium, + marginBottom:Spacing.normal, + borderRadius: 16, + borderBottomColor: theme.colors.divider, + }, + content: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: Spacing.xxsmall, + paddingHorizontal: Spacing.medium, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: theme.colors.divider, + }, + + logoContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + logo: { + width: 40, + height: 40, + marginRight: Spacing.xsmall, + }, + + title: { + flex: 1, + color:theme.colors.text, + + }, + text:{ + color:theme.colors.text + }, + + buttons: { + flexDirection: 'row', + alignItems: 'center', + }, +})); diff --git a/apps/mobile/src/components/Navbar/index.tsx b/apps/mobile/src/components/Navbar/index.tsx new file mode 100644 index 00000000..f09d1764 --- /dev/null +++ b/apps/mobile/src/components/Navbar/index.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native'; +import { useStyles } from '../../hooks'; + +import stylesheet from "./styles"; +import { Icon } from '../Icon'; +interface CustomHeaderInterface { + title?: string + navigation?: any + showLogo?: boolean; +} +export const Navbar = ({ title, navigation, showLogo }: CustomHeaderInterface) => { + const styles = useStyles(stylesheet) + // const navigation = useNavigation() + return ( + + {showLogo && ( + + + {/* */} + + )} + {title} + + navigation?.openDrawer() + } style={styles.burgerIcon}> + + + + ); +} \ No newline at end of file diff --git a/apps/mobile/src/components/Navbar/styles.ts b/apps/mobile/src/components/Navbar/styles.ts new file mode 100644 index 00000000..7cd9b11e --- /dev/null +++ b/apps/mobile/src/components/Navbar/styles.ts @@ -0,0 +1,37 @@ +import { Spacing, ThemedStyleSheet } from "../../styles"; + +export default ThemedStyleSheet((theme) => ({ + + + // const styles = StyleSheet.create({ + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + height: 60, + paddingHorizontal: 15, + backgroundColor: theme.colors.background, + color: theme.colors.text, + borderBottomWidth: 1, + // borderBottomColor: '#ddd', + }, + headerTitle: { + fontSize: 20, + fontWeight: 'bold', + color:theme.colors.text, + }, + burgerIcon: { + padding: 5, + }, + logoContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + logo: { + width: 40, + height: 40, + marginRight: Spacing.xsmall, + }, + + +})) \ No newline at end of file diff --git a/apps/mobile/src/components/Twitter/TwitterCard.tsx b/apps/mobile/src/components/Twitter/TwitterCard.tsx new file mode 100644 index 00000000..a32d68cc --- /dev/null +++ b/apps/mobile/src/components/Twitter/TwitterCard.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { StyleSheet, Dimensions } from 'react-native'; +import { WebView } from 'react-native-webview'; + +// const tweetId = '20'; // Example Tweet ID + + +interface TwitterCardInterface { + tweetId?:string; +} +const TwitterCard = ({tweetId}:TwitterCardInterface) => { + const tweetHtml = ` + + + + + + + + + `; + + return ( + + ); +}; + +const styles = StyleSheet.create({ + container: { + width: Dimensions.get('window').width, + height: 300, // Adjust height accordingly + }, +}); + +export default TwitterCard; diff --git a/apps/mobile/src/components/Twitter/index.tsx b/apps/mobile/src/components/Twitter/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/apps/mobile/src/components/UserCard/index.tsx b/apps/mobile/src/components/UserCard/index.tsx new file mode 100644 index 00000000..86a262de --- /dev/null +++ b/apps/mobile/src/components/UserCard/index.tsx @@ -0,0 +1,49 @@ +import {NDKEvent, NDKUserProfile} from '@nostr-dev-kit/ndk'; +import {useNavigation} from '@react-navigation/native'; +import {Image, ImageSourcePropType, Pressable, View} from 'react-native'; + +// import {useProfile} from '../../hooks'; +import {MainStackNavigationProps} from '../../types'; +import {Text} from '../Text'; +import styles from './styles'; +import {useProfile} from "afk_nostr_sdk" + +export type StoryProps = { + imageProps?: ImageSourcePropType; + name?: string; + event: NDKEvent; + profileProps?: NDKUserProfile; +}; + +export const BubbleUser: React.FC = ({imageProps, name, profileProps, event}) => { + const {data: profile} = useProfile({publicKey: event?.pubkey}); + const navigation = useNavigation(); + const handleNavigateToProfile = () => { + if (!event?.id) return; + navigation.navigate('Profile', {publicKey: event?.pubkey}); + }; + return ( + + + + {/* */} + + + + + + {profile?.name ?? profile?.nip05 ?? profile?.displayName ?? 'Anon'} + + + + ); +}; diff --git a/apps/mobile/src/components/UserCard/styles.ts b/apps/mobile/src/components/UserCard/styles.ts new file mode 100644 index 00000000..4f0f1965 --- /dev/null +++ b/apps/mobile/src/components/UserCard/styles.ts @@ -0,0 +1,32 @@ +import {StyleSheet} from 'react-native'; +import {Spacing} from '../../styles'; + +export default StyleSheet.create({ + container: { + alignItems: 'center', + // width: 100, // Set a fixed width for each item + // height: 100, // Set a fixed height for each item + // justifyContent: 'center', + // // alignItems: 'center', + // marginHorizontal: 10, + // backgroundColor: '#ddd', + }, + + imageContainer: { + position: 'relative', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius:15 + }, + image: { + position: 'absolute', + width: 35, + height: 35, + borderRadius:15 + }, + + name: { + paddingTop: Spacing.xxsmall, + }, +}); diff --git a/apps/mobile/src/modules/Layout/sidebar/index.tsx b/apps/mobile/src/modules/Layout/sidebar/index.tsx index b8415cff..48b5942c 100644 --- a/apps/mobile/src/modules/Layout/sidebar/index.tsx +++ b/apps/mobile/src/modules/Layout/sidebar/index.tsx @@ -1,17 +1,26 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; import stylesheet from './styles'; import { useStyles, useTheme } from '../../../hooks'; import { Icon } from '../../../components/Icon'; import { useNavigation } from '@react-navigation/native'; -import { MainStackNavigationProps } from '../../../types'; +import { DrawerStackNavigationProps, MainStackNavigationProps } from '../../../types'; // import { useAuth } from '../../../store/auth'; import { useAuth } from 'afk_nostr_sdk'; +import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'; -const Sidebar = () => { +interface SidebarInterface { + // navigation:MainStackNavigationProps | DrawerNavigationHelpers + navigation:any +} +const Sidebar = ( + {navigation}:SidebarInterface + +) => { const styles = useStyles(stylesheet); const publicKey = useAuth((state) => state.publicKey); - const navigation = useNavigation() + // const navigation = useNavigation() + // const navigation = useNavigation() const handleNavigateProfile = () => { navigation.navigate("Profile", { publicKey: publicKey }); }; @@ -19,15 +28,26 @@ const Sidebar = () => { // const handleNavigateHome = () => { // navigation.navigate("Home"); // }; - const handleDefiScreen = () => { + const handleDefiScreen = () => { navigation.navigate("Defi"); }; const handleGameScreen = () => { navigation.navigate("Games"); }; const handleHomeScreen = () => { - navigation.navigate("Home"); + navigation.navigate("Feed"); + }; + + const handleTipsScreen = () => { + navigation.navigate("Tips"); }; + useEffect(() => { + const unsubscribe = navigation.addListener('drawerClose', () => { + // Code to handle drawer closing + }); + + return unsubscribe; + }, [navigation]); return ( @@ -64,7 +84,21 @@ const Sidebar = () => { style={{ backgroundColor: theme.theme.colors.background }} /> - Home + Feed + + + + + + + + Tips diff --git a/apps/mobile/src/modules/Layout/sidebar/styles.ts b/apps/mobile/src/modules/Layout/sidebar/styles.ts index ac96ce2a..fc200254 100644 --- a/apps/mobile/src/modules/Layout/sidebar/styles.ts +++ b/apps/mobile/src/modules/Layout/sidebar/styles.ts @@ -3,12 +3,12 @@ import { Spacing, ThemedStyleSheet } from "../../../styles"; export default ThemedStyleSheet((theme) => ({ container: {}, sidebar: { - width: 350, + width: "100%", height: '100%', backgroundColor: theme.colors.background, padding: 20, gap:1, - borderRight:"1" + // borderRight:"1" }, sidebarText: { fontSize: 18, diff --git a/apps/mobile/src/screens/Auth/CreateAccount.tsx b/apps/mobile/src/screens/Auth/CreateAccount.tsx index 3dbfe73d..a7ad4a09 100644 --- a/apps/mobile/src/screens/Auth/CreateAccount.tsx +++ b/apps/mobile/src/screens/Auth/CreateAccount.tsx @@ -96,6 +96,10 @@ export const CreateAccount: React.FC = ({navigatio Import account + + ); }; diff --git a/apps/mobile/src/screens/Auth/ImportKeys.tsx b/apps/mobile/src/screens/Auth/ImportKeys.tsx index 7932f5f7..1aa18921 100644 --- a/apps/mobile/src/screens/Auth/ImportKeys.tsx +++ b/apps/mobile/src/screens/Auth/ImportKeys.tsx @@ -90,6 +90,10 @@ export const ImportKeys: React.FC = ({navigation}) => > Import Account + + ); }; diff --git a/apps/mobile/src/screens/ChannelDetail/index.tsx b/apps/mobile/src/screens/ChannelDetail/index.tsx index d26b8922..8eb7ff73 100644 --- a/apps/mobile/src/screens/ChannelDetail/index.tsx +++ b/apps/mobile/src/screens/ChannelDetail/index.tsx @@ -13,12 +13,14 @@ export const ChannelDetail: React.FC = ({navigation, r return ( -
} - /* right={} */ + left={} + right={} + title="Channel" - /> + /> */} ); diff --git a/apps/mobile/src/screens/ChannelsFeed/index.tsx b/apps/mobile/src/screens/ChannelsFeed/index.tsx index d3836ca6..3433f472 100644 --- a/apps/mobile/src/screens/ChannelsFeed/index.tsx +++ b/apps/mobile/src/screens/ChannelsFeed/index.tsx @@ -18,7 +18,7 @@ export const ChannelsFeed: React.FC = ({navigation}) => source={require('../../assets/feed-background-afk.png')} resizeMode="cover" /> -
+ {/*
*/} Back diff --git a/apps/mobile/src/screens/Defi/index.tsx b/apps/mobile/src/screens/Defi/index.tsx index c5a4f572..38a9670d 100644 --- a/apps/mobile/src/screens/Defi/index.tsx +++ b/apps/mobile/src/screens/Defi/index.tsx @@ -35,7 +35,8 @@ export const Defi: React.FC = ({ navigation }) => { addScreenNavigation={false} > */} - Coming soon + DeFi, Ramp and more soon + Stay tuned for the AFK Fi diff --git a/apps/mobile/src/screens/Defi/styles.ts b/apps/mobile/src/screens/Defi/styles.ts index 5bb3943b..517bfd0a 100644 --- a/apps/mobile/src/screens/Defi/styles.ts +++ b/apps/mobile/src/screens/Defi/styles.ts @@ -5,6 +5,9 @@ import {Spacing, ThemedStyleSheet, Typography} from '../../styles'; export default ThemedStyleSheet((theme) => ({ container: { flex: 1, + backgroundColor: theme.colors.background, + color: theme.colors.text, + }, header: { @@ -19,10 +22,14 @@ export default ThemedStyleSheet((theme) => ({ paddingVertical: Spacing.small, paddingHorizontal: Spacing.xsmall, }, - content: { flex: 1, backgroundColor: theme.colors.background, + color:theme.colors.text, + margin:Spacing.pagePadding + }, + text: { + color:theme.colors.text, }, form: { flex: 1, diff --git a/apps/mobile/src/screens/Feed/index.tsx b/apps/mobile/src/screens/Feed/index.tsx index b802805e..37424c0f 100644 --- a/apps/mobile/src/screens/Feed/index.tsx +++ b/apps/mobile/src/screens/Feed/index.tsx @@ -35,7 +35,7 @@ export const Feed: React.FC = ({ navigation }) => { resizeMode="cover" /> -
+ {/*
*/} {/* = ({ navigation }) => { const theme = useTheme() const styles = useStyles(stylesheet); - const [selectedTab, setSelectedTab] = useState(SelectedTab.VIEW_KEYS_MARKETPLACE); + const [selectedTab, setSelectedTab] = useState(SelectedTab.SLINK); const handleTabSelected = (tab: string | SelectedTab, screen?: string) => { setSelectedTab(tab as any); if (screen) { navigation.navigate(screen as any); } }; - return ( @@ -35,7 +35,12 @@ export const Games: React.FC = ({ navigation }) => { addScreenNavigation={false} > - Moarr features coming soon + More features coming soon + {selectedTab == SelectedTab.SLINK && + <> + + + } {selectedTab == SelectedTab?.VIEW_KEYS_MARKETPLACE && <> diff --git a/apps/mobile/src/screens/Games/styles.ts b/apps/mobile/src/screens/Games/styles.ts index 20033b80..be5dc41e 100644 --- a/apps/mobile/src/screens/Games/styles.ts +++ b/apps/mobile/src/screens/Games/styles.ts @@ -1,4 +1,4 @@ -import {StyleSheet} from 'react-native'; +// import {StyleSheet} from 'react-native'; import {Spacing, ThemedStyleSheet, Typography} from '../../styles'; @@ -6,15 +6,16 @@ export default ThemedStyleSheet((theme) => ({ container: { flex: 1, color:theme.colors.text, - padding:3, + backgroundColor: theme.colors.background, + }, header: { flexDirection: 'row', justifyContent: 'space-between', backgroundColor: theme.colors.surface, paddingHorizontal: Spacing.pagePadding, - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: theme.colors.divider, + // borderBottomWidth: StyleSheet.hairlineWidth, + // borderBottomColor: theme.colors.divider, }, cancelButton: { paddingVertical: Spacing.small, @@ -23,7 +24,8 @@ export default ThemedStyleSheet((theme) => ({ content: { flex: 1, backgroundColor: theme.colors.background, - color: theme.colors.text, + margin:Spacing.pagePadding + // color: theme.colors.text, }, form: { flex: 1, diff --git a/apps/mobile/src/screens/KeysMarketplace/index.tsx b/apps/mobile/src/screens/KeysMarketplace/index.tsx index 069f6ea9..9d33bd6a 100644 --- a/apps/mobile/src/screens/KeysMarketplace/index.tsx +++ b/apps/mobile/src/screens/KeysMarketplace/index.tsx @@ -43,7 +43,7 @@ export const KeysMarketplace: React.FC = () => { return ( -
+ {/*
*/} Key pass for Starknet user Buy or sell the keys of content creator to get perks and rewards from them. diff --git a/apps/mobile/src/screens/PostDetail/index.tsx b/apps/mobile/src/screens/PostDetail/index.tsx index 0805589b..497dcf67 100644 --- a/apps/mobile/src/screens/PostDetail/index.tsx +++ b/apps/mobile/src/screens/PostDetail/index.tsx @@ -1,40 +1,40 @@ -import {useQueryClient} from '@tanstack/react-query'; -import {useState} from 'react'; -import {FlatList, RefreshControl, View} from 'react-native'; +import { useQueryClient } from '@tanstack/react-query'; +import { useState } from 'react'; +import { FlatList, RefreshControl, View } from 'react-native'; -import {Divider, Header, IconButton, Input, KeyboardFixedView} from '../../components'; -import { useStyles} from '../../hooks'; -import {useToast} from '../../hooks/modals'; -import {Post} from '../../modules/Post'; -import {PostDetailScreenProps} from '../../types'; +import { Divider, Header, IconButton, Input, KeyboardFixedView } from '../../components'; +import { useStyles } from '../../hooks'; +import { useToast } from '../../hooks/modals'; +import { Post } from '../../modules/Post'; +import { PostDetailScreenProps } from '../../types'; import stylesheet from './styles'; -import {useNote, useReplyNotes, useSendNote,} from 'afk_nostr_sdk'; +import { useNote, useReplyNotes, useSendNote, } from 'afk_nostr_sdk'; -export const PostDetail: React.FC = ({navigation, route}) => { - const {postId, post} = route.params; +export const PostDetail: React.FC = ({ navigation, route }) => { + const { postId, post } = route.params; const styles = useStyles(stylesheet); const [comment, setComment] = useState(''); const sendNote = useSendNote(); - const {data: note = post} = useNote({noteId: postId}); - const comments = useReplyNotes({noteId: note?.id}); + const { data: note = post } = useNote({ noteId: postId }); + const comments = useReplyNotes({ noteId: note?.id }); const queryClient = useQueryClient(); - const {showToast} = useToast(); + const { showToast } = useToast(); const handleSendComment = async () => { if (!comment || comment?.trim().length == 0) { - showToast({type: 'error', title: 'Please write your comment'}); + showToast({ type: 'error', title: 'Please write your comment' }); return; } sendNote.mutate( - {content: comment, tags: [['e', note?.id ?? '', '', 'root', note?.pubkey ?? '']]}, + { content: comment, tags: [['e', note?.id ?? '', '', 'root', note?.pubkey ?? '']] }, { onSuccess() { - showToast({type: 'success', title: 'Comment sent successfully'}); - queryClient.invalidateQueries({queryKey: ['replyNotes', note?.id]}); + showToast({ type: 'success', title: 'Comment sent successfully' }); + queryClient.invalidateQueries({ queryKey: ['replyNotes', note?.id] }); setComment(''); }, onError() { @@ -49,12 +49,12 @@ export const PostDetail: React.FC = ({navigation, route}) return ( -
} - /* right={} */ + right={} title="Conversation" - /> + /> */} @@ -73,7 +73,7 @@ export const PostDetail: React.FC = ({navigation, route}) } ItemSeparatorComponent={() => } - renderItem={({item}) => ( + renderItem={({ item }) => ( @@ -85,7 +85,7 @@ export const PostDetail: React.FC = ({navigation, route}) /> - + diff --git a/apps/mobile/src/screens/Search/index.tsx b/apps/mobile/src/screens/Search/index.tsx index 6bb98c29..e7262064 100644 --- a/apps/mobile/src/screens/Search/index.tsx +++ b/apps/mobile/src/screens/Search/index.tsx @@ -65,7 +65,7 @@ export const Search: React.FC = ({navigation}) => { resizeMode="cover" /> -
+ {/*
*/} {/* */} diff --git a/apps/mobile/src/screens/Slink/SlinksMap.tsx b/apps/mobile/src/screens/Slink/SlinksMap.tsx new file mode 100644 index 00000000..f9086c80 --- /dev/null +++ b/apps/mobile/src/screens/Slink/SlinksMap.tsx @@ -0,0 +1,52 @@ +import { KeyboardAvoidingView, FlatList } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Divider } from '../../components'; +import { useStyles, useTheme } from '../../hooks'; +import stylesheet from './styles'; +import EmbedCard from '../../components/Embed'; + +export const ECOSYSTEM_INTEGRATION = { + raize: { + title:"Raize", + uri: "https://www.raize.club", + twitter: "", + description:"Prediction Market" + }, + ekubo: { + uri: "https://app.ekubo.org/", + title:"Ekubo", + twitter: "", + description:"DEX on Starknet" + + }, +} +export const SlinksMap: React.FC = () => { + const theme = useTheme() + const styles = useStyles(stylesheet); + + return ( + + + } + renderItem={({ item }) => { + // console.log("item", item) + return ( + + + + ) + }} + /> + + + + ); +}; diff --git a/apps/mobile/src/screens/Slink/index.tsx b/apps/mobile/src/screens/Slink/index.tsx new file mode 100644 index 00000000..4e654b52 --- /dev/null +++ b/apps/mobile/src/screens/Slink/index.tsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; +import { KeyboardAvoidingView, View, Text } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { TextButton } from '../../components'; +import TabSelector from '../../components/TabSelector'; +import { useStyles, useTheme } from '../../hooks'; +import { GameSreenProps } from '../../types'; +import { SelectedTab, TABS_MENU } from '../../types/tab'; +import stylesheet from './styles'; +import { SlinksMap } from './SlinksMap'; + +export const Slink: React.FC = ({ navigation }) => { + const theme = useTheme() + const styles = useStyles(stylesheet); + const [selectedTab, setSelectedTab] = useState(SelectedTab.VIEW_KEYS_MARKETPLACE); + const handleTabSelected = (tab: string | SelectedTab, screen?: string) => { + setSelectedTab(tab as any); + if (screen) { + navigation.navigate(screen as any); + } + }; + + return ( + + + + Cancel + + + + + + + + + + ); +}; diff --git a/apps/mobile/src/screens/Slink/styles.ts b/apps/mobile/src/screens/Slink/styles.ts new file mode 100644 index 00000000..20033b80 --- /dev/null +++ b/apps/mobile/src/screens/Slink/styles.ts @@ -0,0 +1,68 @@ +import {StyleSheet} from 'react-native'; + +import {Spacing, ThemedStyleSheet, Typography} from '../../styles'; + +export default ThemedStyleSheet((theme) => ({ + container: { + flex: 1, + color:theme.colors.text, + padding:3, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + backgroundColor: theme.colors.surface, + paddingHorizontal: Spacing.pagePadding, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: theme.colors.divider, + }, + cancelButton: { + paddingVertical: Spacing.small, + paddingHorizontal: Spacing.xsmall, + }, + content: { + flex: 1, + backgroundColor: theme.colors.background, + color: theme.colors.text, + }, + form: { + flex: 1, + }, + input: { + flex: 1, + padding: Spacing.large, + color: theme.colors.inputText, + textAlignVertical: 'top', + fontSize: 16, + lineHeight: 24, + ...Typography.medium, + }, + imageContainer: { + padding: Spacing.pagePadding, + }, + image: { + width: '100%', + resizeMode: 'cover', + borderRadius: 8, + overflow: 'hidden', + }, + + buttons: { + position: 'relative', + }, + mediaButtons: { + flexDirection: 'row', + paddingHorizontal: Spacing.pagePadding, + paddingVertical: Spacing.small, + gap: Spacing.large, + alignItems: 'center', + }, + sendButton: { + position: 'absolute', + right: Spacing.pagePadding, + bottom: '110%', + }, + text:{ + color: theme.colors.text, + } +})); diff --git a/apps/mobile/src/screens/Tips/index.tsx b/apps/mobile/src/screens/Tips/index.tsx index 975a32d4..f43a59a2 100644 --- a/apps/mobile/src/screens/Tips/index.tsx +++ b/apps/mobile/src/screens/Tips/index.tsx @@ -26,7 +26,7 @@ export const Tips: React.FC = () => { return ( -
+ {/*
*/} { return ( -
+ {/*
*/} {selectedTab == SelectedTab.TIPS ? ( diff --git a/apps/mobile/src/types/routes.ts b/apps/mobile/src/types/routes.ts index 79d6b977..6265de33 100644 --- a/apps/mobile/src/types/routes.ts +++ b/apps/mobile/src/types/routes.ts @@ -1,9 +1,11 @@ import {NDKEvent} from '@nostr-dev-kit/ndk'; import {CompositeScreenProps, NavigatorScreenParams} from '@react-navigation/native'; +import {DrawerNavigationProp, DrawerScreenProps} from '@react-navigation/drawer'; import {NativeStackNavigationProp, NativeStackScreenProps} from '@react-navigation/native-stack'; export type RootStackParams = { MainStack: NavigatorScreenParams; + DrawerStack: DrawerScreenProps; AuthStack: NavigatorScreenParams; }; @@ -18,7 +20,7 @@ export type AuthStackParams = { }; export type MainStackParams = { - Home: NavigatorScreenParams; + // Home: NavigatorScreenParams; CreatePost: undefined; Profile: {publicKey: string}; PostDetail: {postId: string; post?: NDKEvent}; @@ -31,6 +33,10 @@ export type MainStackParams = { Defi: undefined; Games:undefined, KeysMarketplace:undefined; + Slinks:undefined; + Tips: undefined; + Home: undefined; + Feed: undefined; }; export type HomeBottomStackParams = { @@ -40,6 +46,8 @@ export type HomeBottomStackParams = { Tips: undefined; Search: undefined; Games:undefined, + Defi: undefined; + Home: undefined; // ChannelsFeed:undefined; // CreateChannel:undefined; @@ -86,7 +94,7 @@ export type NotificationsScreenProps = CompositeScreenProps< >; export type TipsScreenProps = CompositeScreenProps< - NativeStackScreenProps, + NativeStackScreenProps, NativeStackScreenProps >; @@ -95,10 +103,10 @@ export type SearchScreenProps = CompositeScreenProps< NativeStackScreenProps >; -export type GamesScreenProps = CompositeScreenProps< - NativeStackScreenProps, - NativeStackScreenProps ->; +// export type GamesScreenProps = CompositeScreenProps< +// NativeStackScreenProps, +// NativeStackScreenProps +// >; // export type CreateChannelScreenProps = CompositeScreenProps< @@ -161,13 +169,28 @@ export type DefiScreenProps = CompositeScreenProps< >; export type GameSreenProps = CompositeScreenProps< - NativeStackScreenProps, + NativeStackScreenProps, NativeStackScreenProps >; - export type KeysMarketplaceSreenProps = CompositeScreenProps< NativeStackScreenProps, NativeStackScreenProps >; +export type SlinkScreenProps = CompositeScreenProps< + NativeStackScreenProps, + NativeStackScreenProps +>; + + +// export type TipsMainScreenProps = CompositeScreenProps< +// NativeStackScreenProps, +// NativeStackScreenProps +// >; + + +// Drawer desktop stack + +export type DrawerStackNavigationProps = DrawerNavigationProp; + diff --git a/apps/mobile/src/types/tab.ts b/apps/mobile/src/types/tab.ts index f5e95fe4..7e73e32d 100644 --- a/apps/mobile/src/types/tab.ts +++ b/apps/mobile/src/types/tab.ts @@ -10,6 +10,8 @@ export enum SelectedTab { LAUNCH_TOKEN_UNRUGGABLE, VIEW_KEYS_MARKETPLACE, LAUNCH_TOKEN_PUMP, + SLINK, + } @@ -83,6 +85,11 @@ export const TABS_FORM_CREATE: {screen?: string; title: string; tab: SelectedTab export const TABS_MENU: {screen?: string; title: string; tab: SelectedTab}[] = [ + { + title: 'Slink', + screen: 'Slink', + tab: SelectedTab.SLINK, + }, { title: 'Keys', screen: 'KeysMarketplace', diff --git a/onchain/src/keys/keys.cairo b/onchain/src/keys/keys.cairo index 863a6415..7a4cb08f 100644 --- a/onchain/src/keys/keys.cairo +++ b/onchain/src/keys/keys.cairo @@ -1,17 +1,13 @@ // use social::request::{SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode, Signature}; // use afk::request::{SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode, Signature}; use afk::social::request::{SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode, Signature}; -// pub use social::request; - use afk::types::keys_types::{ KeysBonding, KeysBondingImpl, MINTER_ROLE, ADMIN_ROLE, StoredName, BuyKeys, SellKeys, CreateKeys, KeysUpdated, TokenQuoteBuyKeys, Keys, SharesKeys, BondingType, get_linear_price, }; use starknet::ContractAddress; - type NostrPublicKey = u256; - #[derive(Clone, Debug, Drop, Serde)] pub struct LinkedNostrAddress { pub starknet_address: ContractAddress @@ -46,8 +42,8 @@ pub trait IKeysMarketplace { fn set_protocol_fee_destination( ref self: TContractState, protocol_fee_destination: ContractAddress ); - fn instantiate_keys( - ref self: TContractState, // token_quote: TokenQuoteBuyKeys, // bonding_type: KeysMarketplace::BondingType, + fn instantiate_keys(ref self: TContractState,// token_quote: TokenQuoteBuyKeys, + // bonding_type: KeysMarketplace::BondingType, ); fn instantiate_keys_with_nostr( ref self: TContractState, request_nostr: SocialRequest @@ -176,15 +172,12 @@ mod KeysMarketplace { self.initial_key_price.write(init_token.initial_key_price); self.protocol_fee_destination.write(admin); - // self.protocol_fee_percent.write(MAX_FEE); - // self.creator_fee_percent.write(MAX_FEE_CREATOR); self.step_increase_linear.write(step_increase_linear); self.total_keys.write(0); self.protocol_fee_percent.write(MID_FEE_PROTOCOL); self.creator_fee_percent.write(MAX_FEE_CREATOR); } - // Public functions inside an impl block #[abi(embed_v0)] impl KeysMarketplace of super::IKeysMarketplace { @@ -218,6 +211,9 @@ mod KeysMarketplace { self.creator_fee_percent.write(creator_fee_percent); } + + // User functions + // Create keys for an user fn instantiate_keys(ref self: ContractState, // token_quote: TokenQuoteBuyKeys, // bonding_type: BondingType, diff --git a/onchain/src/launchpad.cairo b/onchain/src/launchpad.cairo new file mode 100644 index 00000000..bec7503f --- /dev/null +++ b/onchain/src/launchpad.cairo @@ -0,0 +1 @@ +pub mod launchpad; diff --git a/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index a8e6b2e1..8b001a40 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -1,7 +1,6 @@ use afk::types::launchpad_types::{ - KeysBonding, KeysBondingImpl, MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, - CreateToken, LaunchUpdated, TokenQuoteBuyKeys, TokenLaunch, SharesKeys, BondingType, - get_linear_price, Token, CreateLaunch + MINTER_ROLE, ADMIN_ROLE, StoredName, BuyToken, SellToken, CreateToken, LaunchUpdated, + TokenQuoteBuyKeys, TokenLaunch, SharesKeys, BondingType, Token, CreateLaunch }; use starknet::ContractAddress; @@ -10,53 +9,60 @@ pub trait ILaunchpadMarketplace { fn set_token(ref self: TContractState, token_quote: TokenQuoteBuyKeys); fn set_protocol_fee_percent(ref self: TContractState, protocol_fee_percent: u256); fn set_creator_fee_percent(ref self: TContractState, creator_fee_percent: u256); + fn set_dollar_paid_coin_creation(ref self: TContractState, dollar_price: u256); + fn set_dollar_paid_launch_creation(ref self: TContractState, dollar_price: u256); + fn set_dollar_paid_finish_percentage(ref self: TContractState, bps: u256); fn set_protocol_fee_destination( ref self: TContractState, protocol_fee_destination: ContractAddress ); - fn instantiate_keys( - ref self: TContractState, // token_quote: TokenQuoteBuyKeys, // bonding_type: LaunchpadMarketplace::BondingType, - ); + fn create_token( - ref self: TContractState, symbol: felt252, ticker: felt252, initial_supply: u256 - // token_quote: TokenQuoteBuyKeys, - // bonding_type: LaunchpadMarketplace::BondingType, - ); - fn launch_token( ref self: TContractState, symbol: felt252, - ticker: felt252, // token_quote: TokenQuoteBuyKeys, - // bonding_type: LaunchpadMarketplace::BondingType, - ); - fn buy_keys(ref self: TContractState, address_user: ContractAddress, amount: u256); - fn sell_keys(ref self: TContractState, address_user: ContractAddress, amount: u256); + name: felt252, + initial_supply: u256, + contract_address_salt: felt252 + ) -> ContractAddress; + + fn create_and_launch_token( + ref self: TContractState, + symbol: felt252, + name: felt252, + initial_supply: u256, + contract_address_salt: felt252, + ) -> ContractAddress; + fn launch_token(ref self: TContractState, coin_address: ContractAddress); + fn buy_coin(ref self: TContractState, coin_address: ContractAddress, amount: u256); + fn sell_coin(ref self: TContractState, coin_address: ContractAddress, amount: u256); fn get_default_token(self: @TContractState,) -> TokenQuoteBuyKeys; fn get_price_of_supply_key( - self: @TContractState, address_user: ContractAddress, amount: u256, is_decreased: bool + self: @TContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256; fn get_key_of_user(self: @TContractState, key_user: ContractAddress,) -> TokenLaunch; fn get_share_key_of_user( self: @TContractState, owner: ContractAddress, key_user: ContractAddress, ) -> SharesKeys; - fn get_all_keys(self: @TContractState) -> Span; + fn get_all_launch(self: @TContractState) -> Span; } #[starknet::contract] mod LaunchpadMarketplace { use afk::erc20::{ERC20, IERC20Dispatcher, IERC20DispatcherTrait}; use core::num::traits::Zero; - - use openzeppelin::access::accesscontrol::{AccessControlComponent}; use openzeppelin::introspection::src5::SRC5Component; + use starknet::syscalls::deploy_syscall; use starknet::{ ContractAddress, get_caller_address, storage_access::StorageBaseAddress, - contract_address_const, get_block_timestamp, get_contract_address, + contract_address_const, get_block_timestamp, get_contract_address, ClassHash }; use super::{ - StoredName, BuyToken, SellToken, CreateToken, LaunchUpdated, SharesKeys, KeysBonding, - KeysBondingImpl, MINTER_ROLE, ADMIN_ROLE, BondingType, Token, TokenLaunch, - TokenQuoteBuyKeys, CreateLaunch + StoredName, BuyToken, SellToken, CreateToken, LaunchUpdated, SharesKeys, MINTER_ROLE, + ADMIN_ROLE, BondingType, Token, TokenLaunch, TokenQuoteBuyKeys, CreateLaunch }; + + const MAX_SUPPLY: u256 = 100_000_000; + const INITIAL_SUPPLY: u256 = MAX_SUPPLY / 4; const MAX_STEPS_LOOP: u256 = 100; const PAY_TO_LAUNCH: u256 = 1; @@ -85,16 +91,25 @@ mod LaunchpadMarketplace { #[storage] struct Storage { + coin_class_hash: ClassHash, + quote_tokens: LegacyMap::, + quote_token: ContractAddress, + threshold_liquidity_raised_amount: u256, + liquidity_raised_amount_in_dollar: u256, names: LegacyMap::, token_created: LegacyMap::, - keys_of_users: LegacyMap::, + launched_coins: LegacyMap::, + pumped_coins: LegacyMap::, shares_by_users: LegacyMap::<(ContractAddress, ContractAddress), SharesKeys>, bonding_type: LegacyMap::, - array_keys_of_users: LegacyMap::, + array_launched_coins: LegacyMap::, tokens_created: LegacyMap::, launch_created: LegacyMap::, is_tokens_buy_enable: LegacyMap::, default_token: TokenQuoteBuyKeys, + dollar_price_launch_pool: u256, + dollar_price_create_token: u256, + dollar_price_percentage: u256, initial_key_price: u256, protocol_fee_percent: u256, creator_fee_percent: u256, @@ -132,11 +147,13 @@ mod LaunchpadMarketplace { fn constructor( ref self: ContractState, admin: ContractAddress, - // init_token: TokenQuoteBuyKeys, initial_key_price: u256, token_address: ContractAddress, - step_increase_linear: u256 + step_increase_linear: u256, + coin_class_hash: ClassHash, + threshold_liquidity_raised_amount: u256, ) { + self.coin_class_hash.write(coin_class_hash); // AccessControl-related initialization self.accesscontrol.initializer(); self.accesscontrol._grant_role(MINTER_ROLE, admin); @@ -154,6 +171,7 @@ mod LaunchpadMarketplace { self.default_token.write(init_token.clone()); self.initial_key_price.write(init_token.initial_key_price); + self.threshold_liquidity_raised_amount.write(threshold_liquidity_raised_amount); self.protocol_fee_destination.write(admin); self.step_increase_linear.write(step_increase_linear); self.total_keys.write(0); @@ -161,7 +179,6 @@ mod LaunchpadMarketplace { self.creator_fee_percent.write(MIN_FEE_CREATOR); } - // Public functions inside an impl block #[abi(embed_v0)] impl LaunchpadMarketplace of super::ILaunchpadMarketplace { @@ -194,200 +211,127 @@ mod LaunchpadMarketplace { self.creator_fee_percent.write(creator_fee_percent); } + fn set_dollar_paid_coin_creation(ref self: ContractState, dollar_price: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.dollar_price_create_token.write(dollar_price); + } + + fn set_dollar_paid_launch_creation(ref self: ContractState, dollar_price: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.dollar_price_launch_pool.write(dollar_price); + } + + fn set_dollar_paid_finish_percentage(ref self: ContractState, bps: u256) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.dollar_price_percentage.write(bps); + } // Create keys for an user fn create_token( - ref self: ContractState, symbol: felt252, name: felt252, initial_supply: u256, - // token_quote: TokenQuoteBuyKeys, - // bonding_type: BondingType, - ) { + ref self: ContractState, + symbol: felt252, + name: felt252, + initial_supply: u256, + contract_address_salt: felt252 + ) -> ContractAddress { let caller = get_caller_address(); - let keys = self.keys_of_users.read(caller); - assert!(keys.owner.is_zero(), "key already created"); - let initial_key_price = self.initial_key_price.read(); - - let mut token_to_use = self.default_token.read(); - // Todo function with custom init token - // if self.is_custom_token_enable.read() { - // token_to_use = token_quote; - // } - // let bond_type = BondingType::Degens(10); - let bond_type = BondingType::Linear; - - // @TODO Deploy an ERC404 - - let token = Token { - token_address: token_address, - owner: caller, - symbol, - price: initial_key_price, - total_supply: initial_supply, - bonding_curve_type: Option::Some(bond_type), - created_at: get_block_timestamp(), - token_type: None, - }; - // Option for liquidity providing and Trading - let key = TokenLaunch { - owner: caller, - token_address: caller, // CREATE 404 - price: initial_key_price, - total_supply: 1, - // Todo price by pricetype after fix Enum instantiate - bonding_curve_type: Option::Some(bond_type), - // bonding_curve_type: BondingType, - created_at: get_block_timestamp(), - token_quote: token_to_use.clone(), - initial_key_price: token_to_use.initial_key_price, - }; - - let share_user = SharesKeys { - owner: get_caller_address(), - key_address: get_caller_address(), - amount_owned: 1, - amount_buy: 1, - amount_sell: 0, - created_at: get_block_timestamp(), - total_paid: 0 - }; - self.shares_by_users.write((get_caller_address(), get_caller_address()), share_user); - self.keys_of_users.write(get_caller_address(), key.clone()); - - let total_key = self.total_keys.read(); - if total_key == 0 { - self.total_keys.write(1); - self.array_keys_of_users.write(0, key); - } else { - self.total_keys.write(total_key + 1); - self.array_keys_of_users.write(total_key, key); - } + let token_address = self + ._create_token(caller, symbol, name, initial_supply, contract_address_salt); self .emit( CreateToken { caller: get_caller_address(), - key_user: get_caller_address(), - amount: 1, - price: 1, + token_address: token_address, + total_supply: initial_supply.clone(), + initial_supply } ); - } + token_address + } // Create keys for an user - fn launch_token( + fn create_and_launch_token( ref self: ContractState, symbol: felt252, - ticker: felt252, // token_quote: TokenQuoteBuyKeys, - // bonding_type: BondingType, - ) { + name: felt252, + initial_supply: u256, + contract_address_salt: felt252 + ) -> ContractAddress { let caller = get_caller_address(); - let keys = self.keys_of_users.read(caller); - assert!(keys.owner.is_zero(), "key already created"); - let initial_key_price = self.initial_key_price.read(); - - let mut token_to_use = self.default_token.read(); - // Todo function with custom init token - // if self.is_custom_token_enable.read() { - // token_to_use = token_quote; - // } - // let bond_type = BondingType::Degens(10); - let bond_type = BondingType::Linear; - - // @TODO Deploy an ERC404 - // Option for liquidity providing and Trading - let key = TokenLaunch { - owner: caller, - token_address: caller, // CREATE 404 - price: initial_key_price, - total_supply: 1, - // Todo price by pricetype after fix Enum instantiate - bonding_curve_type: Option::Some(bond_type), - // bonding_curve_type: BondingType, - created_at: get_block_timestamp(), - token_quote: token_to_use.clone(), - initial_key_price: token_to_use.initial_key_price, - }; + let token_address = self + ._create_token(caller, symbol, name, initial_supply, contract_address_salt); - let share_user = SharesKeys { - owner: get_caller_address(), - key_address: get_caller_address(), - amount_owned: 1, - amount_buy: 1, - amount_sell: 0, - created_at: get_block_timestamp(), - total_paid: 0 - }; - self.shares_by_users.write((get_caller_address(), get_caller_address()), share_user); - self.keys_of_users.write(get_caller_address(), key.clone()); - - let total_key = self.total_keys.read(); - if total_key == 0 { - self.total_keys.write(1); - self.array_keys_of_users.write(0, key); - } else { - self.total_keys.write(total_key + 1); - self.array_keys_of_users.write(total_key, key); - } self .emit( CreateToken { caller: get_caller_address(), - key_user: get_caller_address(), - amount: 1, - price: 1, + token_address: token_address, + total_supply: initial_supply.clone(), + initial_supply } ); + + + self._launch_token(token_address); + + token_address } - // fn liquidity_token() { + // Create keys for an user + fn launch_token( + ref self: ContractState, + coin_address: ContractAddress + ) { // Todo function with custom init token - // } - fn buy_keys(ref self: ContractState, address_user: ContractAddress, amount: u256) { - let caller = get_caller_address(); - let old_keys = self.keys_of_users.read(address_user); - assert!(!old_keys.owner.is_zero(), "key not found"); - let initial_key_price = self.initial_key_price.read(); - assert!(amount <= MAX_STEPS_LOOP, "max step loop"); + self._launch_token(coin_address); + + } + + + fn buy_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { + let old_launch = self.launched_coins.read(coin_address); + assert!(!old_launch.owner.is_zero(), "coin not found"); // TODO erc20 token transfer - let key_token_address = old_keys.token_address; - let total_supply = old_keys.total_supply; - let token_quote = old_keys.token_quote.clone(); + let token_quote = old_launch.token_quote.clone(); let quote_token_address = token_quote.token_address.clone(); let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; let protocol_fee_percent = self.protocol_fee_percent.read(); let creator_fee_percent = self.creator_fee_percent.read(); - // Update keys with new values - let mut key = TokenLaunch { - owner: old_keys.owner, - token_address: old_keys.token_address, // CREATE 404 - created_at: old_keys.created_at, + // Update Launch pool with new values + let mut pool_coin = TokenLaunch { + owner: old_launch.owner, + token_address: old_launch.token_address, // CREATE 404 + created_at: old_launch.created_at, token_quote: token_quote, - price: old_keys.price, initial_key_price: token_quote.initial_key_price, - total_supply: old_keys.total_supply, - bonding_curve_type: old_keys.bonding_curve_type, - }; - // Todo price by pricetype after fix Enum instantiate - // Refactorize and opti - let total_price = self.get_price_of_supply_key(address_user, amount, false); - // println!("total price {}", total_price); - - let amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; - let amount_creator_fee = total_price * creator_fee_percent / BPS; + bonding_curve_type: old_launch.bonding_curve_type, + total_supply: old_launch.total_supply, + available_supply: old_launch.available_supply, + price: old_launch.price, + liquidity_raised: old_launch.liquidity_raised, + token_holded:old_launch.token_holded, + is_liquidity_launch:old_launch.is_liquidity_launch, - let remain_liquidity = total_price - amount_creator_fee - amount_protocol_fee; - let mut old_share = self.shares_by_users.read((get_caller_address(), address_user)); + }; + let total_price = self.get_price_of_supply_key(coin_address, amount, false); + println!("total price cal {:?}", total_price); + let amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; + // let amount_creator_fee = total_price * creator_fee_percent / BPS; + let remain_liquidity = total_price - amount_protocol_fee; + let mut old_share = self.shares_by_users.read((get_caller_address(), coin_address)); let mut share_user = old_share.clone(); if old_share.owner.is_zero() { share_user = SharesKeys { owner: get_caller_address(), - key_address: address_user, + key_address: coin_address, amount_owned: amount, amount_buy: amount, amount_sell: 0, @@ -401,66 +345,51 @@ mod LaunchpadMarketplace { share_user.amount_owned += amount; share_user.amount_buy += amount; } - key.price = total_price; - key.total_supply += amount; - self.shares_by_users.write((get_caller_address(), address_user), share_user.clone()); - - self.keys_of_users.write(address_user, key.clone()); + pool_coin.price = total_price; + pool_coin.total_supply += amount; + self.shares_by_users.write((get_caller_address(), coin_address), share_user.clone()); + self.launched_coins.write(coin_address, pool_coin.clone()); - // println!("caller {:?}", get_caller_address()); - - // // Transfer to Liquidity, Creator and Protocol - - // println!("transfer protocol fee {}", amount_protocol_fee.clone()); - // // TODO uncomment after allowance check script + println!("amount_protocol_fee {:?}", amount_protocol_fee); + println!("remain_liquidity {:?}", remain_liquidity); erc20 .transfer_from( get_caller_address(), self.protocol_fee_destination.read(), amount_protocol_fee ); - // println!("transfer liquidity {}", remain_liquidity.clone()); - // println!("transfer total price {}", total_price.clone()); erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); - // println!("amount_creator_fee fee {}", amount_creator_fee.clone()); - erc20.transfer_from(get_caller_address(), key.owner, amount_creator_fee); - self .emit( BuyToken { caller: get_caller_address(), - key_user: address_user, + key_user: coin_address, amount: amount, price: total_price, protocol_fee: amount_protocol_fee, - creator_fee: amount_creator_fee + creator_fee: 0 } ); } - fn sell_keys(ref self: ContractState, address_user: ContractAddress, amount: u256) { - let old_keys = self.keys_of_users.read(address_user); - assert!(!old_keys.owner.is_zero(), "key not found"); - let initial_key_price = self.initial_key_price.read(); + fn sell_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { + let old_pool = self.launched_coins.read(coin_address); + assert(!old_pool.owner.is_zero(), 'coin not found'); assert!(amount <= MAX_STEPS_LOOP, "max step loop"); // let caller = get_caller_address(); - let mut old_share = self.shares_by_users.read((get_caller_address(), address_user)); + let mut old_share = self.shares_by_users.read((get_caller_address(), coin_address)); let mut share_user = old_share.clone(); // Verify Amount owned assert!(old_share.amount_owned >= amount, "share too low"); - assert!(old_keys.total_supply >= amount, "above supply"); - - // assert!(old_keys.total_supply == 1 && old_keys.owner == caller, "cant sell owner key"); - // assert!(old_keys.total_supply - amount == 0 && old_keys.owner == caller, "cant sell owner key"); + assert!(old_pool.total_supply >= amount, "above supply"); // TODO erc20 token transfer - let token = old_keys.token_quote.clone(); - let key_token_address = old_keys.token_address; - let total_supply = old_keys.total_supply; - let token_quote = old_keys.token_quote.clone(); + let token = old_pool.token_quote.clone(); + let total_supply = old_pool.total_supply; + let token_quote = old_pool.token_quote.clone(); let quote_token_address = token_quote.token_address.clone(); let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; @@ -470,34 +399,34 @@ mod LaunchpadMarketplace { assert!(total_supply >= amount, "share > supply"); // Update keys with new values - let mut key = TokenLaunch { - owner: old_keys.owner, - token_address: old_keys.token_address, // CREATE 404 - created_at: old_keys.created_at, - token_quote: token, - price: old_keys.price, + let mut pool_update = TokenLaunch { + owner: old_pool.owner, + token_address: old_pool.token_address, // CREATE 404 + created_at: old_pool.created_at, + token_quote: token_quote, initial_key_price: token_quote.initial_key_price, - total_supply: old_keys.total_supply, - bonding_curve_type: old_keys.bonding_curve_type, + bonding_curve_type: old_pool.bonding_curve_type, + total_supply: old_pool.total_supply, + available_supply: old_pool.available_supply, + price: old_pool.price, + liquidity_raised: old_pool.liquidity_raised, + token_holded:old_pool.token_holded, + is_liquidity_launch:old_launch.is_liquidity_launch, }; - // Todo price by pricetype after fix Enum instantiate - // Refactorize and opti - - // FIX SELL amount to receive - let mut total_price = self.get_price_of_supply_key(address_user, amount, true); - // println!("total price {}", total_price); + let mut total_price = self.get_price_of_supply_key(coin_address, amount, true); - total_price -= key.initial_key_price.clone(); + total_price -= pool_update.initial_key_price.clone(); let amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; let amount_creator_fee = total_price * creator_fee_percent / BPS; - let remain_liquidity = total_price - amount_creator_fee - amount_protocol_fee; + // let remain_liquidity = total_price - amount_creator_fee - amount_protocol_fee; + let remain_liquidity = total_price - amount_protocol_fee; if old_share.owner.is_zero() { share_user = SharesKeys { owner: get_caller_address(), - key_address: address_user, + key_address: coin_address, amount_owned: amount, amount_buy: amount, amount_sell: amount, @@ -509,33 +438,28 @@ mod LaunchpadMarketplace { share_user.amount_owned -= amount; share_user.amount_sell += amount; } - key.price = total_price; + pool_update.price = total_price; // key.total_supply -= amount; - key.total_supply = key.total_supply - amount; + pool_update.total_supply = pool_update.total_supply - amount; + pool_update.liquidity_raised = pool_update.liquidity_raised + remain_liquidity; self .shares_by_users - .write((get_caller_address(), address_user.clone()), share_user.clone()); - self.keys_of_users.write(address_user.clone(), key.clone()); - - let contract_balance = erc20.balance_of(get_contract_address()); + .write((get_caller_address(), coin_address.clone()), share_user.clone()); + self.launched_coins.write(coin_address.clone(), pool_update.clone()); // Transfer to Liquidity, Creator and Protocol // println!("contract_balance {}", contract_balance); // println!("transfer creator fee {}", amount_creator_fee.clone()); // println!("transfer liquidity {}", remain_liquidity.clone()); - - erc20.transfer(key.owner, amount_creator_fee); - erc20.transfer(get_caller_address(), remain_liquidity); // println!("transfer protocol fee {}", amount_protocol_fee.clone()); - // erc20.transfer(self.protocol_fee_destination.read(), amount_protocol_fee); self .emit( SellToken { caller: get_caller_address(), - key_user: address_user, + key_user: coin_address, amount: amount, price: total_price, protocol_fee: amount_protocol_fee, @@ -549,35 +473,29 @@ mod LaunchpadMarketplace { } fn get_price_of_supply_key( - self: @ContractState, address_user: ContractAddress, amount: u256, is_decreased: bool + self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256 { assert!(amount <= MAX_STEPS_LOOP, "max step loop"); - let key = self.keys_of_users.read(address_user); - let mut total_supply = key.total_supply.clone(); - let mut actual_supply = total_supply; - // let mut final_supply = total_supply; + let pool = self.launched_coins.read(coin_address); + let mut total_supply = pool.token_holded.clone(); let mut final_supply = total_supply + amount; - // if is_decreased { - // final_supply = total_supply - amount; - // } else { - // final_supply = total_supply + amount; - // } - - let mut actual_supply = total_supply; - let final_supply = total_supply + amount; - let mut price = key.price.clone(); - let mut total_price = price.clone(); - let mut initial_key_price = key.initial_key_price.clone(); - let step_increase_linear = key.token_quote.step_increase_linear.clone(); - let bonding_type = key.bonding_curve_type.clone(); + if is_decreased { + final_supply = total_supply - amount; + } else { + final_supply = total_supply + amount; + } + let mut actual_supply = total_supply; + let mut price = pool.price.clone(); + let mut initial_key_price = pool.initial_key_price.clone(); + let step_increase_linear = pool.token_quote.step_increase_linear.clone(); + let bonding_type = pool.bonding_curve_type.clone(); match bonding_type { Option::Some(x) => { match x { BondingType::Linear => { // println!("Linear curve {:?}", x); - let start_price = initial_key_price + (step_increase_linear * actual_supply); let end_price = initial_key_price @@ -588,20 +506,12 @@ mod LaunchpadMarketplace { // println!("total_price {}", total_price.clone()); total_price }, - // BondingType::Scoring => { 0 }, - // BondingType::Exponential => { 0 }, - // BondingType::Limited => { 0 }, - _ => { let start_price = initial_key_price + (step_increase_linear * actual_supply); let end_price = initial_key_price + (step_increase_linear * final_supply); let total_price = amount * (start_price + end_price) / 2; - - // println!("start_price {}", start_price.clone()); - // println!("end_price {}", end_price.clone()); - // println!("total_price {}", total_price.clone()); total_price }, } @@ -609,17 +519,14 @@ mod LaunchpadMarketplace { Option::None => { let start_price = initial_key_price + (step_increase_linear * actual_supply); let end_price = initial_key_price + (step_increase_linear * final_supply); - // println!("start_price {}", start_price.clone()); - // println!("end_price {}", end_price.clone()); let total_price = amount * (start_price + end_price) / 2; - // println!("total_price {}", total_price.clone()); total_price } } } fn get_key_of_user(self: @ContractState, key_user: ContractAddress,) -> TokenLaunch { - self.keys_of_users.read(key_user) + self.launched_coins.read(key_user) } fn get_share_key_of_user( @@ -628,13 +535,13 @@ mod LaunchpadMarketplace { self.shares_by_users.read((owner, key_user)) } - fn get_all_keys(self: @ContractState) -> Span { + fn get_all_launch(self: @ContractState) -> Span { let max_key_id = self.total_keys.read() + 1; let mut keys: Array = ArrayTrait::new(); let mut i = 0; //Since the stream id starts from 0 loop { if i >= max_key_id {} - let key = self.array_keys_of_users.read(i); + let key = self.array_launched_coins.read(i); if key.owner.is_zero() { break keys.span(); } @@ -652,6 +559,93 @@ mod LaunchpadMarketplace { return initial_price + (slope * supply); } + fn _create_token( + ref self: ContractState, + recipient: ContractAddress, + symbol: felt252, + name: felt252, + initial_supply: u256, + contract_address_salt: felt252 + ) -> ContractAddress { + let mut calldata = array![name.into(), symbol.into()]; + Serde::serialize(@initial_supply, ref calldata); + Serde::serialize(@recipient, ref calldata); + Serde::serialize(@18, ref calldata); + + let (token_address, _) = deploy_syscall( + self.coin_class_hash.read(), contract_address_salt, calldata.span(), false + ) + .unwrap(); + // .unwrap_syscall(); + println!("token address {:?}", token_address); + + let token = Token { + token_address: token_address, + owner: recipient, + name, + symbol, + total_supply: initial_supply, + initial_supply: initial_supply, + created_at: get_block_timestamp(), + token_type: Option::None, + }; + + self.token_created.write(token_address, token); + + token_address + } + + + fn _launch_token(ref self: ContractState, coin_address:ContractAddress) { + let caller = get_caller_address(); + let token = self.token_created.read(coin_address); + assert!(!token.owner.is_zero(), "not launch"); + let mut token_to_use = self.default_token.read(); + let mut quote_token_address = token_to_use.token_address.clone(); + + let bond_type = BondingType::Linear; + let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; + + let total_supply = erc20.total_supply(); + // // @TODO Deploy an ERC404 + // // Option for liquidity providing and Trading + let launch_token_pump = TokenLaunch { + owner: caller, + token_address: caller, // CREATE 404 + total_supply: total_supply, + available_supply: total_supply, + // Todo price by pricetype after fix Enum instantiate + bonding_curve_type: Option::Some(bond_type), + // bonding_curve_type: BondingType, + created_at: get_block_timestamp(), + token_quote: token_to_use.clone(), + initial_key_price: token_to_use.initial_key_price, + price: 0, + liquidity_raised: 0, + token_holded:0, + is_liquidity_launch:false, + + // token_holded:1 + }; + + // Send supply need to launch your coin + + let amount_needed = total_supply.clone(); + + // erc20.transfer_from(get_caller_address(), get_contract_address(), amount_needed); + self.launched_coins.write(coin_address, launch_token_pump.clone()); + + self + .emit( + CreateLaunch { + caller: get_caller_address(), + token_address: quote_token_address, + amount: 1, + price: 1, + } + ); + } + fn _calculate_total_cost( price: u256, actual_supply: u256, @@ -669,3 +663,232 @@ mod LaunchpadMarketplace { } } } + + +#[cfg(test)] +mod tests { + use afk::erc20::{ERC20, IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use afk::types::launchpad_types::{MINTER_ROLE, ADMIN_ROLE, TokenQuoteBuyKeys, BondingType}; + use core::array::SpanTrait; + use core::num::traits::Zero; + use core::traits::Into; + use openzeppelin::account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; + use openzeppelin::utils::serde::SerializedAppend; + + use snforge_std::{ + declare, ContractClass, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, + Event, EventAssertions, start_cheat_caller_address, cheat_caller_address_global, + stop_cheat_caller_address, stop_cheat_caller_address_global, start_cheat_block_timestamp + }; + use starknet::syscalls::deploy_syscall; + + // const INITIAL_KEY_PRICE:u256=1/100; + + use starknet::{ + ContractAddress, get_caller_address, storage_access::StorageBaseAddress, + get_block_timestamp, get_contract_address, ClassHash + }; + // use afk::keys::{IKeysMarketplaceDispatcher, IKeysMarketplaceDispatcherTrait}; + use super::{ILaunchpadMarketplaceDispatcher, ILaunchpadMarketplaceDispatcherTrait}; + + // const INITIAL_KEY_PRICE:u256=1/100; + const INITIAL_KEY_PRICE: u256 = 1; + const STEP_LINEAR_INCREASE: u256 = 1; + const THRESHOLD_LIQUIDITY: u256 = 10; + + fn request_fixture() -> (ContractAddress, IERC20Dispatcher, ILaunchpadMarketplaceDispatcher) { + // println!("request_fixture"); + let erc20_class = declare_erc20(); + let launch_class = declare_launchpad(); + request_fixture_custom_classes(erc20_class, launch_class) + } + + fn request_fixture_custom_classes( + erc20_class: ContractClass, launch_class: ContractClass + ) -> (ContractAddress, IERC20Dispatcher, ILaunchpadMarketplaceDispatcher) { + let sender_address: ContractAddress = 123.try_into().unwrap(); + let erc20 = deploy_erc20(erc20_class, 'USDC token', 'USDC', 1_000_000, sender_address); + let token_address = erc20.contract_address.clone(); + let keys = deploy_launchpad( + launch_class, + sender_address, + token_address.clone(), + INITIAL_KEY_PRICE, + STEP_LINEAR_INCREASE, + erc20_class.class_hash, + THRESHOLD_LIQUIDITY + ); + (sender_address, erc20, keys) + } + + fn declare_launchpad() -> ContractClass { + declare("LaunchpadMarketplace").unwrap() + } + + fn declare_erc20() -> ContractClass { + declare("ERC20").unwrap() + } + + fn deploy_launchpad( + class: ContractClass, + admin: ContractAddress, + token_address: ContractAddress, + initial_key_price: u256, + step_increase_linear: u256, + coin_class_hash: ClassHash, + threshold_liquidity: u256 + ) -> ILaunchpadMarketplaceDispatcher { + // println!("deploy marketplace"); + let mut calldata = array![admin.into()]; + calldata.append_serde(initial_key_price); + calldata.append_serde(token_address); + calldata.append_serde(step_increase_linear); + calldata.append_serde(coin_class_hash); + calldata.append_serde(threshold_liquidity); + let (contract_address, _) = class.deploy(@calldata).unwrap(); + ILaunchpadMarketplaceDispatcher { contract_address } + } + + fn deploy_erc20( + class: ContractClass, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress + ) -> IERC20Dispatcher { + let mut calldata = array![]; + + name.serialize(ref calldata); + symbol.serialize(ref calldata); + (2 * initial_supply).serialize(ref calldata); + recipient.serialize(ref calldata); + 18_u8.serialize(ref calldata); + + let (contract_address, _) = class.deploy(@calldata).unwrap(); + + IERC20Dispatcher { contract_address } + } + + fn SALT() -> felt252 { + 'salty'.try_into().unwrap() + } + + + // Constants + fn OWNER() -> ContractAddress { + 'owner'.try_into().unwrap() + } + + fn RECIPIENT() -> ContractAddress { + 'recipient'.try_into().unwrap() + } + + fn SPENDER() -> ContractAddress { + 'spender'.try_into().unwrap() + } + + fn ALICE() -> ContractAddress { + 'alice'.try_into().unwrap() + } + + fn BOB() -> ContractAddress { + 'bob'.try_into().unwrap() + } + + fn NAME() -> felt252 { + 'name'.try_into().unwrap() + } + + fn SYMBOL() -> felt252 { + 'symbol'.try_into().unwrap() + } + + // Math + fn pow_256(self: u256, mut exponent: u8) -> u256 { + if self.is_zero() { + return 0; + } + let mut result = 1; + let mut base = self; + + loop { + if exponent & 1 == 1 { + result = result * base; + } + + exponent = exponent / 2; + if exponent == 0 { + break result; + } + + base = base * base; + } + } + + + fn DEFAULT_INITIAL_SUPPLY() -> u256 { + 21_000_000 * pow_256(10, 18) + } + + #[test] + fn launchpad_end_to_end() { + let (sender_address, erc20, launchpad) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // Call a view function of the contract + // Check default token used + let default_token = launchpad.get_default_token(); + assert(default_token.token_address == erc20.contract_address, 'no default token'); + assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + + let token_address = launchpad + .create_token( + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), + ); + println!("test token_address {:?}", token_address); + + // Launch coin pool + // Send total supply + println!("launch token"); + + let total_supply=erc20.total_supply(); + println!("total_supply {:?}", total_supply); + erc20.approve(launchpad.contract_address, total_supply); + + + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance {}", allowance); + + let pool = launchpad.launch_token(token_address); + // Test buy coin + + println!("amount_to_paid", ); + + // println!("all_keys {:?}", all_keys); + let amount_to_paid = launchpad + .get_price_of_supply_key(token_address, amount_key_buy+1, false, // 1, + // BondingType::Basic, default_token.clone() + ); + println!("test amount_to_paid {:?}", amount_to_paid); + + start_cheat_caller_address(erc20.contract_address, sender_address); + + erc20.approve(launchpad.contract_address, amount_to_paid); + + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance {}", allowance); + stop_cheat_caller_address(erc20.contract_address); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + println!("buy coin", ); + + launchpad.buy_coin(token_address, amount_key_buy); + } +} diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index 6fc27aea..d2b2458f 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -1,11 +1,13 @@ pub mod bip340; pub mod erc20; pub mod keys; +pub mod launchpad; pub mod sha256; pub mod social; pub mod utils; pub mod types { pub mod keys_types; + pub mod launchpad_types; } // pub mod tests { // pub mod keys; diff --git a/onchain/src/tokens.cairo b/onchain/src/tokens.cairo new file mode 100644 index 00000000..5c766355 --- /dev/null +++ b/onchain/src/tokens.cairo @@ -0,0 +1 @@ +pub mod tokens; diff --git a/onchain/src/tokens/erc20.cairo b/onchain/src/tokens/erc20.cairo new file mode 100644 index 00000000..afe8f2f5 --- /dev/null +++ b/onchain/src/tokens/erc20.cairo @@ -0,0 +1,184 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ); + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256); + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: u256); + fn decrease_allowance( + ref self: TContractState, spender: ContractAddress, subtracted_value: u256 + ); +} + +#[starknet::contract] +pub mod ERC20 { + use core::num::traits::Zero; + use starknet::ContractAddress; + use starknet::contract_address_const; + use starknet::get_caller_address; + + #[storage] + struct Storage { + name: felt252, + symbol: felt252, + decimals: u8, + total_supply: u256, + balances: LegacyMap::, + allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + } + #[derive(Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256, + } + #[derive(Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress, + decimals: u8, + ) { + self.name.write(name); + self.symbol.write(symbol); + self.decimals.write(decimals); + assert(!recipient.is_zero(), 'ERC20: mint to the 0 address'); + self.total_supply.write(initial_supply); + self.balances.write(recipient, initial_supply); + self + .emit( + Event::Transfer( + Transfer { + from: contract_address_const::<0>(), to: recipient, value: initial_supply + } + ) + ); + } + + #[abi(embed_v0)] + impl IERC20Impl of super::IERC20 { + fn name(self: @ContractState) -> felt252 { + self.name.read() + } + + fn symbol(self: @ContractState) -> felt252 { + self.symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + self.decimals.read() + } + + fn total_supply(self: @ContractState) -> u256 { + self.total_supply.read() + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self.balances.read(account) + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.allowances.read((owner, spender)) + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) { + let sender = get_caller_address(); + self.transfer_helper(sender, recipient, amount); + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + let caller = get_caller_address(); + self.spend_allowance(sender, caller, amount); + self.transfer_helper(sender, recipient, amount); + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) { + let caller = get_caller_address(); + self.approve_helper(caller, spender, amount); + } + + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) { + let caller = get_caller_address(); + self + .approve_helper( + caller, spender, self.allowances.read((caller, spender)) + added_value + ); + } + + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) { + let caller = get_caller_address(); + self + .approve_helper( + caller, spender, self.allowances.read((caller, spender)) - subtracted_value + ); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn transfer_helper( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), 'ERC20: transfer from 0'); + assert(!recipient.is_zero(), 'ERC20: transfer to 0'); + self.balances.write(sender, self.balances.read(sender) - amount); + self.balances.write(recipient, self.balances.read(recipient) + amount); + self.emit(Transfer { from: sender, to: recipient, value: amount }); + } + fn spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + let current_allowance = self.allowances.read((owner, spender)); + assert(current_allowance >= amount, 'not enough allowance'); + self.allowances.write((owner, spender), current_allowance - amount); + } + + fn approve_helper( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + assert(!spender.is_zero(), 'ERC20: approve from 0'); + self.allowances.write((owner, spender), amount); + self.emit(Approval { owner, spender, value: amount }); + } + } +} + diff --git a/onchain/src/tokens/memecoin.cairo b/onchain/src/tokens/memecoin.cairo new file mode 100644 index 00000000..26c84056 --- /dev/null +++ b/onchain/src/tokens/memecoin.cairo @@ -0,0 +1,184 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ); + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256); + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: u256); + fn decrease_allowance( + ref self: TContractState, spender: ContractAddress, subtracted_value: u256 + ); +} + +#[starknet::contract] +pub mod Memecoin { + use core::num::traits::Zero; + use starknet::ContractAddress; + use starknet::contract_address_const; + use starknet::get_caller_address; + + #[storage] + struct Storage { + name: felt252, + symbol: felt252, + decimals: u8, + total_supply: u256, + balances: LegacyMap::, + allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + } + #[derive(Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256, + } + #[derive(Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress, + decimals: u8, + ) { + self.name.write(name); + self.symbol.write(symbol); + self.decimals.write(decimals); + assert(!recipient.is_zero(), 'ERC20: mint to the 0 address'); + self.total_supply.write(initial_supply); + self.balances.write(recipient, initial_supply); + self + .emit( + Event::Transfer( + Transfer { + from: contract_address_const::<0>(), to: recipient, value: initial_supply + } + ) + ); + } + + #[abi(embed_v0)] + impl IERC20Impl of super::IERC20 { + fn name(self: @ContractState) -> felt252 { + self.name.read() + } + + fn symbol(self: @ContractState) -> felt252 { + self.symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + self.decimals.read() + } + + fn total_supply(self: @ContractState) -> u256 { + self.total_supply.read() + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self.balances.read(account) + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.allowances.read((owner, spender)) + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) { + let sender = get_caller_address(); + self.transfer_helper(sender, recipient, amount); + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + let caller = get_caller_address(); + self.spend_allowance(sender, caller, amount); + self.transfer_helper(sender, recipient, amount); + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) { + let caller = get_caller_address(); + self.approve_helper(caller, spender, amount); + } + + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) { + let caller = get_caller_address(); + self + .approve_helper( + caller, spender, self.allowances.read((caller, spender)) + added_value + ); + } + + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) { + let caller = get_caller_address(); + self + .approve_helper( + caller, spender, self.allowances.read((caller, spender)) - subtracted_value + ); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn transfer_helper( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), 'ERC20: transfer from 0'); + assert(!recipient.is_zero(), 'ERC20: transfer to 0'); + self.balances.write(sender, self.balances.read(sender) - amount); + self.balances.write(recipient, self.balances.read(recipient) + amount); + self.emit(Transfer { from: sender, to: recipient, value: amount }); + } + fn spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + let current_allowance = self.allowances.read((owner, spender)); + assert(current_allowance >= amount, 'not enough allowance'); + self.allowances.write((owner, spender), current_allowance - amount); + } + + fn approve_helper( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + assert(!spender.is_zero(), 'ERC20: approve from 0'); + self.allowances.write((owner, spender), amount); + self.emit(Approval { owner, spender, value: amount }); + } + } +} + diff --git a/onchain/src/types/launchpad_types.cairo b/onchain/src/types/launchpad_types.cairo index 3fde93a7..86f05dec 100644 --- a/onchain/src/types/launchpad_types.cairo +++ b/onchain/src/types/launchpad_types.cairo @@ -35,6 +35,7 @@ pub struct Token { pub symbol: felt252, pub name: felt252, pub total_supply: u256, + pub initial_supply: u256, pub token_type: Option, pub created_at: u64, } @@ -43,12 +44,16 @@ pub struct Token { pub struct TokenLaunch { pub owner: ContractAddress, pub token_address: ContractAddress, - pub price: u256, pub initial_key_price: u256, + pub price: u256, + pub available_supply: u256, pub total_supply: u256, pub bonding_curve_type: Option, pub created_at: u64, - pub token_quote: TokenQuoteBuyKeys + pub token_quote: TokenQuoteBuyKeys, + pub liquidity_raised: u256, + pub token_holded:u256, + pub is_liquidity_launch:bool } #[derive(Drop, Serde, Copy, starknet::Store)] @@ -124,9 +129,9 @@ pub struct CreateToken { #[key] pub caller: ContractAddress, #[key] - pub key_user: ContractAddress, - pub amount: u256, - pub price: u256, + pub token_address: ContractAddress, + pub total_supply: u256, + pub initial_supply: u256 } #[derive(Drop, starknet::Event)] @@ -134,55 +139,15 @@ pub struct CreateLaunch { #[key] pub caller: ContractAddress, #[key] - pub key_user: ContractAddress, + pub token_address: ContractAddress, pub amount: u256, pub price: u256, } #[derive(Drop, starknet::Event)] -pub struct KeysUpdated { +pub struct LaunchUpdated { #[key] user: ContractAddress, supply: u256, price: u256 } - - -pub trait KeysBonding { - fn get_price(self: Keys, supply: u256) -> u256; -} - - -pub fn get_current_price(key: @Keys, supply: u256, amount_to_buy: u256) -> u256 { - let total_cost = 0; - total_cost -} - - -pub fn get_linear_price( // key: @Keys, -key: Keys, supply: u256, // amount_to_buy: u256 -) -> u256 { - let step_increase_linear = key.token_quote.step_increase_linear.clone(); - let initial_key_price = key.token_quote.initial_key_price.clone(); - let price_for_this_key = initial_key_price + (supply * step_increase_linear); - price_for_this_key -} - - -pub impl KeysBondingImpl of KeysBonding { - fn get_price(self: Keys, supply: u256) -> u256 { - match self.bonding_curve_type { - Option::Some(x) => { - match x { - BondingType::Linear => { get_linear_price(self, supply) }, - // BondingType::Scoring => { 0 }, - // BondingType::Exponential => { 0 }, - // BondingType::Limited => { 0 }, - - _ => { get_linear_price(self, supply) }, - } - }, - Option::None => { get_linear_price(self, supply) } - } - } -}