From d25d124c1779a428f728d3c54c7761675a0ece5c Mon Sep 17 00:00:00 2001 From: MSghais Date: Wed, 14 Aug 2024 12:14:00 +0200 Subject: [PATCH 1/4] ui + embed + drawer --- apps/mobile/package.json | 7 +- apps/mobile/src/app/Router.tsx | 90 +++++++++++++++---- .../src/components/Embed/EmbedWebsite.tsx | 33 +++++++ apps/mobile/src/components/Embed/index.tsx | 44 +++++++++ apps/mobile/src/components/Embed/styles.ts | 38 ++++++++ apps/mobile/src/components/Navbar/index.tsx | 34 +++++++ apps/mobile/src/components/Navbar/styles.ts | 37 ++++++++ .../src/components/Twitter/TwitterCard.tsx | 42 +++++++++ apps/mobile/src/components/Twitter/index.tsx | 0 .../src/modules/Layout/sidebar/index.tsx | 14 ++- .../src/modules/Layout/sidebar/styles.ts | 2 +- apps/mobile/src/screens/Auth/ImportKeys.tsx | 4 + .../src/screens/ChannelDetail/index.tsx | 10 ++- .../mobile/src/screens/ChannelsFeed/index.tsx | 2 +- apps/mobile/src/screens/Feed/index.tsx | 2 +- apps/mobile/src/screens/Games/index.tsx | 11 ++- .../src/screens/KeysMarketplace/index.tsx | 2 +- apps/mobile/src/screens/PostDetail/index.tsx | 46 +++++----- apps/mobile/src/screens/Search/index.tsx | 2 +- apps/mobile/src/screens/Slink/SlinksMap.tsx | 71 +++++++++++++++ apps/mobile/src/screens/Slink/index.tsx | 45 ++++++++++ apps/mobile/src/screens/Slink/styles.ts | 68 ++++++++++++++ apps/mobile/src/screens/Tips/index.tsx | 2 +- apps/mobile/src/screens/Whatever/index.tsx | 2 +- apps/mobile/src/types/routes.ts | 8 +- apps/mobile/src/types/tab.ts | 7 ++ 26 files changed, 563 insertions(+), 60 deletions(-) create mode 100644 apps/mobile/src/components/Embed/EmbedWebsite.tsx create mode 100644 apps/mobile/src/components/Embed/index.tsx create mode 100644 apps/mobile/src/components/Embed/styles.ts create mode 100644 apps/mobile/src/components/Navbar/index.tsx create mode 100644 apps/mobile/src/components/Navbar/styles.ts create mode 100644 apps/mobile/src/components/Twitter/TwitterCard.tsx create mode 100644 apps/mobile/src/components/Twitter/index.tsx create mode 100644 apps/mobile/src/screens/Slink/SlinksMap.tsx create mode 100644 apps/mobile/src/screens/Slink/index.tsx create mode 100644 apps/mobile/src/screens/Slink/styles.ts 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..1e352d91 100644 --- a/apps/mobile/src/app/Router.tsx +++ b/apps/mobile/src/app/Router.tsx @@ -2,7 +2,7 @@ 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 { Dimensions, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'; import { Icon } from '../components'; import { useStyles, useTheme } from '../hooks'; import { CreateAccount } from '../screens/Auth/CreateAccount'; @@ -29,7 +29,11 @@ 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 +157,79 @@ const AuthNavigator: React.FC = () => { }; const MainNavigator: React.FC = () => { + + const dimensions = useWindowDimensions(); + const isDesktop = dimensions.width >= 768; // Adjust based on your breakpoint for desktop + + + const theme = useTheme() return ( - } + // drawerType={isDesktop ? 'permanent' : 'front'} + + // openByDefault={isDesktop} + // defaultStatus={isDesktop ? "open" : "closed"} + // overlayColor="transparent" + screenOptions={({ navigation }) => ({ + // headerShown:false, + header: () => , + headerStyle: { + backgroundColor: theme.theme.colors.background + }, + drawerType:isDesktop ? "permanent" : "front", + headerTintColor: theme.theme.colors.text, + overlayColor: 'transparent', // Make sure overlay settings are correct + swipeEdgeWidth:0 + // drawerStyle: { + // width: 240, // Adjust width or other styling as necessary + // } + })} > - - - - - - - - - - - - - + + + + + + + + + + + + + ); }; +// 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 +280,7 @@ export const Router: React.FC = () => { - {shouldShowSidebar && } + {/* {shouldShowSidebar && } */} 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..d1adbacb --- /dev/null +++ b/apps/mobile/src/components/Embed/index.tsx @@ -0,0 +1,44 @@ +import React, { useState } from 'react'; +import { Platform, StyleSheet, View, Text } 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'; +interface EmbedWebsiteInterface { + uri?: string + title?: string; + twitter?: string; + description?: string; +} +const EmbedCard = ({ uri, title, twitter, description }: EmbedWebsiteInterface) => { + const [isOpen, setIsOpen] = useState(false) + const styles = useStyles(stylesheet) + const handleOpen = () => { + setIsOpen(!isOpen) + } + return ( + + + {title && + {title} + } + + + + {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..c5535987 --- /dev/null +++ b/apps/mobile/src/components/Embed/styles.ts @@ -0,0 +1,38 @@ +import {StyleSheet} from 'react-native'; + +import {Spacing, ThemedStyleSheet} from '../../styles'; + +export default ThemedStyleSheet((theme) => ({ + container: { + backgroundColor: theme.colors.surface, + }, + 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, + }, + + 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..13b41eb2 --- /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/modules/Layout/sidebar/index.tsx b/apps/mobile/src/modules/Layout/sidebar/index.tsx index b8415cff..03188ba6 100644 --- a/apps/mobile/src/modules/Layout/sidebar/index.tsx +++ b/apps/mobile/src/modules/Layout/sidebar/index.tsx @@ -7,11 +7,19 @@ import { useNavigation } from '@react-navigation/native'; import { 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 handleNavigateProfile = () => { navigation.navigate("Profile", { publicKey: publicKey }); }; @@ -19,7 +27,7 @@ const Sidebar = () => { // const handleNavigateHome = () => { // navigation.navigate("Home"); // }; - const handleDefiScreen = () => { + const handleDefiScreen = () => { navigation.navigate("Defi"); }; const handleGameScreen = () => { diff --git a/apps/mobile/src/modules/Layout/sidebar/styles.ts b/apps/mobile/src/modules/Layout/sidebar/styles.ts index ac96ce2a..3ed86c52 100644 --- a/apps/mobile/src/modules/Layout/sidebar/styles.ts +++ b/apps/mobile/src/modules/Layout/sidebar/styles.ts @@ -3,7 +3,7 @@ import { Spacing, ThemedStyleSheet } from "../../../styles"; export default ThemedStyleSheet((theme) => ({ container: {}, sidebar: { - width: 350, + width: "100%", height: '100%', backgroundColor: theme.colors.background, padding: 20, 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/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/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..668060b2 --- /dev/null +++ b/apps/mobile/src/screens/Slink/SlinksMap.tsx @@ -0,0 +1,71 @@ +import { useState } from 'react'; +import { KeyboardAvoidingView, View, Text, FlatList, RefreshControl } 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 { AllKeysComponent } from '../KeysMarketplace/AllKeysComponent'; +import EmbedWebsite from '../../components/Embed'; +import EmbedCard from '../../components/Embed'; + + +export const ECOSYSTEM_INTEGRATION = { + raize: { + uri: "https://www.raize.club", + twitter: "", + }, + ekubo: { + uri: "https://app.ekubo.org/", + twitter: "", + }, +} +export const SlinksMap: React.FC = () => { + const theme = useTheme() + const styles = useStyles(stylesheet); + + return ( + + + + } + renderItem={({ item }) => { + console.log("item", item) + return ( + // + + + // + + // + ) + }} + /> + + {/* {Object.fromEntries(ECOSYSTEM_INTEGRATION).map()} */} + {/* {Object.entries(ECOSYSTEM_INTEGRATION).map((e) => { + 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..bf6fe5a0 --- /dev/null +++ b/apps/mobile/src/screens/Slink/index.tsx @@ -0,0 +1,45 @@ +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 { AllKeysComponent } from '../KeysMarketplace/AllKeysComponent'; +import EmbedWebsite from '../../components/Embed'; +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..fe8e369c 100644 --- a/apps/mobile/src/types/routes.ts +++ b/apps/mobile/src/types/routes.ts @@ -31,6 +31,7 @@ export type MainStackParams = { Defi: undefined; Games:undefined, KeysMarketplace:undefined; + Slinks:undefined; }; export type HomeBottomStackParams = { @@ -40,6 +41,8 @@ export type HomeBottomStackParams = { Tips: undefined; Search: undefined; Games:undefined, + Defi: undefined; + Home: undefined; // ChannelsFeed:undefined; // CreateChannel:undefined; @@ -165,9 +168,12 @@ export type GameSreenProps = CompositeScreenProps< NativeStackScreenProps >; - export type KeysMarketplaceSreenProps = CompositeScreenProps< NativeStackScreenProps, NativeStackScreenProps >; +export type SlinkScreenProps = CompositeScreenProps< + NativeStackScreenProps, + NativeStackScreenProps +>; 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', From ba283aaad0ae48bdfecbde6c79ffcf6e35c34922 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Wed, 14 Aug 2024 14:34:52 +0200 Subject: [PATCH 2/4] drawer fixed + stack + ui --- apps/mobile/src/app/Router.tsx | 68 ++- apps/mobile/src/assets/icons.tsx | 82 +++ apps/mobile/src/components/Embed/index.tsx | 48 +- apps/mobile/src/components/Embed/styles.ts | 6 + apps/mobile/src/components/Navbar/index.tsx | 2 +- .../src/modules/Layout/sidebar/index.tsx | 30 +- .../src/modules/Layout/sidebar/styles.ts | 2 +- apps/mobile/src/screens/Defi/index.tsx | 3 +- apps/mobile/src/screens/Defi/styles.ts | 4 + apps/mobile/src/screens/Games/styles.ts | 8 +- apps/mobile/src/screens/Slink/SlinksMap.tsx | 43 +- apps/mobile/src/screens/Slink/index.tsx | 2 - apps/mobile/src/types/routes.ts | 31 +- onchain/src/launchpad.cairo | 1 + onchain/src/launchpad/launchpad.cairo | 555 +++++++++++++----- onchain/src/lib.cairo | 2 + onchain/src/tokens.cairo | 1 + onchain/src/tokens/memecoin.cairo | 184 ++++++ onchain/src/types/launchpad_types.cairo | 42 +- 19 files changed, 843 insertions(+), 271 deletions(-) create mode 100644 onchain/src/launchpad.cairo create mode 100644 onchain/src/tokens.cairo create mode 100644 onchain/src/tokens/memecoin.cairo diff --git a/apps/mobile/src/app/Router.tsx b/apps/mobile/src/app/Router.tsx index 1e352d91..efaa46dd 100644 --- a/apps/mobile/src/app/Router.tsx +++ b/apps/mobile/src/app/Router.tsx @@ -1,7 +1,7 @@ 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 { useEffect, useMemo, useState } from 'react'; import { Dimensions, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'; import { Icon } from '../components'; import { useStyles, useTheme } from '../hooks'; @@ -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'; @@ -32,7 +30,6 @@ 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(); @@ -157,42 +154,42 @@ const AuthNavigator: React.FC = () => { }; const MainNavigator: React.FC = () => { - const dimensions = useWindowDimensions(); - const isDesktop = dimensions.width >= 768; // Adjust based on your breakpoint for desktop + const isDesktop = useMemo(() => { + return dimensions.width >= 1024 + }, [dimensions]); // Adjust based on your breakpoint for desktop - const theme = useTheme() - return ( - } - // drawerType={isDesktop ? 'permanent' : 'front'} - - // openByDefault={isDesktop} - // defaultStatus={isDesktop ? "open" : "closed"} - // overlayColor="transparent" - screenOptions={({ navigation }) => ({ - // headerShown:false, - header: () => , - headerStyle: { - backgroundColor: theme.theme.colors.background - }, - drawerType:isDesktop ? "permanent" : "front", - headerTintColor: theme.theme.colors.text, - overlayColor: 'transparent', // Make sure overlay settings are correct - swipeEdgeWidth:0 - // drawerStyle: { - // width: 240, // Adjust width or other styling as necessary - // } - })} + 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 ? + + : + + + } @@ -204,6 +201,7 @@ const MainNavigator: React.FC = () => { + ); }; 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/Embed/index.tsx b/apps/mobile/src/components/Embed/index.tsx index d1adbacb..f164e276 100644 --- a/apps/mobile/src/components/Embed/index.tsx +++ b/apps/mobile/src/components/Embed/index.tsx @@ -1,33 +1,69 @@ import React, { useState } from 'react'; -import { Platform, StyleSheet, View, Text } from 'react-native'; +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 }: EmbedWebsiteInterface) => { +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} + {title} + } + + {description && + {description} } - + + + + + + + {isOpen && - + + + } diff --git a/apps/mobile/src/components/Embed/styles.ts b/apps/mobile/src/components/Embed/styles.ts index c5535987..3b6dd38c 100644 --- a/apps/mobile/src/components/Embed/styles.ts +++ b/apps/mobile/src/components/Embed/styles.ts @@ -5,6 +5,7 @@ import {Spacing, ThemedStyleSheet} from '../../styles'; export default ThemedStyleSheet((theme) => ({ container: { backgroundColor: theme.colors.surface, + color: theme.colors.text, }, content: { width: '100%', @@ -29,6 +30,11 @@ export default ThemedStyleSheet((theme) => ({ title: { flex: 1, + color:theme.colors.text, + + }, + text:{ + color:theme.colors.text }, buttons: { diff --git a/apps/mobile/src/components/Navbar/index.tsx b/apps/mobile/src/components/Navbar/index.tsx index 13b41eb2..f09d1764 100644 --- a/apps/mobile/src/components/Navbar/index.tsx +++ b/apps/mobile/src/components/Navbar/index.tsx @@ -27,7 +27,7 @@ export const Navbar = ({ title, navigation, showLogo }: CustomHeaderInterface) = navigation?.openDrawer() } style={styles.burgerIcon}> - + ); diff --git a/apps/mobile/src/modules/Layout/sidebar/index.tsx b/apps/mobile/src/modules/Layout/sidebar/index.tsx index 03188ba6..3f31e1e8 100644 --- a/apps/mobile/src/modules/Layout/sidebar/index.tsx +++ b/apps/mobile/src/modules/Layout/sidebar/index.tsx @@ -1,10 +1,10 @@ -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'; @@ -20,6 +20,7 @@ const Sidebar = ( const styles = useStyles(stylesheet); const publicKey = useAuth((state) => state.publicKey); // const navigation = useNavigation() + // const navigation = useNavigation() const handleNavigateProfile = () => { navigation.navigate("Profile", { publicKey: publicKey }); }; @@ -37,6 +38,17 @@ const Sidebar = ( navigation.navigate("Home"); }; + const handleTipsScreen = () => { + navigation.navigate("Tips"); + }; + useEffect(() => { + const unsubscribe = navigation.addListener('drawerClose', () => { + // Code to handle drawer closing + }); + + return unsubscribe; + }, [navigation]); + return ( AFK @@ -77,6 +89,20 @@ const Sidebar = ( + + + + Tips + + + + ({ backgroundColor: theme.colors.background, padding: 20, gap:1, - borderRight:"1" + // borderRight:"1" }, sidebarText: { fontSize: 18, 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..e1ac113b 100644 --- a/apps/mobile/src/screens/Defi/styles.ts +++ b/apps/mobile/src/screens/Defi/styles.ts @@ -23,6 +23,10 @@ export default ThemedStyleSheet((theme) => ({ content: { flex: 1, backgroundColor: theme.colors.background, + color:theme.colors.text, + }, + text: { + color:theme.colors.text, }, form: { flex: 1, diff --git a/apps/mobile/src/screens/Games/styles.ts b/apps/mobile/src/screens/Games/styles.ts index 20033b80..395ca763 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,15 @@ export default ThemedStyleSheet((theme) => ({ container: { flex: 1, color:theme.colors.text, - padding:3, + // padding:3, }, 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, diff --git a/apps/mobile/src/screens/Slink/SlinksMap.tsx b/apps/mobile/src/screens/Slink/SlinksMap.tsx index 668060b2..f9086c80 100644 --- a/apps/mobile/src/screens/Slink/SlinksMap.tsx +++ b/apps/mobile/src/screens/Slink/SlinksMap.tsx @@ -1,25 +1,23 @@ -import { useState } from 'react'; -import { KeyboardAvoidingView, View, Text, FlatList, RefreshControl } from 'react-native'; +import { KeyboardAvoidingView, FlatList } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { TextButton } from '../../components'; -import TabSelector from '../../components/TabSelector'; +import { Divider } from '../../components'; import { useStyles, useTheme } from '../../hooks'; -import { GameSreenProps } from '../../types'; -import { SelectedTab, TABS_MENU } from '../../types/tab'; import stylesheet from './styles'; -import { AllKeysComponent } from '../KeysMarketplace/AllKeysComponent'; -import EmbedWebsite from '../../components/Embed'; 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 = () => { @@ -28,43 +26,26 @@ export const SlinksMap: React.FC = () => { return ( - } + ItemSeparatorComponent={() => } renderItem={({ item }) => { - console.log("item", item) + // console.log("item", item) return ( - // - // - - // + ) }} /> - {/* {Object.fromEntries(ECOSYSTEM_INTEGRATION).map()} */} - {/* {Object.entries(ECOSYSTEM_INTEGRATION).map((e) => { - return( - - ) - })} */} - - {/* */} - ); diff --git a/apps/mobile/src/screens/Slink/index.tsx b/apps/mobile/src/screens/Slink/index.tsx index bf6fe5a0..4e654b52 100644 --- a/apps/mobile/src/screens/Slink/index.tsx +++ b/apps/mobile/src/screens/Slink/index.tsx @@ -7,8 +7,6 @@ import { useStyles, useTheme } from '../../hooks'; import { GameSreenProps } from '../../types'; import { SelectedTab, TABS_MENU } from '../../types/tab'; import stylesheet from './styles'; -import { AllKeysComponent } from '../KeysMarketplace/AllKeysComponent'; -import EmbedWebsite from '../../components/Embed'; import { SlinksMap } from './SlinksMap'; export const Slink: React.FC = ({ navigation }) => { diff --git a/apps/mobile/src/types/routes.ts b/apps/mobile/src/types/routes.ts index fe8e369c..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}; @@ -32,6 +34,9 @@ export type MainStackParams = { Games:undefined, KeysMarketplace:undefined; Slinks:undefined; + Tips: undefined; + Home: undefined; + Feed: undefined; }; export type HomeBottomStackParams = { @@ -89,7 +94,7 @@ export type NotificationsScreenProps = CompositeScreenProps< >; export type TipsScreenProps = CompositeScreenProps< - NativeStackScreenProps, + NativeStackScreenProps, NativeStackScreenProps >; @@ -98,10 +103,10 @@ export type SearchScreenProps = CompositeScreenProps< NativeStackScreenProps >; -export type GamesScreenProps = CompositeScreenProps< - NativeStackScreenProps, - NativeStackScreenProps ->; +// export type GamesScreenProps = CompositeScreenProps< +// NativeStackScreenProps, +// NativeStackScreenProps +// >; // export type CreateChannelScreenProps = CompositeScreenProps< @@ -164,7 +169,7 @@ export type DefiScreenProps = CompositeScreenProps< >; export type GameSreenProps = CompositeScreenProps< - NativeStackScreenProps, + NativeStackScreenProps, NativeStackScreenProps >; @@ -177,3 +182,15 @@ export type SlinkScreenProps = CompositeScreenProps< NativeStackScreenProps, NativeStackScreenProps >; + + +// export type TipsMainScreenProps = CompositeScreenProps< +// NativeStackScreenProps, +// NativeStackScreenProps +// >; + + +// Drawer desktop stack + +export type DrawerStackNavigationProps = DrawerNavigationProp; + 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..f002b394 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,21 +9,35 @@ 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 + ref self: TContractState, + symbol: felt252, + name: felt252, + initial_supply: u256, + contract_address_salt: felt252 // token_quote: TokenQuoteBuyKeys, // bonding_type: LaunchpadMarketplace::BondingType, ); - fn launch_token( + + fn create_and_launch_token( ref self: TContractState, symbol: felt252, - ticker: felt252, // token_quote: TokenQuoteBuyKeys, + name: felt252, + initial_supply: u256, + contract_address_salt: felt252, + // token_quote: TokenQuoteBuyKeys, + // bonding_type: LaunchpadMarketplace::BondingType, + ); + fn launch_token( + ref self: TContractState, + coin_address:ContractAddress // bonding_type: LaunchpadMarketplace::BondingType, ); fn buy_keys(ref self: TContractState, address_user: ContractAddress, amount: u256); @@ -37,7 +50,7 @@ pub trait ILaunchpadMarketplace { 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] @@ -48,14 +61,15 @@ mod LaunchpadMarketplace { 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_STEPS_LOOP: u256 = 100; const PAY_TO_LAUNCH: u256 = 1; @@ -85,6 +99,7 @@ mod LaunchpadMarketplace { #[storage] struct Storage { + coin_class_hash: ClassHash, names: LegacyMap::, token_created: LegacyMap::, keys_of_users: LegacyMap::, @@ -95,6 +110,9 @@ mod LaunchpadMarketplace { 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 +150,12 @@ 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, ) { + self.coin_class_hash.write(coin_class_hash); // AccessControl-related initialization self.accesscontrol.initializer(); self.accesscontrol._grant_role(MINTER_ROLE, admin); @@ -194,72 +213,38 @@ 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 ) { let caller = get_caller_address(); - let keys = self.keys_of_users.read(caller); - assert!(keys.owner.is_zero(), "key already created"); + println!("create token"); 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); - } + // @TODO Deploy an ERC404 or ERC20 + let token_address= self._create_token(caller, symbol, name, initial_supply,contract_address_salt); self .emit( @@ -272,62 +257,23 @@ mod LaunchpadMarketplace { ); } - - // Create keys for an user - fn launch_token( + // Create keys for an user + 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 ) { let caller = get_caller_address(); - let keys = self.keys_of_users.read(caller); - assert!(keys.owner.is_zero(), "key already created"); + println!("create token"); 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 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); - } + // @TODO Deploy an ERC404 or ERC20 + let token_address= self._create_token(caller, symbol, name, initial_supply,contract_address_salt); self .emit( @@ -340,20 +286,77 @@ mod LaunchpadMarketplace { ); } + // Create keys for an user + fn launch_token( + ref self: ContractState, + coin_address:ContractAddress + ) { + let caller = get_caller_address(); + 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 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, + // } + // ); + } + // fn liquidity_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"); // 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 quote_token_address = token_quote.token_address.clone(); let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; @@ -517,8 +520,6 @@ mod LaunchpadMarketplace { .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()); - // Transfer to Liquidity, Creator and Protocol // println!("contract_balance {}", contract_balance); // println!("transfer creator fee {}", amount_creator_fee.clone()); @@ -554,9 +555,6 @@ mod LaunchpadMarketplace { 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 mut final_supply = total_supply + amount; // if is_decreased { // final_supply = total_supply - amount; // } else { @@ -566,7 +564,6 @@ mod LaunchpadMarketplace { 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(); @@ -598,10 +595,6 @@ mod LaunchpadMarketplace { 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,10 +602,7 @@ 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 } } @@ -628,7 +618,7 @@ 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 @@ -652,6 +642,43 @@ mod LaunchpadMarketplace { return initial_price + (slope * supply); } + fn _create_token( + ref self: ContractState, + caller:ContractAddress, + symbol: felt252, + name: felt252, + initial_supply: u256, + contract_address_salt: felt252 + ) -> ContractAddress{ + // @TODO Deploy an ERC404 + // let mut calldata = array![caller.into(), name.into(), symbol.into()]; + let mut calldata = array![name.into(), symbol.into()]; + Serde::serialize(@initial_supply, ref calldata); + Serde::serialize(@caller, 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: caller, + name, + symbol, + total_supply: initial_supply, + created_at: get_block_timestamp(), + token_type: Option::None, + }; + + self.token_created.write(token_address, token); + + token_address + } + fn _calculate_total_cost( price: u256, actual_supply: u256, @@ -669,3 +696,251 @@ 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::traits::Into; + use openzeppelin::account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; + use openzeppelin::utils::serde::SerializedAppend; + use core::num::traits::Zero; + + 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; + + 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 + ); + (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, + ) -> 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); + 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); + erc20.approve(launchpad.contract_address, amount_key_buy); + // 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'); + + // Instantiate keys + // start_cheat_caller_address(key_address, sender_address); + stop_cheat_caller_address(erc20.contract_address); + + // println!("instantiate keys"); + start_cheat_caller_address(launchpad.contract_address, sender_address); + + launchpad + .create_token( + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), + ); + // launchpad.instantiate_keys(); + // println!("get all_keys"); + + // let mut all_keys = keys.get_all_launch(); + // let mut key_user = launchpad.get_key_of_user(sender_address); + // println!("test key_user.owner {:?}", key_user.owner); + // println!("test sender_address {:?}", sender_address); + // assert(key_user.owner == sender_address, 'not same owner'); + // // println!("all_keys {:?}", all_keys); + // // println!("all_keys {:?}", all_keys); + // let amount_to_paid = launchpad + // .get_price_of_supply_key(sender_address, amount_key_buy, false, // 1, + // // BondingType::Basic, default_token.clone() + // ); + // println!("test amount_to_paid {:?}", amount_to_paid); + + // // erc20.approve(keys.contract_address, amount_to_paid*2); + + // start_cheat_caller_address(erc20.contract_address, sender_address); + // // erc20.approve(keys.contract_address, amount_approve); + // 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); + // launchpad.buy_keys(sender_address, amount_key_buy); + + // let mut key_user = launchpad.get_key_of_user(sender_address); + // println!("test key_user total supply {:?}", key_user.total_supply); + + // // Buy others key + // stop_cheat_caller_address(launchpad.contract_address); + + // let amount_key_buy = 3_u256; + + // // println!("all_keys {:?}", all_keys); + // let amount_to_paid = launchpad + // .get_price_of_supply_key(sender_address, amount_key_buy, false, // 1, + // // BondingType::Basic, default_token.clone() + // ); + // 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); + // launchpad.buy_keys(sender_address, amount_key_buy); + // let mut key_user = launchpad.get_key_of_user(sender_address); + + // println!("test key_user total supply {:?}", key_user.total_supply); + } +} 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..362414bb --- /dev/null +++ b/onchain/src/tokens.cairo @@ -0,0 +1 @@ +pub mod tokens; \ No newline at end of file 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..2304775c 100644 --- a/onchain/src/types/launchpad_types.cairo +++ b/onchain/src/types/launchpad_types.cairo @@ -140,49 +140,9 @@ pub struct CreateLaunch { } #[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) } - } - } -} From 3a30a10e27aacbb9cc42895d7f5e16ba3b657824 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Wed, 14 Aug 2024 15:03:18 +0200 Subject: [PATCH 3/4] card slink + cairo fmt --- apps/mobile/src/components/Embed/index.tsx | 4 +- apps/mobile/src/components/Embed/styles.ts | 5 + .../src/modules/Layout/sidebar/index.tsx | 4 +- apps/mobile/src/screens/Defi/styles.ts | 5 +- apps/mobile/src/screens/Games/styles.ts | 6 +- onchain/src/launchpad/launchpad.cairo | 212 ++++++++---------- onchain/src/tokens.cairo | 2 +- 7 files changed, 115 insertions(+), 123 deletions(-) diff --git a/apps/mobile/src/components/Embed/index.tsx b/apps/mobile/src/components/Embed/index.tsx index f164e276..b3d347c9 100644 --- a/apps/mobile/src/components/Embed/index.tsx +++ b/apps/mobile/src/components/Embed/index.tsx @@ -55,13 +55,13 @@ const EmbedCard = ({ uri, title, twitter, description, img }: EmbedWebsiteInterf flex:1, flexDirection:"row" }}> - + {/* */} {isOpen && - + } diff --git a/apps/mobile/src/components/Embed/styles.ts b/apps/mobile/src/components/Embed/styles.ts index 3b6dd38c..885078b9 100644 --- a/apps/mobile/src/components/Embed/styles.ts +++ b/apps/mobile/src/components/Embed/styles.ts @@ -6,6 +6,11 @@ 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%', diff --git a/apps/mobile/src/modules/Layout/sidebar/index.tsx b/apps/mobile/src/modules/Layout/sidebar/index.tsx index 3f31e1e8..48b5942c 100644 --- a/apps/mobile/src/modules/Layout/sidebar/index.tsx +++ b/apps/mobile/src/modules/Layout/sidebar/index.tsx @@ -35,7 +35,7 @@ const Sidebar = ( navigation.navigate("Games"); }; const handleHomeScreen = () => { - navigation.navigate("Home"); + navigation.navigate("Feed"); }; const handleTipsScreen = () => { @@ -84,7 +84,7 @@ const Sidebar = ( style={{ backgroundColor: theme.theme.colors.background }} /> - Home + Feed diff --git a/apps/mobile/src/screens/Defi/styles.ts b/apps/mobile/src/screens/Defi/styles.ts index e1ac113b..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,11 +22,11 @@ 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, diff --git a/apps/mobile/src/screens/Games/styles.ts b/apps/mobile/src/screens/Games/styles.ts index 395ca763..be5dc41e 100644 --- a/apps/mobile/src/screens/Games/styles.ts +++ b/apps/mobile/src/screens/Games/styles.ts @@ -6,7 +6,8 @@ export default ThemedStyleSheet((theme) => ({ container: { flex: 1, color:theme.colors.text, - // padding:3, + backgroundColor: theme.colors.background, + }, header: { flexDirection: 'row', @@ -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/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index f002b394..7f7fcff4 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -22,8 +22,6 @@ pub trait ILaunchpadMarketplace { name: felt252, initial_supply: u256, contract_address_salt: felt252 - // token_quote: TokenQuoteBuyKeys, - // bonding_type: LaunchpadMarketplace::BondingType, ); fn create_and_launch_token( @@ -32,19 +30,16 @@ pub trait ILaunchpadMarketplace { name: felt252, initial_supply: u256, contract_address_salt: felt252, - // token_quote: TokenQuoteBuyKeys, - // bonding_type: LaunchpadMarketplace::BondingType, ); fn launch_token( ref self: TContractState, - coin_address:ContractAddress - // bonding_type: LaunchpadMarketplace::BondingType, + coin_address: ContractAddress ); - fn buy_keys(ref self: TContractState, address_user: ContractAddress, amount: u256); - fn sell_keys(ref self: TContractState, address_user: ContractAddress, amount: u256); + 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( @@ -57,8 +52,6 @@ pub trait ILaunchpadMarketplace { 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; @@ -66,7 +59,6 @@ mod LaunchpadMarketplace { ContractAddress, get_caller_address, storage_access::StorageBaseAddress, contract_address_const, get_block_timestamp, get_contract_address, ClassHash }; - use super::{ StoredName, BuyToken, SellToken, CreateToken, LaunchUpdated, SharesKeys, MINTER_ROLE, ADMIN_ROLE, BondingType, Token, TokenLaunch, TokenQuoteBuyKeys, CreateLaunch @@ -100,12 +92,17 @@ mod LaunchpadMarketplace { #[storage] struct Storage { coin_class_hash: ClassHash, + quote_tokens: LegacyMap::, + quote_token: ContractAddress, + 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::, @@ -180,7 +177,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 { @@ -239,12 +235,11 @@ mod LaunchpadMarketplace { let caller = get_caller_address(); println!("create token"); let initial_key_price = self.initial_key_price.read(); - let mut token_to_use = self.default_token.read(); let bond_type = BondingType::Linear; - // @TODO Deploy an ERC404 or ERC20 - let token_address= self._create_token(caller, symbol, name, initial_supply,contract_address_salt); + let token_address = self + ._create_token(caller, symbol, name, initial_supply, contract_address_salt); self .emit( @@ -257,8 +252,8 @@ mod LaunchpadMarketplace { ); } - // Create keys for an user - fn create_and_launch_token( + // Create keys for an user + fn create_and_launch_token( ref self: ContractState, symbol: felt252, name: felt252, @@ -273,7 +268,8 @@ mod LaunchpadMarketplace { let bond_type = BondingType::Linear; // @TODO Deploy an ERC404 or ERC20 - let token_address= self._create_token(caller, symbol, name, initial_supply,contract_address_salt); + let token_address = self + ._create_token(caller, symbol, name, initial_supply, contract_address_salt); self .emit( @@ -288,73 +284,68 @@ mod LaunchpadMarketplace { // Create keys for an user fn launch_token( - ref self: ContractState, - coin_address:ContractAddress - ) { - let caller = get_caller_address(); - 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 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); - // } + ref self: ContractState, coin_address: ContractAddress + ) { // 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 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.launched_coins.write(get_caller_address(), key.clone()); + + // let total_key = self.total_keys.read(); + // if total_key == 0 { + // self.total_keys.write(1); + // self.array_launched_coins.write(0, key); + // } else { + // self.total_keys.write(total_key + 1); + // self.array_launched_coins.write(total_key, key); + // } - // self - // .emit( - // CreateToken { - // caller: get_caller_address(), - // key_user: get_caller_address(), - // amount: 1, - // price: 1, - // } - // ); + // self + // .emit( + // CreateToken { + // caller: get_caller_address(), + // key_user: get_caller_address(), + // amount: 1, + // price: 1, + // } + // ); } // fn liquidity_token() { // } - fn buy_keys(ref self: ContractState, address_user: ContractAddress, amount: u256) { + fn buy_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { + let old_keys = self.launched_coins.read(coin_address); + assert!(!old_keys.owner.is_zero(), "key not found"); // TODO erc20 token transfer let token_quote = old_keys.token_quote.clone(); @@ -375,7 +366,7 @@ mod LaunchpadMarketplace { }; // Todo price by pricetype after fix Enum instantiate // Refactorize and opti - let total_price = self.get_price_of_supply_key(address_user, amount, false); + let total_price = self.get_price_of_supply_key(coin_address, amount, false); // println!("total price {}", total_price); let amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; @@ -383,14 +374,14 @@ mod LaunchpadMarketplace { 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 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, @@ -406,9 +397,9 @@ mod LaunchpadMarketplace { } key.price = total_price; key.total_supply += amount; - self.shares_by_users.write((get_caller_address(), address_user), share_user.clone()); + self.shares_by_users.write((get_caller_address(), coin_address), share_user.clone()); - self.keys_of_users.write(address_user, key.clone()); + self.launched_coins.write(coin_address, key.clone()); // println!("caller {:?}", get_caller_address()); @@ -433,7 +424,7 @@ mod LaunchpadMarketplace { .emit( BuyToken { caller: get_caller_address(), - key_user: address_user, + key_user: coin_address, amount: amount, price: total_price, protocol_fee: amount_protocol_fee, @@ -442,14 +433,13 @@ mod LaunchpadMarketplace { ); } - fn sell_keys(ref self: ContractState, address_user: ContractAddress, amount: u256) { - let old_keys = self.keys_of_users.read(address_user); + fn sell_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { + let old_keys = self.launched_coins.read(coin_address); 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"); // 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 @@ -461,7 +451,6 @@ mod LaunchpadMarketplace { // 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 quote_token_address = token_quote.token_address.clone(); @@ -483,12 +472,7 @@ mod LaunchpadMarketplace { 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 - - // 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(); @@ -500,7 +484,7 @@ mod LaunchpadMarketplace { share_user = SharesKeys { owner: get_caller_address(), - key_address: address_user, + key_address: coin_address, amount_owned: amount, amount_buy: amount, amount_sell: amount, @@ -517,8 +501,8 @@ mod LaunchpadMarketplace { key.total_supply = key.total_supply - amount; self .shares_by_users - .write((get_caller_address(), address_user.clone()), share_user.clone()); - self.keys_of_users.write(address_user.clone(), key.clone()); + .write((get_caller_address(), coin_address.clone()), share_user.clone()); + self.launched_coins.write(coin_address.clone(), key.clone()); // Transfer to Liquidity, Creator and Protocol // println!("contract_balance {}", contract_balance); @@ -536,7 +520,7 @@ mod LaunchpadMarketplace { .emit( SellToken { caller: get_caller_address(), - key_user: address_user, + key_user: coin_address, amount: amount, price: total_price, protocol_fee: amount_protocol_fee, @@ -550,10 +534,10 @@ 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 key = self.launched_coins.read(coin_address); let mut total_supply = key.total_supply.clone(); // if is_decreased { // final_supply = total_supply - amount; @@ -566,9 +550,7 @@ mod LaunchpadMarketplace { let mut price = key.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(); - match bonding_type { Option::Some(x) => { match x { @@ -609,7 +591,7 @@ mod LaunchpadMarketplace { } 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( @@ -624,7 +606,7 @@ mod LaunchpadMarketplace { 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(); } @@ -642,15 +624,15 @@ mod LaunchpadMarketplace { return initial_price + (slope * supply); } - fn _create_token( + fn _create_token( ref self: ContractState, - caller:ContractAddress, + caller: ContractAddress, symbol: felt252, name: felt252, initial_supply: u256, contract_address_salt: felt252 - ) -> ContractAddress{ - // @TODO Deploy an ERC404 + ) -> ContractAddress { + // @TODO Deploy an ERC404 // let mut calldata = array![caller.into(), name.into(), symbol.into()]; let mut calldata = array![name.into(), symbol.into()]; Serde::serialize(@initial_supply, ref calldata); @@ -703,10 +685,10 @@ 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 core::num::traits::Zero; use snforge_std::{ declare, ContractClass, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, @@ -914,7 +896,7 @@ mod tests { // stop_cheat_caller_address(erc20.contract_address); // start_cheat_caller_address(launchpad.contract_address, sender_address); - // launchpad.buy_keys(sender_address, amount_key_buy); + // launchpad.buy_coin(sender_address, amount_key_buy); // let mut key_user = launchpad.get_key_of_user(sender_address); // println!("test key_user total supply {:?}", key_user.total_supply); @@ -938,7 +920,7 @@ mod tests { // stop_cheat_caller_address(erc20.contract_address); // start_cheat_caller_address(launchpad.contract_address, sender_address); - // launchpad.buy_keys(sender_address, amount_key_buy); + // launchpad.buy_coin(sender_address, amount_key_buy); // let mut key_user = launchpad.get_key_of_user(sender_address); // println!("test key_user total supply {:?}", key_user.total_supply); diff --git a/onchain/src/tokens.cairo b/onchain/src/tokens.cairo index 362414bb..5c766355 100644 --- a/onchain/src/tokens.cairo +++ b/onchain/src/tokens.cairo @@ -1 +1 @@ -pub mod tokens; \ No newline at end of file +pub mod tokens; From 31f16816751fbcf336f993d1db6d578cd0b32482 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Wed, 14 Aug 2024 22:14:34 +0200 Subject: [PATCH 4/4] launch basic create token + ui --- .../src/components/BubbleLive/index.tsx | 49 +++ .../src/components/BubbleLive/styles.ts | 32 ++ .../src/components/BubbleUser/index.tsx | 2 +- apps/mobile/src/components/UserCard/index.tsx | 49 +++ apps/mobile/src/components/UserCard/styles.ts | 32 ++ .../mobile/src/screens/Auth/CreateAccount.tsx | 4 + onchain/src/keys/keys.cairo | 14 +- onchain/src/launchpad/launchpad.cairo | 400 ++++++++---------- onchain/src/tokens/erc20.cairo | 184 ++++++++ onchain/src/types/launchpad_types.cairo | 17 +- 10 files changed, 550 insertions(+), 233 deletions(-) create mode 100644 apps/mobile/src/components/BubbleLive/index.tsx create mode 100644 apps/mobile/src/components/BubbleLive/styles.ts create mode 100644 apps/mobile/src/components/UserCard/index.tsx create mode 100644 apps/mobile/src/components/UserCard/styles.ts create mode 100644 onchain/src/tokens/erc20.cairo 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/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/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/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/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index 7f7fcff4..8b001a40 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -22,7 +22,7 @@ pub trait ILaunchpadMarketplace { name: felt252, initial_supply: u256, contract_address_salt: felt252 - ); + ) -> ContractAddress; fn create_and_launch_token( ref self: TContractState, @@ -30,11 +30,8 @@ pub trait ILaunchpadMarketplace { name: felt252, initial_supply: u256, contract_address_salt: felt252, - ); - fn launch_token( - ref self: TContractState, - coin_address: ContractAddress - ); + ) -> 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; @@ -63,6 +60,9 @@ mod LaunchpadMarketplace { 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; @@ -94,7 +94,7 @@ mod LaunchpadMarketplace { coin_class_hash: ClassHash, quote_tokens: LegacyMap::, quote_token: ContractAddress, - liquidity_raised_amount: u256, + threshold_liquidity_raised_amount: u256, liquidity_raised_amount_in_dollar: u256, names: LegacyMap::, token_created: LegacyMap::, @@ -151,6 +151,7 @@ mod LaunchpadMarketplace { token_address: ContractAddress, step_increase_linear: u256, coin_class_hash: ClassHash, + threshold_liquidity_raised_amount: u256, ) { self.coin_class_hash.write(coin_class_hash); // AccessControl-related initialization @@ -170,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); @@ -231,13 +233,8 @@ mod LaunchpadMarketplace { name: felt252, initial_supply: u256, contract_address_salt: felt252 - ) { + ) -> ContractAddress { let caller = get_caller_address(); - println!("create token"); - let initial_key_price = self.initial_key_price.read(); - let mut token_to_use = self.default_token.read(); - let bond_type = BondingType::Linear; - // @TODO Deploy an ERC404 or ERC20 let token_address = self ._create_token(caller, symbol, name, initial_supply, contract_address_salt); @@ -245,11 +242,13 @@ mod LaunchpadMarketplace { .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 @@ -259,121 +258,72 @@ mod LaunchpadMarketplace { name: felt252, initial_supply: u256, contract_address_salt: felt252 - ) { + ) -> ContractAddress { let caller = get_caller_address(); - println!("create token"); - let initial_key_price = self.initial_key_price.read(); - - let mut token_to_use = self.default_token.read(); - let bond_type = BondingType::Linear; - - // @TODO Deploy an ERC404 or ERC20 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 } ); + + + self._launch_token(token_address); + + token_address } // Create keys for an user fn launch_token( - ref self: ContractState, coin_address: ContractAddress + ref self: ContractState, + coin_address: ContractAddress ) { // 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 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.launched_coins.write(get_caller_address(), key.clone()); - - // let total_key = self.total_keys.read(); - // if total_key == 0 { - // self.total_keys.write(1); - // self.array_launched_coins.write(0, key); - // } else { - // self.total_keys.write(total_key + 1); - // self.array_launched_coins.write(total_key, key); - // } - - // self - // .emit( - // CreateToken { - // caller: get_caller_address(), - // key_user: get_caller_address(), - // amount: 1, - // price: 1, - // } - // ); - } - // fn liquidity_token() { - // } + self._launch_token(coin_address); + + } + fn buy_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { - let old_keys = self.launched_coins.read(coin_address); - assert!(!old_keys.owner.is_zero(), "key not found"); + let old_launch = self.launched_coins.read(coin_address); + assert!(!old_launch.owner.is_zero(), "coin not found"); // TODO erc20 token transfer - 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, + 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, + + }; - // Todo price by pricetype after fix Enum instantiate - // Refactorize and opti let total_price = self.get_price_of_supply_key(coin_address, amount, false); - // println!("total price {}", total_price); - + 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_creator_fee - amount_protocol_fee; - + // 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(); @@ -395,31 +345,21 @@ mod LaunchpadMarketplace { share_user.amount_owned += amount; share_user.amount_buy += amount; } - key.price = total_price; - key.total_supply += amount; + 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()); - self.launched_coins.write(coin_address, key.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 { @@ -428,14 +368,14 @@ mod LaunchpadMarketplace { amount: amount, price: total_price, protocol_fee: amount_protocol_fee, - creator_fee: amount_creator_fee + creator_fee: 0 } ); } fn sell_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { - let old_keys = self.launched_coins.read(coin_address); - assert!(!old_keys.owner.is_zero(), "key not found"); + 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(); @@ -444,15 +384,12 @@ mod LaunchpadMarketplace { 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 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 }; @@ -462,23 +399,28 @@ 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, }; 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 = @@ -496,24 +438,21 @@ 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(), coin_address.clone()), share_user.clone()); - self.launched_coins.write(coin_address.clone(), key.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 @@ -537,26 +476,26 @@ mod LaunchpadMarketplace { self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256 { assert!(amount <= MAX_STEPS_LOOP, "max step loop"); - let key = self.launched_coins.read(coin_address); - let mut total_supply = key.total_supply.clone(); - // if is_decreased { - // final_supply = total_supply - amount; - // } else { - // final_supply = total_supply + amount; - // } + 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 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(); + 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 @@ -567,10 +506,6 @@ 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); @@ -626,17 +561,15 @@ mod LaunchpadMarketplace { fn _create_token( ref self: ContractState, - caller: ContractAddress, + recipient: ContractAddress, symbol: felt252, name: felt252, initial_supply: u256, contract_address_salt: felt252 ) -> ContractAddress { - // @TODO Deploy an ERC404 - // let mut calldata = array![caller.into(), name.into(), symbol.into()]; let mut calldata = array![name.into(), symbol.into()]; Serde::serialize(@initial_supply, ref calldata); - Serde::serialize(@caller, ref calldata); + Serde::serialize(@recipient, ref calldata); Serde::serialize(@18, ref calldata); let (token_address, _) = deploy_syscall( @@ -648,10 +581,11 @@ mod LaunchpadMarketplace { let token = Token { token_address: token_address, - owner: caller, + owner: recipient, name, symbol, total_supply: initial_supply, + initial_supply: initial_supply, created_at: get_block_timestamp(), token_type: Option::None, }; @@ -661,6 +595,57 @@ mod LaunchpadMarketplace { 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, @@ -709,6 +694,7 @@ mod tests { // 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"); @@ -729,7 +715,8 @@ mod tests { token_address.clone(), INITIAL_KEY_PRICE, STEP_LINEAR_INCREASE, - erc20_class.class_hash + erc20_class.class_hash, + THRESHOLD_LIQUIDITY ); (sender_address, erc20, keys) } @@ -749,6 +736,7 @@ mod tests { 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()]; @@ -756,6 +744,7 @@ mod tests { 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 } } @@ -847,21 +836,15 @@ mod tests { let amount_key_buy = 1_u256; cheat_caller_address_global(sender_address); start_cheat_caller_address(erc20.contract_address, sender_address); - erc20.approve(launchpad.contract_address, amount_key_buy); // 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'); - // Instantiate keys - // start_cheat_caller_address(key_address, sender_address); - stop_cheat_caller_address(erc20.contract_address); - - // println!("instantiate keys"); start_cheat_caller_address(launchpad.contract_address, sender_address); - launchpad + let token_address = launchpad .create_token( // owner: OWNER(), symbol: SYMBOL(), @@ -869,60 +852,43 @@ mod tests { initial_supply: DEFAULT_INITIAL_SUPPLY(), contract_address_salt: SALT(), ); - // launchpad.instantiate_keys(); - // println!("get all_keys"); - - // let mut all_keys = keys.get_all_launch(); - // let mut key_user = launchpad.get_key_of_user(sender_address); - // println!("test key_user.owner {:?}", key_user.owner); - // println!("test sender_address {:?}", sender_address); - // assert(key_user.owner == sender_address, 'not same owner'); - // // println!("all_keys {:?}", all_keys); - // // println!("all_keys {:?}", all_keys); - // let amount_to_paid = launchpad - // .get_price_of_supply_key(sender_address, amount_key_buy, false, // 1, - // // BondingType::Basic, default_token.clone() - // ); - // println!("test amount_to_paid {:?}", amount_to_paid); + println!("test token_address {:?}", token_address); - // // erc20.approve(keys.contract_address, amount_to_paid*2); + // Launch coin pool + // Send total supply + println!("launch token"); - // start_cheat_caller_address(erc20.contract_address, sender_address); - // // erc20.approve(keys.contract_address, amount_approve); - // erc20.approve(launchpad.contract_address, amount_to_paid); + 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); - // stop_cheat_caller_address(erc20.contract_address); - // start_cheat_caller_address(launchpad.contract_address, sender_address); - // launchpad.buy_coin(sender_address, amount_key_buy); + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance {}", allowance); - // let mut key_user = launchpad.get_key_of_user(sender_address); - // println!("test key_user total supply {:?}", key_user.total_supply); + let pool = launchpad.launch_token(token_address); + // Test buy coin - // // Buy others key - // stop_cheat_caller_address(launchpad.contract_address); + println!("amount_to_paid", ); - // let amount_key_buy = 3_u256; - - // // println!("all_keys {:?}", all_keys); - // let amount_to_paid = launchpad - // .get_price_of_supply_key(sender_address, amount_key_buy, false, // 1, - // // BondingType::Basic, default_token.clone() - // ); - // start_cheat_caller_address(erc20.contract_address, sender_address); + // 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); - // erc20.approve(launchpad.contract_address, amount_to_paid); + start_cheat_caller_address(erc20.contract_address, sender_address); - // let allowance = erc20.allowance(sender_address, launchpad.contract_address); - // println!("test allowance {}", allowance); - // stop_cheat_caller_address(erc20.contract_address); + erc20.approve(launchpad.contract_address, amount_to_paid); - // start_cheat_caller_address(launchpad.contract_address, sender_address); - // launchpad.buy_coin(sender_address, amount_key_buy); - // let mut key_user = launchpad.get_key_of_user(sender_address); + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance {}", allowance); + stop_cheat_caller_address(erc20.contract_address); - // println!("test key_user total supply {:?}", key_user.total_supply); + 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/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/types/launchpad_types.cairo b/onchain/src/types/launchpad_types.cairo index 2304775c..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,7 +139,7 @@ pub struct CreateLaunch { #[key] pub caller: ContractAddress, #[key] - pub key_user: ContractAddress, + pub token_address: ContractAddress, pub amount: u256, pub price: u256, }