From 5214eb8d64f65d68ca5fdbc6036ac3533177f852 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Thu, 15 Aug 2024 15:24:51 +0200 Subject: [PATCH 1/8] clean repo + setting page + relay store --- apps/mobile/src/app/Router.tsx | 2 + .../src/components/HeaderScreen/index.tsx | 53 ++++++++++ .../src/components/HeaderScreen/styles.ts | 37 +++++++ apps/mobile/src/context/NostrContext.tsx | 44 --------- apps/mobile/src/hooks/index.ts | 1 - .../src/hooks/nostr/channel/useChannels.ts | 40 -------- .../hooks/nostr/channel/useCreateChannel.ts | 34 ------- .../hooks/nostr/channel/useMessagesChannel.ts | 41 -------- .../src/hooks/nostr/channel/useSendMessage.ts | 20 ---- apps/mobile/src/hooks/nostr/index.ts | 11 --- .../nostr/messages/useSendPrivateMessage.ts | 21 ---- .../src/hooks/nostr/search/useSearch.tsx | 46 --------- .../src/hooks/nostr/search/useSearchUsers.tsx | 43 -------- apps/mobile/src/hooks/nostr/useAllProfiles.ts | 38 -------- apps/mobile/src/hooks/nostr/useContacts.ts | 27 ------ .../mobile/src/hooks/nostr/useEditContacts.ts | 40 -------- apps/mobile/src/hooks/nostr/useEditProfile.ts | 31 ------ apps/mobile/src/hooks/nostr/useNote.ts | 24 ----- apps/mobile/src/hooks/nostr/useProfile.ts | 21 ---- apps/mobile/src/hooks/nostr/useReact.ts | 24 ----- apps/mobile/src/hooks/nostr/useReactions.ts | 30 ------ apps/mobile/src/hooks/nostr/useReplyNotes.ts | 41 -------- apps/mobile/src/hooks/nostr/useReposts.ts | 38 -------- apps/mobile/src/hooks/nostr/useRootNotes.ts | 38 -------- apps/mobile/src/hooks/nostr/useSearchNotes.ts | 39 -------- apps/mobile/src/hooks/nostr/useSendNote.ts | 20 ---- apps/mobile/src/modules/Post/index.tsx | 35 ++++++- .../CreateChannel/FormCreateChannel/index.tsx | 2 +- .../src/screens/CreateChannel/index.tsx | 2 +- .../mobile/src/screens/Profile/Head/index.tsx | 70 +++++++++---- .../mobile/src/screens/Profile/Info/index.tsx | 11 ++- apps/mobile/src/screens/Settings/index.tsx | 97 +++++++++++++++++++ apps/mobile/src/screens/Settings/styles.ts | 61 ++++++++++++ apps/mobile/src/types/routes.ts | 10 ++ apps/mobile/src/utils/relay.ts | 25 ----- .../src/context/NostrContext.tsx | 6 +- packages/afk_nostr_sdk/src/store/index.tsx | 1 + packages/afk_nostr_sdk/src/store/settings.ts | 21 ++++ packages/afk_nostr_sdk/src/utils/relay.ts | 4 +- 39 files changed, 386 insertions(+), 763 deletions(-) create mode 100644 apps/mobile/src/components/HeaderScreen/index.tsx create mode 100644 apps/mobile/src/components/HeaderScreen/styles.ts delete mode 100644 apps/mobile/src/context/NostrContext.tsx delete mode 100644 apps/mobile/src/hooks/nostr/channel/useChannels.ts delete mode 100644 apps/mobile/src/hooks/nostr/channel/useCreateChannel.ts delete mode 100644 apps/mobile/src/hooks/nostr/channel/useMessagesChannel.ts delete mode 100644 apps/mobile/src/hooks/nostr/channel/useSendMessage.ts delete mode 100644 apps/mobile/src/hooks/nostr/index.ts delete mode 100644 apps/mobile/src/hooks/nostr/messages/useSendPrivateMessage.ts delete mode 100644 apps/mobile/src/hooks/nostr/search/useSearch.tsx delete mode 100644 apps/mobile/src/hooks/nostr/search/useSearchUsers.tsx delete mode 100644 apps/mobile/src/hooks/nostr/useAllProfiles.ts delete mode 100644 apps/mobile/src/hooks/nostr/useContacts.ts delete mode 100644 apps/mobile/src/hooks/nostr/useEditContacts.ts delete mode 100644 apps/mobile/src/hooks/nostr/useEditProfile.ts delete mode 100644 apps/mobile/src/hooks/nostr/useNote.ts delete mode 100644 apps/mobile/src/hooks/nostr/useProfile.ts delete mode 100644 apps/mobile/src/hooks/nostr/useReact.ts delete mode 100644 apps/mobile/src/hooks/nostr/useReactions.ts delete mode 100644 apps/mobile/src/hooks/nostr/useReplyNotes.ts delete mode 100644 apps/mobile/src/hooks/nostr/useReposts.ts delete mode 100644 apps/mobile/src/hooks/nostr/useRootNotes.ts delete mode 100644 apps/mobile/src/hooks/nostr/useSearchNotes.ts delete mode 100644 apps/mobile/src/hooks/nostr/useSendNote.ts create mode 100644 apps/mobile/src/screens/Settings/index.tsx create mode 100644 apps/mobile/src/screens/Settings/styles.ts delete mode 100644 apps/mobile/src/utils/relay.ts create mode 100644 packages/afk_nostr_sdk/src/store/settings.ts diff --git a/apps/mobile/src/app/Router.tsx b/apps/mobile/src/app/Router.tsx index efaa46dd..30aa60f1 100644 --- a/apps/mobile/src/app/Router.tsx +++ b/apps/mobile/src/app/Router.tsx @@ -29,6 +29,7 @@ import { Games } from '../screens/Games'; import { useAuth } from 'afk_nostr_sdk'; import { createDrawerNavigator } from '@react-navigation/drawer'; import { Navbar } from '../components/Navbar'; +import { Setttings } from '../screens/Settings'; const DrawerStack = createDrawerNavigator(); const RootStack = createNativeStackNavigator(); @@ -202,6 +203,7 @@ const MainNavigator: React.FC = () => { + ); }; diff --git a/apps/mobile/src/components/HeaderScreen/index.tsx b/apps/mobile/src/components/HeaderScreen/index.tsx new file mode 100644 index 00000000..a2763476 --- /dev/null +++ b/apps/mobile/src/components/HeaderScreen/index.tsx @@ -0,0 +1,53 @@ +import { Image, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import { useStyles, useTheme } from '../../hooks'; +import { Text } from '../Text'; +import stylesheet from './styles'; +import { AFKIcon } from '../../assets/icons'; + +export type HeaderProps = { + showLogo?: boolean; + left?: React.ReactNode; + right?: React.ReactNode; + title?: string; +}; + +export const HeaderScreen: React.FC = ({ showLogo = true, left, right, title }) => { + const { theme } = useTheme(); + const styles = useStyles(stylesheet); + + return ( + + + {left} + + {showLogo && ( + + + {/* */} + + )} + + {title && ( + + + {title} + + + )} + + {right} + + {/* {right ?? ( + + + + )} */} + + + ); +}; diff --git a/apps/mobile/src/components/HeaderScreen/styles.ts b/apps/mobile/src/components/HeaderScreen/styles.ts new file mode 100644 index 00000000..721c587b --- /dev/null +++ b/apps/mobile/src/components/HeaderScreen/styles.ts @@ -0,0 +1,37 @@ +import {StyleSheet} from 'react-native'; + +import {Spacing, ThemedStyleSheet} from '../../styles'; + +export default ThemedStyleSheet((theme) => ({ + container: { + }, + 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/context/NostrContext.tsx b/apps/mobile/src/context/NostrContext.tsx deleted file mode 100644 index 44b3a5a0..00000000 --- a/apps/mobile/src/context/NostrContext.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import NDK, {NDKPrivateKeySigner} from '@nostr-dev-kit/ndk'; -import {createContext, useContext, useEffect, useState} from 'react'; - -import {useAuth} from '../store/auth'; -import {AFK_RELAYS} from '../utils/relay'; - -export type NostrContextType = { - ndk: NDK; -}; - -export const NostrContext = createContext(null); - -export const NostrProvider: React.FC = ({children}) => { - const privateKey = useAuth((state) => state.privateKey); - - const [ndk, setNdk] = useState( - new NDK({ - explicitRelayUrls: AFK_RELAYS, - }), - ); - - useEffect(() => { - const newNdk = new NDK({ - explicitRelayUrls: AFK_RELAYS, - signer: privateKey ? new NDKPrivateKeySigner(privateKey) : undefined, - }); - - newNdk.connect().then(() => { - setNdk(newNdk); - }); - }, [privateKey]); - - return {children}; -}; - -export const useNostrContext = () => { - const nostr = useContext(NostrContext); - - if (!nostr) { - throw new Error('NostrContext must be used within a NostrProvider'); - } - - return nostr; -}; diff --git a/apps/mobile/src/hooks/index.ts b/apps/mobile/src/hooks/index.ts index db500bff..14508de8 100644 --- a/apps/mobile/src/hooks/index.ts +++ b/apps/mobile/src/hooks/index.ts @@ -1,4 +1,3 @@ -export * from './nostr'; export {useColor} from './useColor'; export {useStyles} from './useStyles'; export {useTheme} from './useTheme'; diff --git a/apps/mobile/src/hooks/nostr/channel/useChannels.ts b/apps/mobile/src/hooks/nostr/channel/useChannels.ts deleted file mode 100644 index 24076b42..00000000 --- a/apps/mobile/src/hooks/nostr/channel/useChannels.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../../context/NostrContext'; - -export type UseRootNotesOptions = { - authors?: string[]; - search?: string; -}; - -export const useChannels = (options?: UseRootNotesOptions) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['channels', options?.authors, options?.search, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const notes = await ndk.fetchEvents({ - kinds: [NDKKind.ChannelCreation], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - }); - - console.log('notes', notes); - - return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e')); - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/channel/useCreateChannel.ts b/apps/mobile/src/hooks/nostr/channel/useCreateChannel.ts deleted file mode 100644 index 94e23062..00000000 --- a/apps/mobile/src/hooks/nostr/channel/useCreateChannel.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {NDKEvent, NDKKind} from '@nostr-dev-kit/ndk'; -import {useMutation} from '@tanstack/react-query'; - -import {useNostrContext} from '../../../context/NostrContext'; -import {useAuth} from '../../../store/auth'; - -export const useCreateChannel = () => { - const {ndk} = useNostrContext(); - const {publicKey} = useAuth(); - - return useMutation({ - mutationKey: ['createChannel', ndk], - mutationFn: async (data: {content: string; channel_name: string; tags?: string[][]}) => { - try { - const user = ndk.getUser({pubkey: publicKey}); - - // if (!user.profile) { - // throw new Error('Profile not found'); - // } - const event = new NDKEvent(ndk); - event.kind = NDKKind.ChannelCreation; - event.content = data.content; - event.author = user; - event.tags = data.tags ?? []; - await event.publish(); - - return event; - } catch (error) { - console.error('Error create channel', error); - throw error; - } - }, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/channel/useMessagesChannel.ts b/apps/mobile/src/hooks/nostr/channel/useMessagesChannel.ts deleted file mode 100644 index b5f4d20f..00000000 --- a/apps/mobile/src/hooks/nostr/channel/useMessagesChannel.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../../context/NostrContext'; - -export type UseReplyNotesOptions = { - noteId?: string; - channelId?: string; - authors?: string[]; - search?: string; -}; - -export const useMessagesChannels = (options?: UseReplyNotesOptions) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['messagesChannels', options?.noteId, options?.authors, options?.search, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const notes = await ndk.fetchEvents({ - kinds: [NDKKind.ChannelMessage], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - '#e': options?.noteId ? [options.noteId] : undefined, - }); - - return [...notes].filter((note) => note.tags.every((tag) => tag[0] === 'e')); - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/channel/useSendMessage.ts b/apps/mobile/src/hooks/nostr/channel/useSendMessage.ts deleted file mode 100644 index 25ce24d9..00000000 --- a/apps/mobile/src/hooks/nostr/channel/useSendMessage.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {NDKEvent, NDKKind} from '@nostr-dev-kit/ndk'; -import {useMutation} from '@tanstack/react-query'; - -import {useNostrContext} from '../../../context/NostrContext'; - -export const useSendMessageChannel = () => { - const {ndk} = useNostrContext(); - - return useMutation({ - mutationKey: ['sendNoteChannel', ndk], - mutationFn: async (data: {content: string; tags?: string[][]}) => { - const event = new NDKEvent(ndk); - event.kind = NDKKind.ChannelMessage; - event.content = data.content; - event.tags = data.tags ?? []; - - return event.publish(); - }, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/index.ts b/apps/mobile/src/hooks/nostr/index.ts deleted file mode 100644 index 49e6d3eb..00000000 --- a/apps/mobile/src/hooks/nostr/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export {useContacts} from './useContacts'; -export {useEditContacts} from './useEditContacts'; -export {useEditProfile} from './useEditProfile'; -export {useNote} from './useNote'; -export {useProfile} from './useProfile'; -export {useReact} from './useReact'; -export {useReactions} from './useReactions'; -export {useReplyNotes} from './useReplyNotes'; -export {useReposts} from './useReposts'; -export {useRootNotes} from './useRootNotes'; -export {useSendNote} from './useSendNote'; diff --git a/apps/mobile/src/hooks/nostr/messages/useSendPrivateMessage.ts b/apps/mobile/src/hooks/nostr/messages/useSendPrivateMessage.ts deleted file mode 100644 index 759d093c..00000000 --- a/apps/mobile/src/hooks/nostr/messages/useSendPrivateMessage.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {NDKEvent} from '@nostr-dev-kit/ndk'; -import {useMutation} from '@tanstack/react-query'; - -import {useNostrContext} from '../../../context/NostrContext'; - -export const useSendPrivateMessage = () => { - const {ndk} = useNostrContext(); - - return useMutation({ - mutationKey: ['sendPrivateMessage', ndk], - mutationFn: async (data: {content: string; tags?: string[][]}) => { - const event = new NDKEvent(ndk); - event.kind = 14; - // const encryptedContent = nip44 - event.content = data.content; - event.tags = data.tags ?? []; - - return event.publish(); - }, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/search/useSearch.tsx b/apps/mobile/src/hooks/nostr/search/useSearch.tsx deleted file mode 100644 index 1d82fe66..00000000 --- a/apps/mobile/src/hooks/nostr/search/useSearch.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// useSearchUsers.ts -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../../context/NostrContext'; - -export type UseSearch = { - authors?: string[]; - search?: string; - kind?: NDKKind; - kinds?: NDKKind[]; -}; - -export const useSearch = (options?: UseSearch) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['search', options?.authors, options?.search, options?.kind, options?.kinds, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - console.log('search query', options?.search); - const notes = await ndk.fetchEvents({ - kinds: options?.kinds ?? [options?.kind ?? NDKKind.Text], - authors: options?.authors, - search: options?.search, - // content: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - }); - - return [notes]; - // return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e')); - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; - -export default useSearch; diff --git a/apps/mobile/src/hooks/nostr/search/useSearchUsers.tsx b/apps/mobile/src/hooks/nostr/search/useSearchUsers.tsx deleted file mode 100644 index 42dca83a..00000000 --- a/apps/mobile/src/hooks/nostr/search/useSearchUsers.tsx +++ /dev/null @@ -1,43 +0,0 @@ -// useSearchUsers.ts -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../../context/NostrContext'; - -export type UseSearchUsers = { - authors?: string[]; - search?: string; - kind?: NDKKind; -}; - -export const useSearchUsers = (options?: UseSearchUsers) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['search_user', options?.authors, options?.search, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const notes = await ndk.fetchEvents({ - kinds: [options?.kind ?? NDKKind.Text], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - }); - - // return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e')); - return [...notes]; - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; - -export default useSearchUsers; diff --git a/apps/mobile/src/hooks/nostr/useAllProfiles.ts b/apps/mobile/src/hooks/nostr/useAllProfiles.ts deleted file mode 100644 index 553ff84d..00000000 --- a/apps/mobile/src/hooks/nostr/useAllProfiles.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseRootProfilesOptions = { - authors?: string[]; - search?: string; -}; - -export const useAllProfiles = (options?: UseRootProfilesOptions) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['rootProfiles', options?.authors, options?.search, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const notes = await ndk.fetchEvents({ - kinds: [NDKKind.Metadata], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - }); - - return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e')); - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useContacts.ts b/apps/mobile/src/hooks/nostr/useContacts.ts deleted file mode 100644 index 86d99b22..00000000 --- a/apps/mobile/src/hooks/nostr/useContacts.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseContactsOptions = { - authors?: string[]; - search?: string; -}; - -export const useContacts = (options?: UseContactsOptions) => { - const {ndk} = useNostrContext(); - - return useQuery({ - queryKey: ['contacts', options?.authors, options?.search, ndk], - queryFn: async () => { - const contacts = await ndk.fetchEvent({ - kinds: [NDKKind.Contacts], - authors: options?.authors, - search: options?.search, - }); - - return contacts?.tags.filter((tag) => tag[0] === 'p').map((tag) => tag[1]) ?? []; - }, - placeholderData: [], - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useEditContacts.ts b/apps/mobile/src/hooks/nostr/useEditContacts.ts deleted file mode 100644 index 8b38d15b..00000000 --- a/apps/mobile/src/hooks/nostr/useEditContacts.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {NDKEvent, NDKKind} from '@nostr-dev-kit/ndk'; -import {useMutation} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; -import {useAuth} from '../../store/auth'; - -export const useEditContacts = () => { - const {ndk} = useNostrContext(); - const {publicKey} = useAuth(); - - return useMutation({ - mutationKey: ['editContacts', ndk], - mutationFn: async (data: {pubkey: string; type: 'add' | 'remove'}) => { - let contacts = await ndk.fetchEvent({ - kinds: [NDKKind.Contacts], - authors: [publicKey], - }); - - if (!contacts) { - contacts = new NDKEvent(ndk); - contacts.kind = NDKKind.Contacts; - contacts.content = ''; - contacts.tags = []; - } - - // Resetting the id and created_at to avoid conflicts - contacts.id = undefined as any; - contacts.created_at = undefined; - - if (data.type === 'add') { - contacts.tags.push(['p', data.pubkey, '', '']); - } else { - contacts.tags = contacts.tags.filter((tag) => tag[1] !== data.pubkey); - } - - await contacts.sign(); - return contacts.publish(); - }, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useEditProfile.ts b/apps/mobile/src/hooks/nostr/useEditProfile.ts deleted file mode 100644 index 2fdb9110..00000000 --- a/apps/mobile/src/hooks/nostr/useEditProfile.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {NDKUserProfile} from '@nostr-dev-kit/ndk'; -import {useMutation} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; -import {useAuth} from '../../store/auth'; - -export const useEditProfile = () => { - const {ndk} = useNostrContext(); - const {publicKey} = useAuth(); - - return useMutation({ - mutationKey: ['editProfile', ndk], - mutationFn: async (data: NDKUserProfile) => { - try { - const user = ndk.getUser({pubkey: publicKey}); - await user.fetchProfile(); - - if (!user.profile) { - throw new Error('Profile not found'); - } - - user.profile = {...user.profile, ...data}; - - return user.publish(); - } catch (error) { - console.error('Error editing profile', error); - throw error; - } - }, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useNote.ts b/apps/mobile/src/hooks/nostr/useNote.ts deleted file mode 100644 index bdacc1f7..00000000 --- a/apps/mobile/src/hooks/nostr/useNote.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseNoteOptions = { - noteId: string; -}; - -export const useNote = (options: UseNoteOptions) => { - const {ndk} = useNostrContext(); - - return useQuery({ - queryKey: ['note', options.noteId, ndk], - queryFn: async () => { - const note = await ndk.fetchEvent({ - kinds: [NDKKind.Text], - ids: [options.noteId], - }); - - return note ?? undefined; - }, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useProfile.ts b/apps/mobile/src/hooks/nostr/useProfile.ts deleted file mode 100644 index 3b2110d0..00000000 --- a/apps/mobile/src/hooks/nostr/useProfile.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {useQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseProfileOptions = { - publicKey?: string; -}; - -export const useProfile = (options: UseProfileOptions) => { - const {ndk} = useNostrContext(); - - return useQuery({ - queryKey: ['profile', options.publicKey, ndk], - queryFn: async () => { - const user = ndk.getUser({pubkey: options.publicKey}); - - return user.fetchProfile(); - }, - placeholderData: {} as any, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useReact.ts b/apps/mobile/src/hooks/nostr/useReact.ts deleted file mode 100644 index 2a21214b..00000000 --- a/apps/mobile/src/hooks/nostr/useReact.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {NDKEvent, NDKKind} from '@nostr-dev-kit/ndk'; -import {useMutation} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export const useReact = () => { - const {ndk} = useNostrContext(); - - return useMutation({ - mutationKey: ['react', ndk], - mutationFn: async (data: {event: NDKEvent; type: 'like' | 'dislike'}) => { - const event = new NDKEvent(ndk); - event.kind = NDKKind.Reaction; - event.content = data.type === 'like' ? '+' : '-'; - event.tags = [ - ['e', data.event.id], - ['p', data.event.pubkey], - ['k', (data.event.kind ?? 1).toString()], - ]; - - return event.publish(); - }, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useReactions.ts b/apps/mobile/src/hooks/nostr/useReactions.ts deleted file mode 100644 index d58dee34..00000000 --- a/apps/mobile/src/hooks/nostr/useReactions.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseReactionsOptions = { - authors?: string[]; - search?: string; - noteId?: string; -}; - -export const useReactions = (options?: UseReactionsOptions) => { - const {ndk} = useNostrContext(); - - return useQuery({ - queryKey: ['reactions', options?.noteId, options?.authors, options?.search, ndk], - queryFn: async () => { - const notes = await ndk.fetchEvents({ - kinds: [NDKKind.Reaction], - authors: options?.authors, - search: options?.search, - - '#e': options?.noteId ? [options.noteId] : undefined, - }); - - return [...notes]; - }, - placeholderData: [], - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useReplyNotes.ts b/apps/mobile/src/hooks/nostr/useReplyNotes.ts deleted file mode 100644 index 6fed31f2..00000000 --- a/apps/mobile/src/hooks/nostr/useReplyNotes.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseReplyNotesOptions = { - noteId?: string; - authors?: string[]; - search?: string; -}; - -export const useReplyNotes = (options?: UseReplyNotesOptions) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['replyNotes', options?.noteId, options?.authors, options?.search, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const notes = await ndk.fetchEvents({ - kinds: [NDKKind.Text], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - - '#e': options?.noteId ? [options.noteId] : undefined, - }); - - return [...notes].filter((note) => note.tags.every((tag) => tag[0] === 'e')); - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useReposts.ts b/apps/mobile/src/hooks/nostr/useReposts.ts deleted file mode 100644 index 1320a52d..00000000 --- a/apps/mobile/src/hooks/nostr/useReposts.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseRepostsOptions = { - authors?: string[]; - search?: string; -}; - -export const useReposts = (options?: UseRepostsOptions) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['reposts', options?.authors, options?.search, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const reposts = await ndk.fetchEvents({ - kinds: [NDKKind.Repost], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - }); - - return [...reposts]; - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useRootNotes.ts b/apps/mobile/src/hooks/nostr/useRootNotes.ts deleted file mode 100644 index c04b2343..00000000 --- a/apps/mobile/src/hooks/nostr/useRootNotes.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseRootNotesOptions = { - authors?: string[]; - search?: string; -}; - -export const useRootNotes = (options?: UseRootNotesOptions) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['rootNotes', options?.authors, options?.search, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const notes = await ndk.fetchEvents({ - kinds: [NDKKind.Text], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - }); - - return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e')); - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useSearchNotes.ts b/apps/mobile/src/hooks/nostr/useSearchNotes.ts deleted file mode 100644 index db5ce2d2..00000000 --- a/apps/mobile/src/hooks/nostr/useSearchNotes.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {NDKKind} from '@nostr-dev-kit/ndk'; -import {useInfiniteQuery} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export type UseRootNotesOptions = { - authors?: string[]; - search?: string; - kinds?: NDKKind[]; -}; - -export const useSearchNotes = (options?: UseRootNotesOptions) => { - const {ndk} = useNostrContext(); - - return useInfiniteQuery({ - initialPageParam: 0, - queryKey: ['searchNotes', options?.authors, options?.search, options?.kinds, ndk], - getNextPageParam: (lastPage: any, allPages, lastPageParam) => { - if (!lastPage?.length) return undefined; - - const pageParam = lastPage[lastPage.length - 1].created_at - 1; - - if (!pageParam || pageParam === lastPageParam) return undefined; - return pageParam; - }, - queryFn: async ({pageParam}) => { - const notes = await ndk.fetchEvents({ - kinds: options?.kinds ?? [NDKKind.Text], - authors: options?.authors, - search: options?.search, - until: pageParam || Math.round(Date.now() / 1000), - limit: 20, - }); - - return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e')); - }, - placeholderData: {pages: [], pageParams: []}, - }); -}; diff --git a/apps/mobile/src/hooks/nostr/useSendNote.ts b/apps/mobile/src/hooks/nostr/useSendNote.ts deleted file mode 100644 index 49cd18f6..00000000 --- a/apps/mobile/src/hooks/nostr/useSendNote.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {NDKEvent, NDKKind} from '@nostr-dev-kit/ndk'; -import {useMutation} from '@tanstack/react-query'; - -import {useNostrContext} from '../../context/NostrContext'; - -export const useSendNote = () => { - const {ndk} = useNostrContext(); - - return useMutation({ - mutationKey: ['sendNote', ndk], - mutationFn: async (data: {content: string; tags?: string[][]}) => { - const event = new NDKEvent(ndk); - event.kind = NDKKind.Text; - event.content = data.content; - event.tags = data.tags ?? []; - - return event.publish(); - }, - }); -}; diff --git a/apps/mobile/src/modules/Post/index.tsx b/apps/mobile/src/modules/Post/index.tsx index d0f97f67..f283e6a6 100644 --- a/apps/mobile/src/modules/Post/index.tsx +++ b/apps/mobile/src/modules/Post/index.tsx @@ -13,9 +13,9 @@ import Animated, { } from 'react-native-reanimated'; import { CommentIcon, LikeFillIcon, LikeIcon, RepostIcon } from '../../assets/icons'; -import { Avatar, IconButton, Menu, Text } from '../../components'; +import { Avatar, Icon, IconButton, Menu, Text } from '../../components'; import { useStyles, useTheme } from '../../hooks'; -import {useProfile, useReact, useReactions, useReplyNotes, } from "afk_nostr_sdk" +import { useProfile, useReact, useReactions, useReplyNotes, } from "afk_nostr_sdk" import { useTipModal } from '../../hooks/modals'; // import { useAuth } from '../../store/auth'; import { useAuth } from 'afk_nostr_sdk'; @@ -78,7 +78,12 @@ export const Post: React.FC = ({ asComment, event }) => { const imageTag = event.tags.find((tag) => tag[0] === 'image'); if (!imageTag) return; - const dimensions = imageTag[2].split('x').map(Number); + let dimensions = [200, 2500] + if (imageTag[2]) { + // const dimensions = imageTag[2].split('x').map(Number); + dimensions = imageTag[2].split('x').map(Number); + + } return { uri: imageTag[1], width: dimensions[0], height: dimensions[1] }; }, [event?.tags]); @@ -236,6 +241,30 @@ export const Post: React.FC = ({ asComment, event }) => { + { + if (!event) return; + + showTipModal(event); + setMenuOpen(false); + }} + > + + + + {/* + { + if (!event) return; + + showTipModal(event); + setMenuOpen(false); + }} + /> */} + setMenuOpen(false)} diff --git a/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx b/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx index ba919112..3e6d881a 100644 --- a/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx +++ b/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx @@ -17,9 +17,9 @@ import { // import { useAuth } from '../../../store/auth'; import { useAuth } from 'afk_nostr_sdk'; -import { AFK_RELAYS } from '../../../utils/relay'; import { ChannelHead } from '../Head'; import stylesheet from './styles'; +import { AFK_RELAYS } from 'afk_nostr_sdk/src/utils/relay'; const UsernameInputLeft = ( diff --git a/apps/mobile/src/screens/CreateChannel/index.tsx b/apps/mobile/src/screens/CreateChannel/index.tsx index b5154d8f..8c1ea679 100644 --- a/apps/mobile/src/screens/CreateChannel/index.tsx +++ b/apps/mobile/src/screens/CreateChannel/index.tsx @@ -16,9 +16,9 @@ import {useToast} from '../../hooks/modals'; // import {useAuth} from '../../store/auth'; import { useAuth, useCreateChannel } from 'afk_nostr_sdk'; import {CreateChannelScreenProps, MainStackNavigationProps} from '../../types'; -import {AFK_RELAYS} from '../../utils/relay'; import {ChannelHead} from './Head'; import stylesheet from './styles'; +import { AFK_RELAYS } from 'afk_nostr_sdk/src/utils/relay'; const UsernameInputLeft = ( diff --git a/apps/mobile/src/screens/Profile/Head/index.tsx b/apps/mobile/src/screens/Profile/Head/index.tsx index 22a66ed0..42d93d87 100644 --- a/apps/mobile/src/screens/Profile/Head/index.tsx +++ b/apps/mobile/src/screens/Profile/Head/index.tsx @@ -1,12 +1,13 @@ -import {useNavigation} from '@react-navigation/native'; -import {useState} from 'react'; -import {Image, ImageSourcePropType, Pressable, View} from 'react-native'; -import {SafeAreaView} from 'react-native-safe-area-context'; +import { useNavigation } from '@react-navigation/native'; +import { useState } from 'react'; +import { Image, ImageSourcePropType, Pressable, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; -import {SettingsIcon, UploadIcon} from '../../../assets/icons'; -import {Avatar, IconButton, Menu, Text} from '../../../components'; -import {useStyles, useTheme} from '../../../hooks'; -import stylesheet, {AVATAR_SIZE} from './styles'; +import { SettingsIcon, UploadIcon } from '../../../assets/icons'; +import { Avatar, IconButton, Menu, Text } from '../../../components'; +import { useStyles, useTheme } from '../../../hooks'; +import stylesheet, { AVATAR_SIZE } from './styles'; +import { MainStackNavigationProps } from '../../../types'; export type ProfileHeadProps = { profilePhoto?: ImageSourcePropType; @@ -27,16 +28,19 @@ export const ProfileHead: React.FC = ({ showSettingsButton, buttons, }) => { - const {theme, toggleTheme} = useTheme(); + const { theme, toggleTheme } = useTheme(); const styles = useStyles(stylesheet); - const navigation = useNavigation(); + const navigation = useNavigation(); const [menuOpen, setMenuOpen] = useState(false); - const goToSettings = () => { - setMenuOpen(menuOpen == true ? false : true); - // navigation.navigate('Settings'); - }; + // const goToSettings = () => { + // setMenuOpen(menuOpen == true ? false : true); + // // navigation.navigate('Settings'); + // }; + // const handleSettings = () => { + // navigation.navigate("Settings") + // } return ( @@ -57,10 +61,16 @@ export const ProfileHead: React.FC = ({ /> )} - {showSettingsButton && ( + + {/* {showSettingsButton && ( setMenuOpen(false)} + onClose={() => { + handleSettings() + // setMenuOpen(false) + } + + } handle={ @@ -76,7 +86,33 @@ export const ProfileHead: React.FC = ({ onPress={toggleTheme} /> - )} + )} */} + + {/* {showSettingsButton && ( + { + handleSettings() + // setMenuOpen(false) + } + + } + handle={ + + + + Settings + + + } + > + + + )} */} {onCoverPhotoUpload && ( diff --git a/apps/mobile/src/screens/Profile/Info/index.tsx b/apps/mobile/src/screens/Profile/Info/index.tsx index e5dcc818..c5b9b733 100644 --- a/apps/mobile/src/screens/Profile/Info/index.tsx +++ b/apps/mobile/src/screens/Profile/Info/index.tsx @@ -76,7 +76,9 @@ export const ProfileInfo: React.FC = ({publicKey: userPublicKe await Clipboard.setStringAsync(userPublicKey); showToast({type: 'info', title: 'Public key copied to the clipboard'}); }; - + const handleSettings = () => { + navigation.navigate("Settings") + } return ( = ({publicKey: userPublicKe Edit profile + + setMenuOpen(false)} diff --git a/apps/mobile/src/screens/Settings/index.tsx b/apps/mobile/src/screens/Settings/index.tsx new file mode 100644 index 00000000..3222807e --- /dev/null +++ b/apps/mobile/src/screens/Settings/index.tsx @@ -0,0 +1,97 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useState } from 'react'; +import { View, Text, Pressable } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import { Button, Divider, Header, Icon, IconButton, Input, KeyboardFixedView } from '../../components'; +import { useStyles, useTheme } from '../../hooks'; +import { useToast } from '../../hooks/modals'; +import { SettingsScreenProps } from '../../types'; +import stylesheet from './styles'; +import { HeaderScreen } from '../../components/HeaderScreen'; +import { useAuth, useSettingsStore } from 'afk_nostr_sdk'; +import { AFK_RELAYS } from 'afk_nostr_sdk/src/utils/relay'; + +export const Setttings: React.FC = ({ navigation }) => { + const styles = useStyles(stylesheet); + const { showToast } = useToast(); + const { theme, toggleTheme } = useTheme(); + const { relays, setRelays } = useSettingsStore() + const { publicKey } = useAuth() + const RELAYS_USED = relays ?? AFK_RELAYS + return ( + + { + navigation.navigate("Profile", { publicKey }) + // navigation.goBack + } + } />} + // right={} + title="Settings" + /> + + {/* + + */} + + + + + + + + + + { + navigation.navigate("Profile", { publicKey }) + }} + /> + + + + + + + + AFK: All relays used + + {RELAYS_USED?.map((r) => { + return ( + + Relay: {r} + + ) + })} + + + + + + ); +}; diff --git a/apps/mobile/src/screens/Settings/styles.ts b/apps/mobile/src/screens/Settings/styles.ts new file mode 100644 index 00000000..07e75fb6 --- /dev/null +++ b/apps/mobile/src/screens/Settings/styles.ts @@ -0,0 +1,61 @@ +import {Spacing, ThemedStyleSheet} from '../../styles'; + +export default ThemedStyleSheet((theme) => ({ + container: { + flex: 1, + backgroundColor: theme.colors.background, + padding:Spacing.pagePadding, + color: theme.colors.text, + + }, + content: { + flex: 1, + color: theme.colors.text, + padding:Spacing.medium, + + }, + + relaysSettings: { + flex: 1, + color: theme.colors.text, + backgroundColor: theme.colors.surface, + borderRadius:10, + padding:Spacing.medium, + height:"100%" + + }, + coverButtons: { + // position: 'relative', + width: '100%', + height: '100%', + }, + + backButton: { + position: 'absolute', + top: Spacing.pagePadding, + left: Spacing.pagePadding, + }, + themeButton: { + // flex: 1, + // flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + justifyItems:"baseline" + }, + title:{ + color: theme.colors.text, + fontSize:24, + marginBottom:4 + }, + text:{ + color: theme.colors.text, + }, + buttons: { + flex: 1, + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + gap: Spacing.xsmall, + paddingTop: Spacing.xsmall, + }, +})); diff --git a/apps/mobile/src/types/routes.ts b/apps/mobile/src/types/routes.ts index 6265de33..387627ae 100644 --- a/apps/mobile/src/types/routes.ts +++ b/apps/mobile/src/types/routes.ts @@ -37,6 +37,8 @@ export type MainStackParams = { Tips: undefined; Home: undefined; Feed: undefined; + Settings:undefined; + }; export type HomeBottomStackParams = { @@ -48,6 +50,8 @@ export type HomeBottomStackParams = { Games:undefined, Defi: undefined; Home: undefined; + Settings:undefined; + Profile:{publicKey:string}; // ChannelsFeed:undefined; // CreateChannel:undefined; @@ -184,6 +188,12 @@ export type SlinkScreenProps = CompositeScreenProps< >; +export type SettingsScreenProps = CompositeScreenProps< + NativeStackScreenProps, + NativeStackScreenProps +>; + + // export type TipsMainScreenProps = CompositeScreenProps< // NativeStackScreenProps, // NativeStackScreenProps diff --git a/apps/mobile/src/utils/relay.ts b/apps/mobile/src/utils/relay.ts deleted file mode 100644 index 513d6dbd..00000000 --- a/apps/mobile/src/utils/relay.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const RELAYS_PROD = ['wss://relay.n057r.club', 'wss://relay.nostr.net']; - -export const JOYBOY_RELAYS = [ - // 'wss://nostr.joyboy.community', - 'ws://nostr-relay-nestjs-production.up.railway.app', - // 'ws://localhost:3000', // comment if you don't run a relayer in localhost -]; - -// export const AFK_RELAYS = [ -// // 'wss://nostr.joyboy.community', -// 'ws://nostr-relay-nestjs-production.up.railway.app', -// // 'ws://localhost:3000', // comment if you don't run a relayer in localhost -// ] -export const AFK_RELAYS = - process.env.EXPO_NODE_ENV == 'production' || process.env.NODE_ENV == 'production' - ? [ - // 'wss://nostr.joyboy.community', - 'wss://nostr-relay-nestjs-production.up.railway.app', - // 'ws://localhost:3000', // comment if you don't run a relayer in localhost - ] - : [ - // 'wss://nostr.joyboy.community', - // 'ws://nostr-relay-nestjs-production.up.railway.app', - 'ws://localhost:8080', // comment if you don't run a relayer in localhost - ]; diff --git a/packages/afk_nostr_sdk/src/context/NostrContext.tsx b/packages/afk_nostr_sdk/src/context/NostrContext.tsx index 1043d692..f45cf2fe 100644 --- a/packages/afk_nostr_sdk/src/context/NostrContext.tsx +++ b/packages/afk_nostr_sdk/src/context/NostrContext.tsx @@ -2,22 +2,24 @@ import React, {createContext, useContext, useEffect, useState} from 'react'; import NDK, {NDKPrivateKeySigner} from '@nostr-dev-kit/ndk'; import { useAuth } from '../store/auth'; import {AFK_RELAYS} from "../utils/relay" +import { useSettingsStore } from '../store'; export type NostrContextType = { ndk: NDK; }; export const NostrContext = createContext(null); export const NostrProvider: React.FC = ({children}) => { const privateKey = useAuth((state) => state.privateKey); + const relays = useSettingsStore((state) => state.relays); const [ndk, setNdk] = useState( new NDK({ - explicitRelayUrls: AFK_RELAYS, + explicitRelayUrls: relays ?? AFK_RELAYS, }), ); useEffect(() => { const newNdk = new NDK({ - explicitRelayUrls: AFK_RELAYS, + explicitRelayUrls: relays ?? AFK_RELAYS, signer: privateKey ? new NDKPrivateKeySigner(privateKey) : undefined, }); diff --git a/packages/afk_nostr_sdk/src/store/index.tsx b/packages/afk_nostr_sdk/src/store/index.tsx index efba1120..ab9318ef 100644 --- a/packages/afk_nostr_sdk/src/store/index.tsx +++ b/packages/afk_nostr_sdk/src/store/index.tsx @@ -1,2 +1,3 @@ export * from "./auth" +export * from "./settings" export * from "./createBoundedUseStore" \ No newline at end of file diff --git a/packages/afk_nostr_sdk/src/store/settings.ts b/packages/afk_nostr_sdk/src/store/settings.ts new file mode 100644 index 00000000..5624a724 --- /dev/null +++ b/packages/afk_nostr_sdk/src/store/settings.ts @@ -0,0 +1,21 @@ +import {createStore} from 'zustand'; + +import createBoundedUseStore from './createBoundedUseStore'; +import { AFK_RELAYS } from '../utils/relay'; + +type State = { + relays: string[]; +}; + +type Action = { + setRelays: (relays:string[]) => void; +}; + +export const settingsStore = createStore((set, get) => ({ + relays: undefined as unknown as string[], + setRelays: (relays) => { + set({relays}); + }, +})); + +export const useSettingsStore = createBoundedUseStore(settingsStore); diff --git a/packages/afk_nostr_sdk/src/utils/relay.ts b/packages/afk_nostr_sdk/src/utils/relay.ts index 190c629f..1eca0e50 100644 --- a/packages/afk_nostr_sdk/src/utils/relay.ts +++ b/packages/afk_nostr_sdk/src/utils/relay.ts @@ -1,10 +1,11 @@ -export const RELAYS_PROD = ['wss://relay.n057r.club', 'wss://relay.nostr.net']; +export const RELAYS_PROD = ['wss://relay.n057r.club', 'wss://relay.nostr.net', "wss://relay.primal.net", "wss://relay.nostr.band", "wss://purplepag.es"]; export const AFK_RELAYS = process.env.EXPO_NODE_ENV == 'production' || process.env.NODE_ENV == 'production' ? [ // 'wss://nostr.joyboy.community', 'wss://nostr-relay-nestjs-production.up.railway.app', + ...RELAYS_PROD // 'ws://localhost:3000', // comment if you don't run a relayer in localhost ] : [ @@ -12,6 +13,7 @@ export const AFK_RELAYS = // 'ws://nostr-relay-nestjs-production.up.railway.app', // 'wss://nostr-relay-nestjs-production.up.railway.app', 'ws://localhost:8080', // comment if you don't run a relayer in localhost + ...RELAYS_PROD ]; // export const AFK_RELAYS = [ From 23ab7e4d9492c43866642bdb1b589891313a1cb8 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Thu, 15 Aug 2024 16:44:06 +0200 Subject: [PATCH 2/8] modal refacto + ui + cairo --- apps/mobile/src/app/Wrapper.tsx | 9 +- apps/mobile/src/assets/icons.tsx | 2 +- apps/mobile/src/components/Modalize/styles.ts | 6 +- apps/mobile/src/context/TipModal.tsx | 1 + apps/mobile/src/context/TipModalStarknet.tsx | 62 ++++ .../src/hooks/modals/useTipStarknetModal.ts | 13 + apps/mobile/src/modules/Post/index.tsx | 75 +++-- apps/mobile/src/modules/Post/styles.ts | 12 +- apps/mobile/src/modules/TipModal/index.tsx | 306 ++++++------------ .../src/modules/TipModal/starknet/form.tsx | 226 +++++++++++++ .../src/modules/TipModal/starknet/index.tsx | 239 ++++++++++++++ .../src/modules/TipModal/starknet/styles.ts | 111 +++++++ apps/mobile/src/modules/TipModal/styles.ts | 3 +- apps/mobile/src/screens/Feed/styles.ts | 2 + apps/mobile/src/screens/Settings/index.tsx | 7 +- onchain/src/keys/keys.cairo | 2 +- onchain/src/launchpad/launchpad.cairo | 217 ++++++++----- onchain/src/types/launchpad_types.cairo | 4 +- packages/afk_nostr_sdk/src/store/settings.ts | 11 +- packages/afk_nostr_sdk/src/utils/relay.ts | 3 +- 20 files changed, 973 insertions(+), 338 deletions(-) create mode 100644 apps/mobile/src/context/TipModalStarknet.tsx create mode 100644 apps/mobile/src/hooks/modals/useTipStarknetModal.ts create mode 100644 apps/mobile/src/modules/TipModal/starknet/form.tsx create mode 100644 apps/mobile/src/modules/TipModal/starknet/index.tsx create mode 100644 apps/mobile/src/modules/TipModal/starknet/styles.ts diff --git a/apps/mobile/src/app/Wrapper.tsx b/apps/mobile/src/app/Wrapper.tsx index 28ffa1b0..ba9693d6 100644 --- a/apps/mobile/src/app/Wrapper.tsx +++ b/apps/mobile/src/app/Wrapper.tsx @@ -14,8 +14,9 @@ import { WalletModalProvider } from '../context/WalletModal'; import App from './App'; import { StarknetProvider } from './StarknetProvider'; // import { NostrProvider } from '../context/NostrContext'; -import {TanstackProvider} from 'afk_nostr_sdk'; -import {NostrProvider} from 'afk_nostr_sdk'; +import { TanstackProvider } from 'afk_nostr_sdk'; +import { NostrProvider } from 'afk_nostr_sdk'; +import { TipModalStarknetProvider } from '../context/TipModalStarknet'; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 2 } }, }); @@ -26,7 +27,9 @@ const ModalProviders = ({ children }: { children: React.ReactNode }) => { - {children} + + {children} + diff --git a/apps/mobile/src/assets/icons.tsx b/apps/mobile/src/assets/icons.tsx index 43e471bc..089321e6 100644 --- a/apps/mobile/src/assets/icons.tsx +++ b/apps/mobile/src/assets/icons.tsx @@ -3,7 +3,7 @@ import Svg, { G, Path, Rect, SvgProps } from 'react-native-svg'; export const AddPostIcon: React.FC = (props) => ( ({ modal: { backgroundColor: theme.colors.surface, + // width:Dimensions.get("window").width >= 1024 ? 300 : "100%", + // flex: 1, + // justifyContent: 'center', + // alignItems: 'center' }, header: { diff --git a/apps/mobile/src/context/TipModal.tsx b/apps/mobile/src/context/TipModal.tsx index 7d47c973..4bac9f5b 100644 --- a/apps/mobile/src/context/TipModal.tsx +++ b/apps/mobile/src/context/TipModal.tsx @@ -2,6 +2,7 @@ import {NDKEvent} from '@nostr-dev-kit/ndk'; import {createContext, useCallback, useMemo, useRef, useState} from 'react'; import {TipModal} from '../modules/TipModal'; + import {TipSuccessModal, TipSuccessModalProps} from '../modules/TipSuccessModal'; export type TipModalContextType = { diff --git a/apps/mobile/src/context/TipModalStarknet.tsx b/apps/mobile/src/context/TipModalStarknet.tsx new file mode 100644 index 00000000..1633d84b --- /dev/null +++ b/apps/mobile/src/context/TipModalStarknet.tsx @@ -0,0 +1,62 @@ +import {NDKEvent} from '@nostr-dev-kit/ndk'; +import {createContext, useCallback, useMemo, useRef, useState} from 'react'; + +import {TipModalStarknet} from '../modules/TipModal/starknet'; +import {TipSuccessModal, TipSuccessModalProps} from '../modules/TipSuccessModal'; + +export type TipModalContextType = { + show: (event: NDKEvent) => void; + hide: () => void; + + showSuccess: (props: TipSuccessModalProps) => void; + hideSuccess: () => void; +}; + +export const TipModalStarknetContext = createContext(null); + +export const TipModalStarknetProvider: React.FC = ({children}) => { + const tipModalRef = useRef(null); + + const [event, setEvent] = useState(); + const [successModal, setSuccessModal] = useState(null); + + const show = useCallback((event: NDKEvent) => { + setEvent(event); + tipModalRef.current?.open(); + }, []); + + const hide = useCallback(() => { + tipModalRef.current?.close(); + setEvent(undefined); + }, []); + + const showSuccess = useCallback((props: TipSuccessModalProps) => { + setSuccessModal(props); + }, []); + + const hideSuccess = useCallback(() => { + setSuccessModal(null); + }, []); + + const context = useMemo( + () => ({show, hide, showSuccess, hideSuccess}), + [show, hide, showSuccess, hideSuccess], + ); + + return ( + + {children} + + + + {successModal && } + + ); +}; diff --git a/apps/mobile/src/hooks/modals/useTipStarknetModal.ts b/apps/mobile/src/hooks/modals/useTipStarknetModal.ts new file mode 100644 index 00000000..33bb3ea7 --- /dev/null +++ b/apps/mobile/src/hooks/modals/useTipStarknetModal.ts @@ -0,0 +1,13 @@ +import {useContext} from 'react'; + +import {TipModalContext} from '../../context/TipModal'; + +export const useTipModal = () => { + const context = useContext(TipModalContext); + + if (!context) { + throw new Error('useTipModal must be used within a TipModalProvider'); + } + + return context; +}; diff --git a/apps/mobile/src/modules/Post/index.tsx b/apps/mobile/src/modules/Post/index.tsx index f283e6a6..afa0aa85 100644 --- a/apps/mobile/src/modules/Post/index.tsx +++ b/apps/mobile/src/modules/Post/index.tsx @@ -38,6 +38,7 @@ export const Post: React.FC = ({ asComment, event }) => { const navigation = useNavigation(); + const [dimensionsMedia, setMediaDimensions] = useState([250,300]) const { publicKey } = useAuth(); const { show: showTipModal } = useTipModal(); const { data: profile } = useProfile({ publicKey: event?.pubkey }); @@ -77,12 +78,11 @@ export const Post: React.FC = ({ asComment, event }) => { const imageTag = event.tags.find((tag) => tag[0] === 'image'); if (!imageTag) return; - - let dimensions = [200, 2500] + /** @TODO finish good dimensions with correct ratio and base on the Platform */ + let dimensions = [250, 300] if (imageTag[2]) { - // const dimensions = imageTag[2].split('x').map(Number); - dimensions = imageTag[2].split('x').map(Number); - + dimensions = imageTag[2].split('x').map(Number) + setMediaDimensions(dimensions) } return { uri: imageTag[1], width: dimensions[0], height: dimensions[1] }; }, [event?.tags]); @@ -220,7 +220,11 @@ export const Post: React.FC = ({ asComment, event }) => { source={postSource} style={[ styles.contentImage, - { aspectRatio: getImageRatio(postSource.width, postSource.height) }, + { + // width:dimensionsMedia[0], + height:dimensionsMedia[1], + aspectRatio: getImageRatio(postSource.width, postSource.height) + }, ]} /> )} @@ -231,28 +235,59 @@ export const Post: React.FC = ({ asComment, event }) => { {!asComment && ( - - - + + + + + + + {comments.data?.pages.flat().length} comments + + + + + { + if (!event) return; + showTipModal(event); + }} + > + { + if (!event) return; + + showTipModal(event); + }} + /> + + + - - {comments.data?.pages.flat().length} comments - - - - { if (!event) return; showTipModal(event); - setMenuOpen(false); }} > - - + + */} {/* = ({ asComment, event }) => { setMenuOpen(true)} /> } > - {/* */} + ({ marginBottom: Spacing.medium, color: theme.colors.text, }, + // contentImage: { + // width: '100%', + // height: 'auto', + // resizeMode: 'cover', + // borderRadius: 8, + // overflow: 'hidden', + // marginTop: Spacing.small, + // }, contentImage: { width: '100%', - height: 'auto', - resizeMode: 'cover', + height: '100%', + // resizeMode: 'cover', borderRadius: 8, overflow: 'hidden', marginTop: Spacing.small, diff --git a/apps/mobile/src/modules/TipModal/index.tsx b/apps/mobile/src/modules/TipModal/index.tsx index 81ec159d..ac71bc5a 100644 --- a/apps/mobile/src/modules/TipModal/index.tsx +++ b/apps/mobile/src/modules/TipModal/index.tsx @@ -1,226 +1,102 @@ -import {NDKEvent} from '@nostr-dev-kit/ndk'; -import {useAccount} from '@starknet-react/core'; -import {forwardRef, useState} from 'react'; -import {View} from 'react-native'; -import {CallData, uint256} from 'starknet'; - -import {Avatar, Button, Input, Modalize, Picker, Text} from '../../components'; -import {ESCROW_ADDRESSES} from '../../constants/contracts'; -import {CHAIN_ID} from '../../constants/env'; -import {DEFAULT_TIMELOCK, Entrypoint} from '../../constants/misc'; -import {TOKENS, TokenSymbol} from '../../constants/tokens'; -import { useStyles, useWaitConnection} from '../../hooks'; -import {useProfile} from "afk_nostr_sdk" - -import {useTransactionModal} from '../../hooks/modals'; -import {useDialog} from '../../hooks/modals/useDialog'; -import {useTransaction} from '../../hooks/modals/useTransaction'; -import {useWalletModal} from '../../hooks/modals/useWalletModal'; -import {TipSuccessModalProps} from '../TipSuccessModal'; +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { useAccount } from '@starknet-react/core'; +import { forwardRef, useState } from 'react'; +import { Pressable, View } from 'react-native'; +import { CallData, uint256 } from 'starknet'; + +import { Avatar, Button, Input, Modalize, Picker, Text } from '../../components'; +import { ESCROW_ADDRESSES } from '../../constants/contracts'; +import { CHAIN_ID } from '../../constants/env'; +import { DEFAULT_TIMELOCK, Entrypoint } from '../../constants/misc'; +import { TOKENS, TokenSymbol } from '../../constants/tokens'; +import { useStyles, useTheme, useWaitConnection } from '../../hooks'; +import { useProfile } from "afk_nostr_sdk" + +import { useTransactionModal } from '../../hooks/modals'; +import { useDialog } from '../../hooks/modals/useDialog'; +import { useTransaction } from '../../hooks/modals/useTransaction'; +import { useWalletModal } from '../../hooks/modals/useWalletModal'; +import { TipSuccessModalProps } from '../TipSuccessModal'; import stylesheet from './styles'; +import { FormTipStarknet } from './starknet/form'; +import { TipModalStarknet } from './starknet'; export type TipModal = Modalize; +enum TipTypeMode { + ZAP, + STARKNET +} export type TipModalProps = { - event?: NDKEvent; + event?: NDKEvent; - show: (event: NDKEvent) => void; - hide: () => void; - showSuccess: (props: TipSuccessModalProps) => void; - hideSuccess: () => void; + show: (event: NDKEvent) => void; + hide: () => void; + showSuccess: (props: TipSuccessModalProps) => void; + hideSuccess: () => void; }; export const TipModal = forwardRef( - ({event, hide: hideTipModal, showSuccess, hideSuccess}, ref) => { - const styles = useStyles(stylesheet); - - const [token, setToken] = useState(TokenSymbol.ETH); - const [amount, setAmount] = useState(''); - - const {data: profile} = useProfile({publicKey: event?.pubkey}); - - const account = useAccount(); - const walletModal = useWalletModal(); - const sendTransaction = useTransaction(); - const {hide: hideTransactionModal} = useTransactionModal(); - const waitConnection = useWaitConnection(); - - const {showDialog, hideDialog} = useDialog(); - - const isActive = !!amount && !!token; - - const onTipPress = async () => { - if (!account.address) { - walletModal.show(); - - const result = await waitConnection(); - if (!result) return; - } - - const amountUint256 = uint256.bnToUint256( - Math.ceil(Number(amount) * 10 ** TOKENS[token][CHAIN_ID].decimals), - ); - - const approveCallData = CallData.compile([ - ESCROW_ADDRESSES[CHAIN_ID], // Contract address - amountUint256, // Amount - ]); - - const depositCallData = CallData.compile([ - amountUint256, // Amount - TOKENS[token][CHAIN_ID].address, // Token address - uint256.bnToUint256(`0x${event?.pubkey}`), // Recipient nostr pubkey - DEFAULT_TIMELOCK, // timelock - ]); - - const receipt = await sendTransaction({ - calls: [ - { - contractAddress: TOKENS[token][CHAIN_ID].address, - entrypoint: Entrypoint.APPROVE, - calldata: approveCallData, - }, - { - contractAddress: ESCROW_ADDRESSES[CHAIN_ID], - entrypoint: Entrypoint.DEPOSIT, - calldata: depositCallData, - }, - ], - }); - - if (receipt?.isSuccess()) { - hideTipModal(); - hideTransactionModal(); - showSuccess({ - amount: Number(amount), - symbol: token, - user: - (profile?.nip05 && `@${profile.nip05}`) ?? - profile?.displayName ?? - profile?.name ?? - event?.pubkey, - hide: hideSuccess, - }); - } else { - let description = 'Please Try Again Later.'; - if (receipt?.isRejected()) { - description = receipt.transaction_failure_reason.error_message; - } - - showDialog({ - title: 'Failed to send the tip', - description, - buttons: [{type: 'secondary', label: 'Close', onPress: () => hideDialog()}], - }); - } - }; - - return ( - - - - - - - - - {profile?.displayName ?? profile?.name ?? event?.pubkey} - - - {profile?.nip05 && ( - - @{profile?.nip05} - - )} - - - - - - {event?.content} - - - - - - setToken(itemValue as TokenSymbol)} - > - {Object.values(TOKENS).map((tkn) => ( - - ))} - - - - - - - - - - Sending - - - {amount.length > 0 && token.length > 0 ? ( - - {amount} {token} - - ) : ( - - ... - - )} - - - - - to - - - {(profile?.nip05 && `@${profile.nip05}`) ?? - profile?.displayName ?? - profile?.name ?? - event?.pubkey} - - - - - - - - - - Tip friends and support creators with your favorite tokens. - - - ); - }, + ({ event, hide: hideTipModal, showSuccess, hideSuccess, show, hide }, ref) => { + const styles = useStyles(stylesheet); + const [tipType, setTipType] = useState(TipTypeMode.STARKNET); + const theme = useTheme() + return ( + + + + + Starknet tip + + + + + Zap coming soon + + + + + { + tipType == TipTypeMode.STARKNET && + + + } + + + ); + }, ); TipModal.displayName = 'TipModal'; diff --git a/apps/mobile/src/modules/TipModal/starknet/form.tsx b/apps/mobile/src/modules/TipModal/starknet/form.tsx new file mode 100644 index 00000000..14358662 --- /dev/null +++ b/apps/mobile/src/modules/TipModal/starknet/form.tsx @@ -0,0 +1,226 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { useAccount } from '@starknet-react/core'; +import React, { forwardRef, useState } from 'react'; +import { Pressable, View } from 'react-native'; +import { CallData, uint256 } from 'starknet'; + +import { Avatar, Button, Input, Modalize, Picker, Text } from '../../../components'; +import { ESCROW_ADDRESSES } from '../../../constants/contracts'; +import { CHAIN_ID } from '../../../constants/env'; +import { DEFAULT_TIMELOCK, Entrypoint } from '../../../constants/misc'; +import { TOKENS, TokenSymbol } from '../../../constants/tokens'; +import { useStyles, useWaitConnection } from '../../../hooks'; +import { useProfile } from "afk_nostr_sdk" + +import { useTransactionModal } from '../../../hooks/modals'; +import { useDialog } from '../../../hooks/modals/useDialog'; +import { useTransaction } from '../../../hooks/modals/useTransaction'; +import { useWalletModal } from '../../../hooks/modals/useWalletModal'; +import { TipSuccessModalProps } from '../../TipSuccessModal'; +import stylesheet from './styles'; + +export type TipModalStarknet = Modalize; + +export type FormTipModalStarknetProps = { + event?: NDKEvent; + ref?: any + show: (event: NDKEvent) => void; + hide: () => void; + showSuccess: (props: TipSuccessModalProps) => void; + hideSuccess: () => void; +}; + +export const FormTipStarknet: React.FC = ( + { event, hide: hideTipModal, showSuccess, hideSuccess, ref }: FormTipModalStarknetProps,) => { + const styles = useStyles(stylesheet); + + const [token, setToken] = useState(TokenSymbol.ETH); + const [amount, setAmount] = useState(''); + + const { data: profile } = useProfile({ publicKey: event?.pubkey }); + + const account = useAccount(); + const walletModal = useWalletModal(); + const sendTransaction = useTransaction(); + const { hide: hideTransactionModal } = useTransactionModal(); + const waitConnection = useWaitConnection(); + + const { showDialog, hideDialog } = useDialog(); + + const isActive = !!amount && !!token; + + const onTipPress = async () => { + if (!account.address) { + walletModal.show(); + + const result = await waitConnection(); + if (!result) return; + } + + const amountUint256 = uint256.bnToUint256( + Math.ceil(Number(amount) * 10 ** TOKENS[token][CHAIN_ID].decimals), + ); + + const approveCallData = CallData.compile([ + ESCROW_ADDRESSES[CHAIN_ID], // Contract address + amountUint256, // Amount + ]); + + const depositCallData = CallData.compile([ + amountUint256, // Amount + TOKENS[token][CHAIN_ID].address, // Token address + uint256.bnToUint256(`0x${event?.pubkey}`), // Recipient nostr pubkey + DEFAULT_TIMELOCK, // timelock + ]); + + const receipt = await sendTransaction({ + calls: [ + { + contractAddress: TOKENS[token][CHAIN_ID].address, + entrypoint: Entrypoint.APPROVE, + calldata: approveCallData, + }, + { + contractAddress: ESCROW_ADDRESSES[CHAIN_ID], + entrypoint: Entrypoint.DEPOSIT, + calldata: depositCallData, + }, + ], + }); + + if (receipt?.isSuccess()) { + hideTipModal(); + hideTransactionModal(); + showSuccess({ + amount: Number(amount), + symbol: token, + user: + (profile?.nip05 && `@${profile.nip05}`) ?? + profile?.displayName ?? + profile?.name ?? + event?.pubkey, + hide: hideSuccess, + }); + } else { + let description = 'Please Try Again Later.'; + if (receipt?.isRejected()) { + description = receipt.transaction_failure_reason.error_message; + } + + showDialog({ + title: 'Failed to send the tip', + description, + buttons: [{ type: 'secondary', label: 'Close', onPress: () => hideDialog() }], + }); + } + }; + + return ( + + + + + + + + + + {profile?.displayName ?? profile?.name ?? event?.pubkey} + + + {profile?.nip05 && ( + + @{profile?.nip05} + + )} + + + + + + {event?.content} + + + + + + setToken(itemValue as TokenSymbol)} + > + {Object.values(TOKENS).map((tkn) => ( + + ))} + + + + + + + + + + Sending + + + {amount.length > 0 && token.length > 0 ? ( + + {amount} {token} + + ) : ( + + ... + + )} + + + + + to + + + {(profile?.nip05 && `@${profile.nip05}`) ?? + profile?.displayName ?? + profile?.name ?? + event?.pubkey} + + + + + + + + + + Tip friends and support creators with your favorite tokens. + + + ); +} + // FormTipStarknet.displayName = 'FormTipStarknet'; diff --git a/apps/mobile/src/modules/TipModal/starknet/index.tsx b/apps/mobile/src/modules/TipModal/starknet/index.tsx new file mode 100644 index 00000000..ea126641 --- /dev/null +++ b/apps/mobile/src/modules/TipModal/starknet/index.tsx @@ -0,0 +1,239 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { useAccount } from '@starknet-react/core'; +import { forwardRef, useState } from 'react'; +import { Pressable, View } from 'react-native'; +import { CallData, uint256 } from 'starknet'; + +import { Avatar, Button, Input, Modalize, Picker, Text } from '../../../components'; +import { ESCROW_ADDRESSES } from '../../../constants/contracts'; +import { CHAIN_ID } from '../../../constants/env'; +import { DEFAULT_TIMELOCK, Entrypoint } from '../../../constants/misc'; +import { TOKENS, TokenSymbol } from '../../../constants/tokens'; +import { useStyles, useWaitConnection } from '../../../hooks'; +import { useProfile } from "afk_nostr_sdk" + +import { useTransactionModal } from '../../../hooks/modals'; +import { useDialog } from '../../../hooks/modals/useDialog'; +import { useTransaction } from '../../../hooks/modals/useTransaction'; +import { useWalletModal } from '../../../hooks/modals/useWalletModal'; +import { TipSuccessModalProps } from '../../TipSuccessModal'; +import stylesheet from './styles'; + +export type TipModalStarknet = Modalize; + +export type TipModalStarknetProps = { + event?: NDKEvent; + + show: (event: NDKEvent) => void; + hide: () => void; + showSuccess: (props: TipSuccessModalProps) => void; + hideSuccess: () => void; +}; + +export const TipModalStarknet = forwardRef( + ({ event, hide: hideTipModal, showSuccess, hideSuccess }, ref) => { + const styles = useStyles(stylesheet); + + const [token, setToken] = useState(TokenSymbol.ETH); + const [amount, setAmount] = useState(''); + + const { data: profile } = useProfile({ publicKey: event?.pubkey }); + + const account = useAccount(); + const walletModal = useWalletModal(); + const sendTransaction = useTransaction(); + const { hide: hideTransactionModal } = useTransactionModal(); + const waitConnection = useWaitConnection(); + + const { showDialog, hideDialog } = useDialog(); + + const isActive = !!amount && !!token; + + const onTipPress = async () => { + if (!account.address) { + walletModal.show(); + + const result = await waitConnection(); + if (!result) return; + } + + const amountUint256 = uint256.bnToUint256( + Math.ceil(Number(amount) * 10 ** TOKENS[token][CHAIN_ID].decimals), + ); + + const approveCallData = CallData.compile([ + ESCROW_ADDRESSES[CHAIN_ID], // Contract address + amountUint256, // Amount + ]); + + const depositCallData = CallData.compile([ + amountUint256, // Amount + TOKENS[token][CHAIN_ID].address, // Token address + uint256.bnToUint256(`0x${event?.pubkey}`), // Recipient nostr pubkey + DEFAULT_TIMELOCK, // timelock + ]); + + const receipt = await sendTransaction({ + calls: [ + { + contractAddress: TOKENS[token][CHAIN_ID].address, + entrypoint: Entrypoint.APPROVE, + calldata: approveCallData, + }, + { + contractAddress: ESCROW_ADDRESSES[CHAIN_ID], + entrypoint: Entrypoint.DEPOSIT, + calldata: depositCallData, + }, + ], + }); + + if (receipt?.isSuccess()) { + hideTipModal(); + hideTransactionModal(); + showSuccess({ + amount: Number(amount), + symbol: token, + user: + (profile?.nip05 && `@${profile.nip05}`) ?? + profile?.displayName ?? + profile?.name ?? + event?.pubkey, + hide: hideSuccess, + }); + } else { + let description = 'Please Try Again Later.'; + if (receipt?.isRejected()) { + description = receipt.transaction_failure_reason.error_message; + } + + showDialog({ + title: 'Failed to send the tip', + description, + buttons: [{ type: 'secondary', label: 'Close', onPress: () => hideDialog() }], + }); + } + }; + + return ( + + + + + Starknet tip + + + + + Zap coming soon + + + + + + + + + + + + {profile?.displayName ?? profile?.name ?? event?.pubkey} + + + {profile?.nip05 && ( + + @{profile?.nip05} + + )} + + + + + + {event?.content} + + + + + + setToken(itemValue as TokenSymbol)} + > + {Object.values(TOKENS).map((tkn) => ( + + ))} + + + + + + + + + + Sending + + + {amount.length > 0 && token.length > 0 ? ( + + {amount} {token} + + ) : ( + + ... + + )} + + + + + to + + + {(profile?.nip05 && `@${profile.nip05}`) ?? + profile?.displayName ?? + profile?.name ?? + event?.pubkey} + + + + + + + + + + Tip friends and support creators with your favorite tokens. + + + ); + }, +); +TipModalStarknet.displayName = 'TipModalStarknet'; diff --git a/apps/mobile/src/modules/TipModal/starknet/styles.ts b/apps/mobile/src/modules/TipModal/starknet/styles.ts new file mode 100644 index 00000000..53d34a7b --- /dev/null +++ b/apps/mobile/src/modules/TipModal/starknet/styles.ts @@ -0,0 +1,111 @@ +import {Platform} from 'react-native'; + +import {Spacing, ThemedStyleSheet} from '../../../styles'; + +export default ThemedStyleSheet((theme) => ({ + modal: { + paddingBottom: Spacing.xxlarge, + }, + + header: { + width: '100%', + marginBottom: Spacing.medium, + paddingTop: Spacing.small, + paddingLeft: Spacing.small, + paddingBottom: Spacing.medium, + paddingRight: Spacing.small, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + icon: { + color: theme.colors.primary, + }, + + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + cardContent: { + flex: 1, + flexDirection: 'row', + gap: 5, + alignItems: 'center', + }, + cardInfo: { + flex: 1, + paddingRight: Spacing.small, + }, + + title: { + marginBottom: Spacing.xsmall, + }, + + cardContentText: { + paddingTop: Spacing.small, + paddingRight: Spacing.small, + color: theme.colors.text, + }, + likes: { + flexDirection: 'row', + gap: 3, + alignItems: 'center', + }, + + sending: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + sendingText: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + gap: Spacing.xsmall, + }, + + recipient: { + flex: 1, + flexDirection: 'row', + gap: 4, + alignItems: 'center', + justifyContent: 'flex-end', + }, + card: { + width: '100%', + backgroundColor: theme.colors.primaryLight, + borderRadius: 16, + padding: Spacing.medium, + }, + + comment: { + paddingTop: Spacing.small, + }, + + pickerContainer: { + flex: 1, + gap: 20, + paddingTop: Spacing.xxlarge, + paddingBottom: Spacing.xxlarge, + }, + + more: { + paddingLeft: Spacing.small, + color: theme.colors.primary, + }, + + likeIcon: { + color: theme.colors.primary, + }, + + content: { + padding: Spacing.xlarge, + paddingTop: Platform.OS === 'ios' ? Spacing.xlarge : Spacing.xsmall, + }, + + submitButton: { + paddingTop: Spacing.xlarge, + }, +})); diff --git a/apps/mobile/src/modules/TipModal/styles.ts b/apps/mobile/src/modules/TipModal/styles.ts index 098c0ef2..e0b3da01 100644 --- a/apps/mobile/src/modules/TipModal/styles.ts +++ b/apps/mobile/src/modules/TipModal/styles.ts @@ -1,10 +1,11 @@ -import {Platform} from 'react-native'; +import {Dimensions, Platform} from 'react-native'; import {Spacing, ThemedStyleSheet} from '../../styles'; export default ThemedStyleSheet((theme) => ({ modal: { paddingBottom: Spacing.xxlarge, + // width:Dimensions.get("window").width >= 1024 ? 300 : "100%", }, header: { diff --git a/apps/mobile/src/screens/Feed/styles.ts b/apps/mobile/src/screens/Feed/styles.ts index 2658a9d2..139ad39f 100644 --- a/apps/mobile/src/screens/Feed/styles.ts +++ b/apps/mobile/src/screens/Feed/styles.ts @@ -23,6 +23,8 @@ export default ThemedStyleSheet((theme) => ({ position: 'absolute', bottom: Spacing.large, right: Spacing.pagePadding, + // backgroundColor:theme.colors.primary, + color:theme.colors.primary }, stories: { diff --git a/apps/mobile/src/screens/Settings/index.tsx b/apps/mobile/src/screens/Settings/index.tsx index 3222807e..b026842c 100644 --- a/apps/mobile/src/screens/Settings/index.tsx +++ b/apps/mobile/src/screens/Settings/index.tsx @@ -18,7 +18,8 @@ export const Setttings: React.FC = ({ navigation }) => { const { theme, toggleTheme } = useTheme(); const { relays, setRelays } = useSettingsStore() const { publicKey } = useAuth() - const RELAYS_USED = relays ?? AFK_RELAYS + // const RELAYS_USED = relays ?? AFK_RELAYS + const RELAYS_USED = relays return ( = ({ navigation }) => { AFK: All relays used - {RELAYS_USED?.map((r) => { + {RELAYS_USED?.map((r, i) => { return ( - + Relay: {r} ) diff --git a/onchain/src/keys/keys.cairo b/onchain/src/keys/keys.cairo index 7a4cb08f..1ade02e2 100644 --- a/onchain/src/keys/keys.cairo +++ b/onchain/src/keys/keys.cairo @@ -42,7 +42,7 @@ pub trait IKeysMarketplace { fn set_protocol_fee_destination( ref self: TContractState, protocol_fee_destination: ContractAddress ); - fn instantiate_keys(ref self: TContractState,// token_quote: TokenQuoteBuyKeys, + fn instantiate_keys(ref self: TContractState, // token_quote: TokenQuoteBuyKeys, // bonding_type: KeysMarketplace::BondingType, ); fn instantiate_keys_with_nostr( diff --git a/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index 8b001a40..9362d25a 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -238,16 +238,6 @@ mod LaunchpadMarketplace { let token_address = self ._create_token(caller, symbol, name, initial_supply, contract_address_salt); - self - .emit( - CreateToken { - caller: get_caller_address(), - token_address: token_address, - total_supply: initial_supply.clone(), - initial_supply - } - ); - token_address } @@ -259,36 +249,18 @@ mod LaunchpadMarketplace { initial_supply: u256, contract_address_salt: felt252 ) -> ContractAddress { - let caller = get_caller_address(); + let contract_address = get_contract_address(); let token_address = self - ._create_token(caller, symbol, name, initial_supply, contract_address_salt); - - - self - .emit( - CreateToken { - caller: get_caller_address(), - token_address: token_address, - total_supply: initial_supply.clone(), - initial_supply - } - ); - - + ._create_token(contract_address, symbol, name, initial_supply, contract_address_salt); 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 - - self._launch_token(coin_address); - } @@ -303,22 +275,24 @@ mod LaunchpadMarketplace { let protocol_fee_percent = self.protocol_fee_percent.read(); let creator_fee_percent = self.creator_fee_percent.read(); // 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, - initial_key_price: token_quote.initial_key_price, - bonding_curve_type: old_launch.bonding_curve_type, - total_supply: old_launch.total_supply, - available_supply: old_launch.available_supply, - price: old_launch.price, - liquidity_raised: old_launch.liquidity_raised, - token_holded:old_launch.token_holded, - is_liquidity_launch:old_launch.is_liquidity_launch, - - - }; + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + + let mut pool_coin = old_launch.clone(); + // 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, + // initial_key_price: token_quote.initial_key_price, + // bonding_curve_type: old_launch.bonding_curve_type, + // total_supply: old_launch.total_supply, + // available_supply: old_launch.available_supply, + // price: old_launch.price, + // liquidity_raised: old_launch.liquidity_raised, + // token_holded:old_launch.token_holded, + // is_liquidity_launch:old_launch.is_liquidity_launch, + + // }; let total_price = self.get_price_of_supply_key(coin_address, amount, false); println!("total price cal {:?}", total_price); let amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; @@ -350,15 +324,16 @@ mod LaunchpadMarketplace { self.shares_by_users.write((get_caller_address(), coin_address), share_user.clone()); self.launched_coins.write(coin_address, pool_coin.clone()); - 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 ); - + // Pay with quote token erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); + // Sent coin + memecoin.transfer(get_caller_address(), amount); self .emit( @@ -410,8 +385,8 @@ mod LaunchpadMarketplace { 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, + token_holded: old_pool.token_holded, + is_liquidity_launch: old_pool.is_liquidity_launch, }; let mut total_price = self.get_price_of_supply_key(coin_address, amount, true); @@ -591,12 +566,21 @@ mod LaunchpadMarketplace { }; self.token_created.write(token_address, token); - + + self + .emit( + CreateToken { + caller: get_caller_address(), + token_address: token_address, + total_supply: initial_supply.clone(), + initial_supply + } + ); token_address } - fn _launch_token(ref self: ContractState, coin_address:ContractAddress) { + 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"); @@ -606,7 +590,8 @@ mod LaunchpadMarketplace { let bond_type = BondingType::Linear; let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; - let total_supply = erc20.total_supply(); + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + let total_supply = memecoin.total_supply(); // // @TODO Deploy an ERC404 // // Option for liquidity providing and Trading let launch_token_pump = TokenLaunch { @@ -622,17 +607,18 @@ mod LaunchpadMarketplace { initial_key_price: token_to_use.initial_key_price, price: 0, liquidity_raised: 0, - token_holded:0, - is_liquidity_launch:false, - - // token_holded:1 + token_holded: 0, + is_liquidity_launch: false, + // token_holded:1 }; // Send supply need to launch your coin - let amount_needed = total_supply.clone(); + println!("amount_needed {:?}", amount_needed); - // erc20.transfer_from(get_caller_address(), get_contract_address(), amount_needed); + let allowance = memecoin.allowance(get_caller_address(), get_contract_address()); + println!("test allowance {:?}", allowance); + // memecoin.transfer_from(get_caller_address(), get_contract_address(), amount_needed); self.launched_coins.write(coin_address, launch_token_pump.clone()); self @@ -646,21 +632,6 @@ mod LaunchpadMarketplace { ); } - fn _calculate_total_cost( - price: u256, - actual_supply: u256, - amount: u256, - initial_key_price: u256, - step_increase_linear: u256 - ) -> u256 { - let mut total_supply = actual_supply.clone(); - let mut actual_supply = total_supply; - let final_supply = total_supply + amount; - let start_price = initial_key_price + (step_increase_linear * actual_supply); - let end_price = initial_key_price + (step_increase_linear * final_supply); - let total_price = amount * (start_price + end_price) / 2; - total_price - } } } @@ -830,8 +801,11 @@ mod tests { 21_000_000 * pow_256(10, 18) } + #[test] fn launchpad_end_to_end() { + println!("launchpad_enter_end_to_end"); + let (sender_address, erc20, launchpad) = request_fixture(); let amount_key_buy = 1_u256; cheat_caller_address_global(sender_address); @@ -844,8 +818,9 @@ mod tests { start_cheat_caller_address(launchpad.contract_address, sender_address); + println!("create and launch token"); let token_address = launchpad - .create_token( + .create_and_launch_token( // owner: OWNER(), symbol: SYMBOL(), name: NAME(), @@ -854,26 +829,96 @@ mod tests { ); println!("test token_address {:?}", token_address); + + let memecoin = IERC20Dispatcher {contract_address:token_address}; + start_cheat_caller_address(memecoin.contract_address, sender_address); + + let total_supply = memecoin.total_supply(); + println!(" memecoin total_supply {:?}", total_supply); + memecoin.approve(launchpad.contract_address, total_supply); + + + let allowance = memecoin.allowance(sender_address, launchpad.contract_address); + println!("test allowance meme coin{}", allowance); + // Launch coin pool // Send total supply - println!("launch token"); + // Test buy coin + + println!("amount_to_paid",); + + // println!("all_keys {:?}", all_keys); + let amount_to_paid = launchpad + .get_price_of_supply_key(token_address, amount_key_buy + 1, false, // 1, + // BondingType::Basic, default_token.clone() + ); + println!("test amount_to_paid {:?}", amount_to_paid); - let total_supply=erc20.total_supply(); - println!("total_supply {:?}", total_supply); - erc20.approve(launchpad.contract_address, total_supply); + 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); - let pool = launchpad.launch_token(token_address); - // Test buy coin + start_cheat_caller_address(launchpad.contract_address, sender_address); + println!("buy coin",); + + launchpad.buy_coin(token_address, amount_key_buy); + + + + } - println!("amount_to_paid", ); + #[test] + fn launchpad_integration() { + println!("launchpad_integration"); + + let (sender_address, erc20, launchpad) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // Call a view function of the contract + // Check default token used + let default_token = launchpad.get_default_token(); + assert(default_token.token_address == erc20.contract_address, 'no default token'); + assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + + let token_address = launchpad + .create_token( + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), + ); + println!("test token_address {:?}", token_address); + + + let memecoin = IERC20Dispatcher {contract_address:token_address}; + start_cheat_caller_address(memecoin.contract_address, sender_address); + + let total_supply = memecoin.total_supply(); + println!(" memecoin total_supply {:?}", total_supply); + memecoin.approve(launchpad.contract_address, total_supply); + + + let allowance = memecoin.allowance(sender_address, launchpad.contract_address); + println!("test allowance meme coin{}", allowance); + + // Launch coin pool + // Send total supply + println!("launch token"); + let pool = launchpad.launch_token(token_address); + // Test buy coin + println!("amount_to_paid",); // println!("all_keys {:?}", all_keys); let amount_to_paid = launchpad - .get_price_of_supply_key(token_address, amount_key_buy+1, false, // 1, + .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); @@ -887,8 +932,8 @@ mod tests { stop_cheat_caller_address(erc20.contract_address); start_cheat_caller_address(launchpad.contract_address, sender_address); - println!("buy coin", ); - + println!("buy coin",); + launchpad.buy_coin(token_address, amount_key_buy); } } diff --git a/onchain/src/types/launchpad_types.cairo b/onchain/src/types/launchpad_types.cairo index 86f05dec..1c7d49c9 100644 --- a/onchain/src/types/launchpad_types.cairo +++ b/onchain/src/types/launchpad_types.cairo @@ -52,8 +52,8 @@ pub struct TokenLaunch { pub created_at: u64, pub token_quote: TokenQuoteBuyKeys, pub liquidity_raised: u256, - pub token_holded:u256, - pub is_liquidity_launch:bool + pub token_holded: u256, + pub is_liquidity_launch: bool } #[derive(Drop, Serde, Copy, starknet::Store)] diff --git a/packages/afk_nostr_sdk/src/store/settings.ts b/packages/afk_nostr_sdk/src/store/settings.ts index 5624a724..cc5af215 100644 --- a/packages/afk_nostr_sdk/src/store/settings.ts +++ b/packages/afk_nostr_sdk/src/store/settings.ts @@ -11,11 +11,18 @@ type Action = { setRelays: (relays:string[]) => void; }; -export const settingsStore = createStore((set, get) => ({ - relays: undefined as unknown as string[], +const getDefaultValue = () => { + return { + relays:AFK_RELAYS + } +} +export const settingsStore = createStore( (set, get) => ({ + // relays: undefined as unknown as string[], + relays: getDefaultValue().relays, setRelays: (relays) => { set({relays}); }, })); + export const useSettingsStore = createBoundedUseStore(settingsStore); diff --git a/packages/afk_nostr_sdk/src/utils/relay.ts b/packages/afk_nostr_sdk/src/utils/relay.ts index 1eca0e50..2b724742 100644 --- a/packages/afk_nostr_sdk/src/utils/relay.ts +++ b/packages/afk_nostr_sdk/src/utils/relay.ts @@ -1,4 +1,5 @@ export const RELAYS_PROD = ['wss://relay.n057r.club', 'wss://relay.nostr.net', "wss://relay.primal.net", "wss://relay.nostr.band", "wss://purplepag.es"]; +export const RELAYS_TEST= ['wss://relay.n057r.club', 'wss://relay.nostr.net']; export const AFK_RELAYS = process.env.EXPO_NODE_ENV == 'production' || process.env.NODE_ENV == 'production' @@ -13,7 +14,7 @@ export const AFK_RELAYS = // 'ws://nostr-relay-nestjs-production.up.railway.app', // 'wss://nostr-relay-nestjs-production.up.railway.app', 'ws://localhost:8080', // comment if you don't run a relayer in localhost - ...RELAYS_PROD + ...RELAYS_TEST ]; // export const AFK_RELAYS = [ From 8181e57bd5631eb847998afeff6c86c6df1f6843 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Thu, 15 Aug 2024 18:54:04 +0200 Subject: [PATCH 3/8] todo fix issue buy coin --- onchain/.snfoundry_cache/.prev_tests_failed | 1 + onchain/src/launchpad/launchpad.cairo | 364 +++++++++++++------- onchain/src/lib.cairo | 2 + onchain/src/tests/keys.cairo | 2 + onchain/src/types/launchpad_types.cairo | 10 +- 5 files changed, 245 insertions(+), 134 deletions(-) create mode 100644 onchain/src/tests/keys.cairo diff --git a/onchain/.snfoundry_cache/.prev_tests_failed b/onchain/.snfoundry_cache/.prev_tests_failed index e69de29b..538233de 100644 --- a/onchain/.snfoundry_cache/.prev_tests_failed +++ b/onchain/.snfoundry_cache/.prev_tests_failed @@ -0,0 +1 @@ +afk::launchpad::launchpad::tests::launchpad_integration diff --git a/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index 9362d25a..a9e03860 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -18,6 +18,7 @@ pub trait ILaunchpadMarketplace { fn create_token( ref self: TContractState, + recipient: ContractAddress, symbol: felt252, name: felt252, initial_supply: u256, @@ -94,7 +95,8 @@ mod LaunchpadMarketplace { coin_class_hash: ClassHash, quote_tokens: LegacyMap::, quote_token: ContractAddress, - threshold_liquidity_raised_amount: u256, + threshold_liquidity: u256, + threshold_market_cap: u256, liquidity_raised_amount_in_dollar: u256, names: LegacyMap::, token_created: LegacyMap::, @@ -151,7 +153,8 @@ mod LaunchpadMarketplace { token_address: ContractAddress, step_increase_linear: u256, coin_class_hash: ClassHash, - threshold_liquidity_raised_amount: u256, + threshold_liquidity: u256, + threshold_market_cap: u256 ) { self.coin_class_hash.write(coin_class_hash); // AccessControl-related initialization @@ -171,7 +174,8 @@ 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.threshold_liquidity.write(threshold_liquidity); + self.threshold_market_cap.write(threshold_market_cap); self.protocol_fee_destination.write(admin); self.step_increase_linear.write(step_increase_linear); self.total_keys.write(0); @@ -229,6 +233,7 @@ mod LaunchpadMarketplace { // Create keys for an user fn create_token( ref self: ContractState, + recipient: ContractAddress, symbol: felt252, name: felt252, initial_supply: u256, @@ -236,12 +241,12 @@ mod LaunchpadMarketplace { ) -> ContractAddress { let caller = get_caller_address(); let token_address = self - ._create_token(caller, symbol, name, initial_supply, contract_address_salt); + ._create_token(recipient, caller, symbol, name, initial_supply, contract_address_salt); token_address } - // Create keys for an user + // Creat coin and launch fn create_and_launch_token( ref self: ContractState, symbol: felt252, @@ -250,55 +255,78 @@ mod LaunchpadMarketplace { contract_address_salt: felt252 ) -> ContractAddress { let contract_address = get_contract_address(); + let caller = get_caller_address(); let token_address = self - ._create_token(contract_address, symbol, name, initial_supply, contract_address_salt); - self._launch_token(token_address); + ._create_token( + contract_address, caller, symbol, name, initial_supply, contract_address_salt + ); + self._launch_token(token_address, contract_address); token_address } - // Create keys for an user - fn launch_token( - ref self: ContractState, coin_address: ContractAddress - ) { // Todo function with custom init token - self._launch_token(coin_address); + // Launch coin to pool bonding curve + fn launch_token(ref self: ContractState, coin_address: ContractAddress) { + let caller = get_caller_address(); + self._launch_token(coin_address, caller); } - + // Buy a coin to a bonding curve fn buy_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { let old_launch = self.launched_coins.read(coin_address); assert!(!old_launch.owner.is_zero(), "coin not found"); - + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + let mut pool_coin = old_launch.clone(); + let total_supply_memecoin = memecoin.total_supply(); + assert!(amount < total_supply_memecoin, "too much"); // TODO erc20 token transfer 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 Launch pool with new values - let memecoin = IERC20Dispatcher { contract_address: coin_address }; - let mut pool_coin = old_launch.clone(); - // 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, - // initial_key_price: token_quote.initial_key_price, - // bonding_curve_type: old_launch.bonding_curve_type, - // total_supply: old_launch.total_supply, - // available_supply: old_launch.available_supply, - // price: old_launch.price, - // liquidity_raised: old_launch.liquidity_raised, - // token_holded:old_launch.token_holded, - // is_liquidity_launch:old_launch.is_liquidity_launch, - - // }; let total_price = self.get_price_of_supply_key(coin_address, amount, false); - println!("total price cal {:?}", total_price); + + let old_price = pool_coin.price.clone(); + // println!("total price cal {:?}", total_price); let amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; + // println!("amount_protocol_fee cal {:?}", amount_protocol_fee); + // let amount_creator_fee = total_price * creator_fee_percent / BPS; let remain_liquidity = total_price - amount_protocol_fee; + // println!("remain_liquidity cal {:?}", remain_liquidity); + + println!("amount_protocol_fee {:?}", amount_protocol_fee); + + erc20 + .transfer_from( + get_caller_address(), self.protocol_fee_destination.read(), amount_protocol_fee + ); + // Pay with quote token + println!("remain_liquidity {:?}", remain_liquidity); + erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); + // Sent coin + println!("amount transfer to buyer {:?}", amount); + + let balance_contract = memecoin.balance_of(get_contract_address()); + println!("amount balance_contract {:?}", balance_contract); + + + let allowance = memecoin.allowance(pool_coin.owner.clone(), get_contract_address()); + println!("amount allowance {:?}", allowance); + + // TODO Fixed + if balance_contract < amount { + memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + } else { + println!("transfer direct amount {:?}", amount); + memecoin.transfer(get_caller_address(), amount); + // memecoin.transfer_from(get_contract_address(),get_caller_address(), amount); + } + + // Update share and key stats let mut old_share = self.shares_by_users.read((get_caller_address(), coin_address)); + println!("old_share {:?}", old_share.owner); let mut share_user = old_share.clone(); if old_share.owner.is_zero() { @@ -319,21 +347,36 @@ mod LaunchpadMarketplace { share_user.amount_owned += amount; share_user.amount_buy += amount; } - pool_coin.price = total_price; + // pool_coin.price = total_price; + pool_coin.price = total_price / amount; + pool_coin.liquidity_raised = pool_coin.liquidity_raised + total_price; pool_coin.total_supply += amount; + pool_coin.token_holded += amount; + + // Update state self.shares_by_users.write((get_caller_address(), coin_address), share_user.clone()); self.launched_coins.write(coin_address, pool_coin.clone()); - 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 - ); - // Pay with quote token - erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); - // Sent coin - memecoin.transfer(get_caller_address(), amount); + // Check if liquidity threshold raise + let threshold = self.threshold_liquidity.read(); + let threshold_mc = self.threshold_market_cap.read(); + println!("threshold {:?}", threshold); + println!("pool_coin.liquidity_raised {:?}", pool_coin.liquidity_raised); + + let mc = (pool_coin.price * total_supply_memecoin); + // TODO add liquidity launch + // TOTAL_SUPPLY / 5 + // 20% go the liquidity + // 80% bought by others + if pool_coin.liquidity_raised >= threshold { + println!("mc threshold reached"); + self._add_liquidity(coin_address); + } + + if mc >= threshold_mc { + println!("mc threshold reached"); + self._add_liquidity(coin_address); + } self .emit( @@ -343,7 +386,9 @@ mod LaunchpadMarketplace { amount: amount, price: total_price, protocol_fee: amount_protocol_fee, - creator_fee: 0 + creator_fee: 0, + timestamp: get_block_timestamp(), + last_price: old_price, } ); } @@ -351,7 +396,6 @@ mod LaunchpadMarketplace { fn sell_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { let old_pool = self.launched_coins.read(coin_address); assert(!old_pool.owner.is_zero(), 'coin not found'); - assert!(amount <= MAX_STEPS_LOOP, "max step loop"); // let caller = get_caller_address(); let mut old_share = self.shares_by_users.read((get_caller_address(), coin_address)); @@ -372,6 +416,7 @@ mod LaunchpadMarketplace { let creator_fee_percent = self.creator_fee_percent.read(); assert!(total_supply >= amount, "share > supply"); + let old_price = old_pool.price.clone(); // Update keys with new values let mut pool_update = TokenLaunch { @@ -388,9 +433,9 @@ mod LaunchpadMarketplace { token_holded: old_pool.token_holded, is_liquidity_launch: old_pool.is_liquidity_launch, }; - let mut total_price = self.get_price_of_supply_key(coin_address, amount, true); - total_price -= pool_update.initial_key_price.clone(); + let mut total_price = self.get_price_of_supply_key(coin_address, amount, true); + // 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; @@ -438,7 +483,9 @@ mod LaunchpadMarketplace { amount: amount, price: total_price, protocol_fee: amount_protocol_fee, - creator_fee: amount_creator_fee + creator_fee: amount_creator_fee, + timestamp: get_block_timestamp(), + last_price: old_price, } ); } @@ -450,54 +497,7 @@ mod LaunchpadMarketplace { fn get_price_of_supply_key( self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256 { - assert!(amount <= MAX_STEPS_LOOP, "max step loop"); - 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 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 - + (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 - }, - _ => { - let start_price = initial_key_price - + (step_increase_linear * actual_supply); - let end_price = initial_key_price - + (step_increase_linear * final_supply); - let total_price = amount * (start_price + end_price) / 2; - total_price - }, - } - }, - Option::None => { - let start_price = initial_key_price + (step_increase_linear * actual_supply); - let end_price = initial_key_price + (step_increase_linear * final_supply); - let total_price = amount * (start_price + end_price) / 2; - total_price - } - } + self._get_price_of_supply_key(coin_address, amount, is_decreased) } fn get_key_of_user(self: @ContractState, key_user: ContractAddress,) -> TokenLaunch { @@ -529,14 +529,10 @@ mod LaunchpadMarketplace { // // Could be a group of functions about a same topic #[generate_trait] impl InternalFunctions of InternalFunctionsTrait { - // Function to calculate the price for the next token to be minted - fn _get_linear_price(initial_price: u256, slope: u256, supply: u256) -> u256 { - return initial_price + (slope * supply); - } - fn _create_token( ref self: ContractState, recipient: ContractAddress, + owner: ContractAddress, symbol: felt252, name: felt252, initial_supply: u256, @@ -556,7 +552,7 @@ mod LaunchpadMarketplace { let token = Token { token_address: token_address, - owner: recipient, + owner: owner, name, symbol, total_supply: initial_supply, @@ -566,7 +562,7 @@ mod LaunchpadMarketplace { }; self.token_created.write(token_address, token); - + self .emit( CreateToken { @@ -580,18 +576,28 @@ mod LaunchpadMarketplace { } - fn _launch_token(ref self: ContractState, coin_address: ContractAddress) { - let caller = get_caller_address(); + fn _launch_token( + ref self: ContractState, coin_address: ContractAddress, caller: 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 erc20 = IERC20Dispatcher { contract_address: quote_token_address }; let memecoin = IERC20Dispatcher { contract_address: coin_address }; let total_supply = memecoin.total_supply(); + + let threshold = self.threshold_liquidity.read(); + + // TODO calculate initial key price based on + // MC + // Threshold liquidity + // total supply + + let initial_key_price = threshold / total_supply; // // @TODO Deploy an ERC404 // // Option for liquidity providing and Trading let launch_token_pump = TokenLaunch { @@ -604,7 +610,8 @@ mod LaunchpadMarketplace { // bonding_curve_type: BondingType, created_at: get_block_timestamp(), token_quote: token_to_use.clone(), - initial_key_price: token_to_use.initial_key_price, + initial_key_price: initial_key_price.clone(), + // initial_key_price: token_to_use.initial_key_price, price: 0, liquidity_raised: 0, token_holded: 0, @@ -616,8 +623,24 @@ mod LaunchpadMarketplace { let amount_needed = total_supply.clone(); println!("amount_needed {:?}", amount_needed); - let allowance = memecoin.allowance(get_caller_address(), get_contract_address()); - println!("test allowance {:?}", allowance); + let allowance = memecoin.allowance(caller, get_contract_address()); + println!("test allowance contract {:?}", allowance); + + let balance_contract = memecoin.balance_of(get_contract_address()); + println!("amount balance_contract {:?}", balance_contract); + + println!("amount caller {:?}", caller); + + + if balance_contract < total_supply { + memecoin.transfer_from(caller, get_contract_address(), total_supply - balance_contract); + } + // else { + // println!("amount balance_contract {:?}", balance_contract); + + // memecoin.transfer(get_caller_address(), total_supply); + // } + // memecoin.transfer_from(get_caller_address(), get_contract_address(), amount_needed); self.launched_coins.write(coin_address, launch_token_pump.clone()); @@ -632,6 +655,63 @@ mod LaunchpadMarketplace { ); } + fn _add_liquidity(ref self: ContractState, coin_address: ContractAddress) {} + + // Function to calculate the price for the next token to be minted + fn _get_linear_price(initial_price: u256, slope: u256, supply: u256) -> u256 { + return initial_price + (slope * supply); + } + + fn _get_price_of_supply_key( + self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool + ) -> u256 { + 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 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 + + (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 + }, + _ => { + let start_price = initial_key_price + + (step_increase_linear * actual_supply); + let end_price = initial_key_price + + (step_increase_linear * final_supply); + let total_price = amount * (start_price + end_price) / 2; + total_price + }, + } + }, + Option::None => { + let start_price = initial_key_price + (step_increase_linear * actual_supply); + let end_price = initial_key_price + (step_increase_linear * final_supply); + let total_price = amount * (start_price + end_price) / 2; + total_price + } + } + } } } @@ -666,6 +746,7 @@ mod tests { const INITIAL_KEY_PRICE: u256 = 1; const STEP_LINEAR_INCREASE: u256 = 1; const THRESHOLD_LIQUIDITY: u256 = 10; + const THRESHOLD_MARKET_CAP: u256 = 20_000; fn request_fixture() -> (ContractAddress, IERC20Dispatcher, ILaunchpadMarketplaceDispatcher) { // println!("request_fixture"); @@ -687,7 +768,8 @@ mod tests { INITIAL_KEY_PRICE, STEP_LINEAR_INCREASE, erc20_class.class_hash, - THRESHOLD_LIQUIDITY + THRESHOLD_LIQUIDITY, + THRESHOLD_MARKET_CAP ); (sender_address, erc20, keys) } @@ -707,7 +789,8 @@ mod tests { initial_key_price: u256, step_increase_linear: u256, coin_class_hash: ClassHash, - threshold_liquidity: u256 + threshold_liquidity: u256, + threshold_marketcap: u256, ) -> ILaunchpadMarketplaceDispatcher { // println!("deploy marketplace"); let mut calldata = array![admin.into()]; @@ -716,6 +799,7 @@ mod tests { calldata.append_serde(step_increase_linear); calldata.append_serde(coin_class_hash); calldata.append_serde(threshold_liquidity); + calldata.append_serde(threshold_marketcap); let (contract_address, _) = class.deploy(@calldata).unwrap(); ILaunchpadMarketplaceDispatcher { contract_address } } @@ -744,10 +828,10 @@ mod tests { 'salty'.try_into().unwrap() } - // Constants fn OWNER() -> ContractAddress { - 'owner'.try_into().unwrap() + // 'owner'.try_into().unwrap() + 123.try_into().unwrap() } fn RECIPIENT() -> ContractAddress { @@ -801,7 +885,6 @@ mod tests { 21_000_000 * pow_256(10, 18) } - #[test] fn launchpad_end_to_end() { println!("launchpad_enter_end_to_end"); @@ -829,15 +912,15 @@ mod tests { ); println!("test token_address {:?}", token_address); - - let memecoin = IERC20Dispatcher {contract_address:token_address}; + let memecoin = IERC20Dispatcher { contract_address: token_address }; start_cheat_caller_address(memecoin.contract_address, sender_address); - + + let balance_contract = memecoin.balance_of(launchpad.contract_address); + println!("test balance_contract {:?}", balance_contract); + let total_supply = memecoin.total_supply(); - println!(" memecoin total_supply {:?}", total_supply); memecoin.approve(launchpad.contract_address, total_supply); - let allowance = memecoin.allowance(sender_address, launchpad.contract_address); println!("test allowance meme coin{}", allowance); @@ -849,7 +932,7 @@ mod tests { // println!("all_keys {:?}", all_keys); let amount_to_paid = launchpad - .get_price_of_supply_key(token_address, amount_key_buy + 1, false, // 1, + .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, // BondingType::Basic, default_token.clone() ); println!("test amount_to_paid {:?}", amount_to_paid); @@ -866,16 +949,13 @@ mod tests { println!("buy coin",); launchpad.buy_coin(token_address, amount_key_buy); - - - } #[test] fn launchpad_integration() { println!("launchpad_integration"); - + let (sender_address, erc20, launchpad) = request_fixture(); let amount_key_buy = 1_u256; cheat_caller_address_global(sender_address); @@ -890,6 +970,7 @@ mod tests { let token_address = launchpad .create_token( + recipient: OWNER(), // owner: OWNER(), symbol: SYMBOL(), name: NAME(), @@ -898,15 +979,16 @@ mod tests { ); println!("test token_address {:?}", token_address); - - let memecoin = IERC20Dispatcher {contract_address:token_address}; + let memecoin = IERC20Dispatcher { contract_address: token_address }; start_cheat_caller_address(memecoin.contract_address, sender_address); - + + let balance_contract = memecoin.balance_of(launchpad.contract_address); + println!("test balance_contract {:?}", balance_contract); + let total_supply = memecoin.total_supply(); println!(" memecoin total_supply {:?}", total_supply); memecoin.approve(launchpad.contract_address, total_supply); - let allowance = memecoin.allowance(sender_address, launchpad.contract_address); println!("test allowance meme coin{}", allowance); @@ -918,7 +1000,7 @@ mod tests { println!("amount_to_paid",); // println!("all_keys {:?}", all_keys); let amount_to_paid = launchpad - .get_price_of_supply_key(token_address, amount_key_buy + 1, false, // 1, + .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, // BondingType::Basic, default_token.clone() ); println!("test amount_to_paid {:?}", amount_to_paid); @@ -935,5 +1017,25 @@ mod tests { println!("buy coin",); launchpad.buy_coin(token_address, amount_key_buy); + + println!("buy amount_to_paid",); + let amount_key_buy = 100_u256; + println!("buy 100 coin",); + // println!("all_keys {:?}", all_keys); + let amount_to_paid = launchpad + .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, + // BondingType::Basic, default_token.clone() + ); + println!("test amount_to_paid {:?}", amount_to_paid); + stop_cheat_caller_address(launchpad.contract_address); + + 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 for 100 coin {}", allowance); + + launchpad.buy_coin(token_address, amount_key_buy); } } diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index d2b2458f..ef80a4b6 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -9,8 +9,10 @@ pub mod types { pub mod keys_types; pub mod launchpad_types; } +// #[cfg(test)] // pub mod tests { // pub mod keys; +// pub mod launchpad; // } diff --git a/onchain/src/tests/keys.cairo b/onchain/src/tests/keys.cairo new file mode 100644 index 00000000..2046b3a3 --- /dev/null +++ b/onchain/src/tests/keys.cairo @@ -0,0 +1,2 @@ +#[cfg(test)] +pub mod tests {} diff --git a/onchain/src/types/launchpad_types.cairo b/onchain/src/types/launchpad_types.cairo index 1c7d49c9..b86ded1c 100644 --- a/onchain/src/types/launchpad_types.cairo +++ b/onchain/src/types/launchpad_types.cairo @@ -53,7 +53,7 @@ pub struct TokenLaunch { pub token_quote: TokenQuoteBuyKeys, pub liquidity_raised: u256, pub token_holded: u256, - pub is_liquidity_launch: bool + pub is_liquidity_launch: bool, } #[derive(Drop, Serde, Copy, starknet::Store)] @@ -109,7 +109,9 @@ pub struct BuyToken { pub amount: u256, pub price: u256, pub protocol_fee: u256, - pub creator_fee: u256 + pub creator_fee: u256, + pub timestamp: u64, + pub last_price: u256, } #[derive(Drop, starknet::Event)] @@ -121,7 +123,9 @@ pub struct SellToken { pub amount: u256, pub price: u256, pub protocol_fee: u256, - pub creator_fee: u256 + pub creator_fee: u256, + pub timestamp: u64, + pub last_price: u256, } #[derive(Drop, starknet::Event)] From a6a578f624fbf62e6a626095533d48d24dac7df3 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Fri, 16 Aug 2024 00:22:59 +0200 Subject: [PATCH 4/8] deploy namespace + try fix launchpad + scripts --- apps/mobile/src/constants/contracts.ts | 10 + onchain/.snfoundry_cache/.prev_tests_failed | 2 +- onchain/src/keys/keys.cairo | 45 +- onchain/src/launchpad/launchpad.cairo | 479 +++++++++++++------- onchain/src/lib.cairo | 2 +- onchain/src/tests/launchpad.cairo | 1 + onchain/src/types/launchpad_types.cairo | 1 + packages/afk_nostr_sdk/tsconfig copy 4.json | 25 - scripts/.env.exemple | 1 + scripts/constants/index.ts | 1 + scripts/deploy/launchpad.ts | 52 +++ scripts/package.json | 1 + scripts/utils/launchpad.ts | 131 ++++++ 13 files changed, 543 insertions(+), 208 deletions(-) create mode 100644 onchain/src/tests/launchpad.cairo delete mode 100644 packages/afk_nostr_sdk/tsconfig copy 4.json create mode 100644 scripts/deploy/launchpad.ts create mode 100644 scripts/utils/launchpad.ts diff --git a/apps/mobile/src/constants/contracts.ts b/apps/mobile/src/constants/contracts.ts index c32bafe3..1e75692e 100644 --- a/apps/mobile/src/constants/contracts.ts +++ b/apps/mobile/src/constants/contracts.ts @@ -33,3 +33,13 @@ export const UNRUGGABLE_FACTORY_ADDRESS = { // [constants.StarknetChainId.SN_SEPOLIA]: "0x5e89dc74f1a40d7814966b028a9b1853d39006a954b27828a9de7e333ec8119", }; + +export const NAMESPACE_ADDRESS = { + [constants.StarknetChainId.SN_SEPOLIA]:"0x6e8ecfa6872bd27a7517077069b401a494687e66e2a98d37311eee1d96f1b57", + [constants.StarknetChainId.SN_MAIN]:"" +} + +export const LAUNCHPAD_ADDRESS = { + [constants.StarknetChainId.SN_SEPOLIA]:"0x517110eac4a6e8a50a0966b386a3b19f1facf96a8adb393594a52edf6e9fcc7", + [constants.StarknetChainId.SN_MAIN]:"", +} \ No newline at end of file diff --git a/onchain/.snfoundry_cache/.prev_tests_failed b/onchain/.snfoundry_cache/.prev_tests_failed index 538233de..022ee682 100644 --- a/onchain/.snfoundry_cache/.prev_tests_failed +++ b/onchain/.snfoundry_cache/.prev_tests_failed @@ -1 +1 @@ -afk::launchpad::launchpad::tests::launchpad_integration +afk::launchpad::launchpad::tests::launchpad_end_to_end diff --git a/onchain/src/keys/keys.cairo b/onchain/src/keys/keys.cairo index 1ade02e2..21bc3a76 100644 --- a/onchain/src/keys/keys.cairo +++ b/onchain/src/keys/keys.cairo @@ -578,9 +578,8 @@ mod KeysMarketplace { 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(); @@ -594,19 +593,37 @@ mod KeysMarketplace { 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 - + (step_increase_linear * final_supply); - let total_price = amount * (start_price + end_price) / 2; - // println!("total_price {}", total_price.clone()); - total_price - }, - // BondingType::Scoring => { 0 }, - // BondingType::Exponential => { 0 }, - // BondingType::Limited => { 0 }, + if !is_decreased { + let start_price = initial_key_price + + (step_increase_linear * actual_supply); + println!("start_price {:?}", start_price); + let end_price = initial_key_price + + (step_increase_linear * final_supply); + println!("end_price{:?}", end_price); + + // let total_price = amount * (start_price + end_price) / 2; + let total_price = (final_supply - actual_supply) + * (start_price + end_price) + / 2; + total_price + } else { + let start_price = initial_key_price + + (step_increase_linear * final_supply); + println!("start_price {:?}", start_price); + let end_price = initial_key_price + + (step_increase_linear * actual_supply); + println!("end_price{:?}", end_price); + + // let total_price = amount * (start_price + end_price) / 2; + let total_price = (actual_supply - final_supply) + * (start_price + end_price) + / 2; + + // println!("total_price {}", total_price.clone()); + total_price + } + }, _ => { let start_price = initial_key_price + (step_increase_linear * actual_supply); diff --git a/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index a9e03860..cb418cc3 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -63,8 +63,9 @@ mod LaunchpadMarketplace { }; const MAX_SUPPLY: u256 = 100_000_000; - const INITIAL_SUPPLY: u256 = MAX_SUPPLY / 4; + const INITIAL_SUPPLY: u256 = MAX_SUPPLY / 5; const MAX_STEPS_LOOP: u256 = 100; + const LIQUIDITY_RATIO: u256 = 5; const PAY_TO_LAUNCH: u256 = 1; const MIN_FEE_PROTOCOL: u256 = 10; //0.1% @@ -241,7 +242,9 @@ mod LaunchpadMarketplace { ) -> ContractAddress { let caller = get_caller_address(); let token_address = self - ._create_token(recipient, caller, symbol, name, initial_supply, contract_address_salt); + ._create_token( + recipient, caller, symbol, name, initial_supply, contract_address_salt + ); token_address } @@ -285,48 +288,86 @@ mod LaunchpadMarketplace { let protocol_fee_percent = self.protocol_fee_percent.read(); // Update Launch pool with new values - let total_price = self.get_price_of_supply_key(coin_address, amount, false); + let mut total_price = self.get_price_of_supply_key(coin_address, amount, false); + // println!("total_price {:?}", total_price); let old_price = pool_coin.price.clone(); // println!("total price cal {:?}", total_price); - let amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; + let mut amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; // println!("amount_protocol_fee cal {:?}", amount_protocol_fee); // let amount_creator_fee = total_price * creator_fee_percent / BPS; - let remain_liquidity = total_price - amount_protocol_fee; + let mut remain_liquidity = total_price - amount_protocol_fee; // println!("remain_liquidity cal {:?}", remain_liquidity); - println!("amount_protocol_fee {:?}", amount_protocol_fee); - - erc20 - .transfer_from( - get_caller_address(), self.protocol_fee_destination.read(), amount_protocol_fee - ); // Pay with quote token - println!("remain_liquidity {:?}", remain_liquidity); - erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); + // println!("amount_protocol_fee {:?}", amount_protocol_fee); + + let threshold_liquidity = self.threshold_liquidity.read(); + + + // TOdo fix issue price + if total_price + old_launch.liquidity_raised.clone() > threshold_liquidity { + // println!( + // "total_price + old_launch.liquidity_raised.clone() > threshold_liquidity {:?}", + // total_price + old_launch.liquidity_raised.clone() > threshold_liquidity + // ); + + total_price = threshold_liquidity - old_launch.liquidity_raised.clone(); + // println!("total_price {:?}", total_price); + + amount_protocol_fee = total_price * protocol_fee_percent / BPS; + remain_liquidity = total_price - amount_protocol_fee; + + erc20 + .transfer_from( + get_caller_address(), + self.protocol_fee_destination.read(), + amount_protocol_fee + ); + // println!("remain_liquidity {:?}", remain_liquidity); + erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); + } else { + erc20 + .transfer_from( + get_caller_address(), + self.protocol_fee_destination.read(), + amount_protocol_fee + ); + // println!("remain_liquidity {:?}", remain_liquidity); + erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); + } + // Sent coin - println!("amount transfer to buyer {:?}", amount); + // println!("amount transfer to buyer {:?}", amount); let balance_contract = memecoin.balance_of(get_contract_address()); - println!("amount balance_contract {:?}", balance_contract); - + // println!("buy amount balance_contract {:?}", balance_contract); let allowance = memecoin.allowance(pool_coin.owner.clone(), get_contract_address()); - println!("amount allowance {:?}", allowance); + // println!("amount allowance {:?}", allowance); // TODO Fixed + + if allowance >= amount && balance_contract < amount { + // println!("allowance ok {:?}", allowance); + memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + } + if balance_contract < amount { memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); - } else { - println!("transfer direct amount {:?}", amount); + } else if balance_contract >= amount { + let balance_contract = memecoin.balance_of(get_contract_address()); + // println!("buy amount balance_contract {:?}", balance_contract); + // TODO FIX + // println!("transfer direct amount {:?}", amount); memecoin.transfer(get_caller_address(), amount); - // memecoin.transfer_from(get_contract_address(),get_caller_address(), amount); + // memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); } // Update share and key stats let mut old_share = self.shares_by_users.read((get_caller_address(), coin_address)); - println!("old_share {:?}", old_share.owner); + // println!("old_share {:?}", old_share.owner); let mut share_user = old_share.clone(); if old_share.owner.is_zero() { @@ -348,9 +389,9 @@ mod LaunchpadMarketplace { share_user.amount_buy += amount; } // pool_coin.price = total_price; - pool_coin.price = total_price / amount; + // pool_coin.price = total_price / amount; pool_coin.liquidity_raised = pool_coin.liquidity_raised + total_price; - pool_coin.total_supply += amount; + // pool_coin.total_supply += amount; pool_coin.token_holded += amount; // Update state @@ -360,8 +401,8 @@ mod LaunchpadMarketplace { // Check if liquidity threshold raise let threshold = self.threshold_liquidity.read(); let threshold_mc = self.threshold_market_cap.read(); - println!("threshold {:?}", threshold); - println!("pool_coin.liquidity_raised {:?}", pool_coin.liquidity_raised); + // println!("threshold {:?}", threshold); + // println!("pool_coin.liquidity_raised {:?}", pool_coin.liquidity_raised); let mc = (pool_coin.price * total_supply_memecoin); // TODO add liquidity launch @@ -369,12 +410,12 @@ mod LaunchpadMarketplace { // 20% go the liquidity // 80% bought by others if pool_coin.liquidity_raised >= threshold { - println!("mc threshold reached"); + // println!("mc threshold reached"); self._add_liquidity(coin_address); } if mc >= threshold_mc { - println!("mc threshold reached"); + // println!("mc threshold reached"); self._add_liquidity(coin_address); } @@ -432,6 +473,7 @@ mod LaunchpadMarketplace { liquidity_raised: old_pool.liquidity_raised, token_holded: old_pool.token_holded, is_liquidity_launch: old_pool.is_liquidity_launch, + slope:old_pool.slope, }; let mut total_price = self.get_price_of_supply_key(coin_address, amount, true); @@ -458,7 +500,7 @@ mod LaunchpadMarketplace { share_user.amount_owned -= amount; share_user.amount_sell += amount; } - pool_update.price = total_price; + // pool_update.price = total_price; // key.total_supply -= amount; pool_update.total_supply = pool_update.total_supply - amount; pool_update.liquidity_raised = pool_update.liquidity_raised + remain_liquidity; @@ -548,7 +590,7 @@ mod LaunchpadMarketplace { ) .unwrap(); // .unwrap_syscall(); - println!("token address {:?}", token_address); + // println!("token address {:?}", token_address); let token = Token { token_address: token_address, @@ -597,7 +639,15 @@ mod LaunchpadMarketplace { // Threshold liquidity // total supply + // let (slope, ini_price) = self._calculate_pricing(total_supply/LIQUIDITY_RATIO); + let (slope, ini_price) = self._calculate_pricing(total_supply-(total_supply/LIQUIDITY_RATIO)); + // println!("slope key price {:?}",slope); + // println!("ini_price key price {:?}",ini_price); + + // let initial_key_price = ini_price; let initial_key_price = threshold / total_supply; + + // println!("initial key price {:?}",initial_key_price); // // @TODO Deploy an ERC404 // // Option for liquidity providing and Trading let launch_token_pump = TokenLaunch { @@ -616,30 +666,35 @@ mod LaunchpadMarketplace { liquidity_raised: 0, token_holded: 0, is_liquidity_launch: false, + slope:slope // token_holded:1 }; // Send supply need to launch your coin let amount_needed = total_supply.clone(); - println!("amount_needed {:?}", amount_needed); + // println!("amount_needed {:?}", amount_needed); let allowance = memecoin.allowance(caller, get_contract_address()); - println!("test allowance contract {:?}", allowance); + // println!("test allowance contract {:?}", allowance); let balance_contract = memecoin.balance_of(get_contract_address()); - println!("amount balance_contract {:?}", balance_contract); + // println!("amount balance_contract {:?}", balance_contract); - println!("amount caller {:?}", caller); + // println!("caller {:?}", caller); + // Check if allowance or balance is ok if balance_contract < total_supply { - memecoin.transfer_from(caller, get_contract_address(), total_supply - balance_contract); + if allowance >= amount_needed { + // println!("allowance > amount_needed{:?}", allowance > amount_needed); + memecoin + .transfer_from( + caller, get_contract_address(), total_supply - balance_contract + ); + } else { + panic!("no supply provided") + } } - // else { - // println!("amount balance_contract {:?}", balance_contract); - - // memecoin.transfer(get_caller_address(), total_supply); - // } // memecoin.transfer_from(get_caller_address(), get_contract_address(), amount_needed); self.launched_coins.write(coin_address, launch_token_pump.clone()); @@ -655,6 +710,7 @@ mod LaunchpadMarketplace { ); } + // TODO add liquidity to Ekubo, Jediswap and others exhanges enabled fn _add_liquidity(ref self: ContractState, coin_address: ContractAddress) {} // Function to calculate the price for the next token to be minted @@ -662,6 +718,18 @@ mod LaunchpadMarketplace { return initial_price + (slope * supply); } + + fn _calculate_pricing(ref self: ContractState, liquidity_available:u256) -> (u256, u256) { + + let threshold_liquidity = self.threshold_liquidity.read(); + let slope= (2 *threshold_liquidity ) / (liquidity_available * (liquidity_available-1)); + // println!("slope {:?}", slope); + + let initial_price= (2* threshold_liquidity / liquidity_available) - slope * (liquidity_available -1) / 2; + // println!("initial_price {:?}", initial_price); + (slope, initial_price) + } + fn _get_price_of_supply_key( self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256 { @@ -671,28 +739,58 @@ mod LaunchpadMarketplace { if is_decreased { final_supply = total_supply - amount; - } else { - final_supply = total_supply + amount; } + let mut actual_supply = total_supply; - let mut price = pool.price.clone(); let mut initial_key_price = pool.initial_key_price.clone(); - let step_increase_linear = pool.token_quote.step_increase_linear.clone(); + let step_increase_linear =pool.slope.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 - + (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 + if !is_decreased { + // println!("initial_key_price {:?}", initial_key_price); + // println!("step_increase_linear {:?}", step_increase_linear); + // println!("final_supply {:?}", final_supply); + + + let start_price = initial_key_price + + (step_increase_linear * actual_supply); + // println!("start_price {:?}", start_price); + + let end_price = initial_key_price + + (step_increase_linear * final_supply); + // let end_price = initial_key_price + // + (step_increase_linear * final_supply -1); + // println!("end_price{:?}", end_price); + + // let total_price = amount * (start_price + end_price) / 2; + let total_price = (final_supply - actual_supply) + * (start_price + end_price) + / 2; + total_price + } else { + // println!("initial_key_price {:?}", initial_key_price); + // println!("step_increase_linear {:?}", step_increase_linear); + // println!("final_supply {:?}", final_supply); + + let start_price = initial_key_price + + (step_increase_linear * final_supply); + // println!("start_price {:?}", start_price); + let end_price = initial_key_price + + (step_increase_linear * actual_supply); + // println!("end_price{:?}", end_price); + + // let total_price = amount * (start_price + end_price) / 2; + let total_price = (actual_supply - final_supply) + * (start_price + end_price) + / 2; + + // println!("total_price {}", total_price.clone()); + total_price + } }, _ => { let start_price = initial_key_price @@ -742,11 +840,80 @@ mod tests { // use afk::keys::{IKeysMarketplaceDispatcher, IKeysMarketplaceDispatcherTrait}; use super::{ILaunchpadMarketplaceDispatcher, ILaunchpadMarketplaceDispatcherTrait}; + fn DEFAULT_INITIAL_SUPPLY() -> u256 { + // 21_000_000 * pow_256(10, 18) + 100_000_000 + // * pow_256(10, 18) + } + // const INITIAL_KEY_PRICE:u256=1/100; - const INITIAL_KEY_PRICE: u256 = 1; + const INITIAL_SUPPLY_DEFAULT: u256 = 100_000_000; + const INITIAL_KEY_PRICE: u256 = 1 / 10_000; const STEP_LINEAR_INCREASE: u256 = 1; - const THRESHOLD_LIQUIDITY: u256 = 10; - const THRESHOLD_MARKET_CAP: u256 = 20_000; + // const THRESHOLD_LIQUIDITY: u256 = 10; + const THRESHOLD_LIQUIDITY: u256 = 10_000; + const THRESHOLD_MARKET_CAP: u256 = 50_000; + const RATIO_SUPPLY_LAUNCH: u256 = 5; + const LIQUIDITY_SUPPLY: u256 = INITIAL_SUPPLY_DEFAULT / RATIO_SUPPLY_LAUNCH; + const BUYABLE: u256 = INITIAL_SUPPLY_DEFAULT / RATIO_SUPPLY_LAUNCH; + + + fn SALT() -> felt252 { + 'salty'.try_into().unwrap() + } + + // Constants + fn OWNER() -> ContractAddress { + // 'owner'.try_into().unwrap() + 123.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 request_fixture() -> (ContractAddress, IERC20Dispatcher, ILaunchpadMarketplaceDispatcher) { // println!("request_fixture"); @@ -761,7 +928,7 @@ mod tests { 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( + let launchpad = deploy_launchpad( launch_class, sender_address, token_address.clone(), @@ -771,15 +938,21 @@ mod tests { THRESHOLD_LIQUIDITY, THRESHOLD_MARKET_CAP ); - (sender_address, erc20, keys) - } - - fn declare_launchpad() -> ContractClass { - declare("LaunchpadMarketplace").unwrap() - } - - fn declare_erc20() -> ContractClass { - declare("ERC20").unwrap() + // let launchpad = deploy_launchpad( + // launch_class, + // sender_address, + // token_address.clone(), + // INITIAL_KEY_PRICE * pow_256(10,18), + // // INITIAL_KEY_PRICE, + // // STEP_LINEAR_INCREASE, + // STEP_LINEAR_INCREASE * pow_256(10,18), + // erc20_class.class_hash, + // THRESHOLD_LIQUIDITY * pow_256(10,18), + // // THRESHOLD_LIQUIDITY, + // THRESHOLD_MARKET_CAP * pow_256(10,18), + // // THRESHOLD_MARKET_CAP + // ); + (sender_address, erc20, launchpad) } fn deploy_launchpad( @@ -804,6 +977,15 @@ mod tests { ILaunchpadMarketplaceDispatcher { contract_address } } + fn declare_launchpad() -> ContractClass { + declare("LaunchpadMarketplace").unwrap() + } + + fn declare_erc20() -> ContractClass { + declare("ERC20").unwrap() + } + + fn deploy_erc20( class: ContractClass, name: felt252, @@ -823,72 +1005,40 @@ mod tests { IERC20Dispatcher { contract_address } } + + fn run_buy( + launchpad: ILaunchpadMarketplaceDispatcher, + erc20: IERC20Dispatcher, + memecoin: IERC20Dispatcher, + amount_key_buy: u256, + token_address: ContractAddress, + sender_address: ContractAddress, + ) { + let amount_to_paid = launchpad + .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, + ); + println!("test amount_to_paid erc20 {:?}", amount_to_paid); - fn SALT() -> felt252 { - 'salty'.try_into().unwrap() - } - - // Constants - fn OWNER() -> ContractAddress { - // 'owner'.try_into().unwrap() - 123.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; + start_cheat_caller_address(erc20.contract_address, sender_address); - loop { - if exponent & 1 == 1 { - result = result * base; - } + erc20.approve(launchpad.contract_address, amount_to_paid); - exponent = exponent / 2; - if exponent == 0 { - break result; - } + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance erc20 {}", allowance); + stop_cheat_caller_address(erc20.contract_address); - base = base * base; - } - } + start_cheat_caller_address(launchpad.contract_address, sender_address); + println!("buy coin",); + let allowance = memecoin.allowance(sender_address, launchpad.contract_address); + println!("test allowance meme coin{}", allowance); - fn DEFAULT_INITIAL_SUPPLY() -> u256 { - 21_000_000 * pow_256(10, 18) + launchpad.buy_coin(token_address, amount_key_buy); } #[test] fn launchpad_end_to_end() { println!("launchpad_enter_end_to_end"); - let (sender_address, erc20, launchpad) = request_fixture(); let amount_key_buy = 1_u256; cheat_caller_address_global(sender_address); @@ -913,42 +1063,48 @@ mod tests { println!("test token_address {:?}", token_address); let memecoin = IERC20Dispatcher { contract_address: token_address }; - start_cheat_caller_address(memecoin.contract_address, sender_address); - - let balance_contract = memecoin.balance_of(launchpad.contract_address); - println!("test balance_contract {:?}", balance_contract); - - let total_supply = memecoin.total_supply(); - memecoin.approve(launchpad.contract_address, total_supply); - let allowance = memecoin.allowance(sender_address, launchpad.contract_address); - println!("test allowance meme coin{}", allowance); + // Final buy + let res = run_buy( + launchpad, erc20, memecoin, INITIAL_SUPPLY_DEFAULT - LIQUIDITY_SUPPLY, token_address, sender_address, + ); + + } - // Launch coin pool - // Send total supply - // Test buy coin + #[test] + fn launchpad_all_coin() { + println!("launchpad_enter_end_to_end"); + let (sender_address, erc20, launchpad) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // Call a view function of the contract + // Check default token used + let default_token = launchpad.get_default_token(); + assert(default_token.token_address == erc20.contract_address, 'no default token'); + assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); - println!("amount_to_paid",); + start_cheat_caller_address(launchpad.contract_address, sender_address); - // println!("all_keys {:?}", all_keys); - let amount_to_paid = launchpad - .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, - // BondingType::Basic, default_token.clone() + println!("create and launch token"); + let token_address = launchpad + .create_and_launch_token( + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), ); - println!("test amount_to_paid {:?}", amount_to_paid); - - start_cheat_caller_address(erc20.contract_address, sender_address); - - erc20.approve(launchpad.contract_address, amount_to_paid); + println!("test token_address {:?}", token_address); - let allowance = erc20.allowance(sender_address, launchpad.contract_address); - println!("test allowance {}", allowance); - stop_cheat_caller_address(erc20.contract_address); + let memecoin = IERC20Dispatcher { contract_address: token_address }; - start_cheat_caller_address(launchpad.contract_address, sender_address); - println!("buy coin",); + // Final buy + let res = run_buy( + launchpad, erc20, memecoin, LIQUIDITY_SUPPLY, token_address, sender_address, + ); + - launchpad.buy_coin(token_address, amount_key_buy); } @@ -991,14 +1147,18 @@ mod tests { let allowance = memecoin.allowance(sender_address, launchpad.contract_address); println!("test allowance meme coin{}", allowance); + memecoin.transfer(launchpad.contract_address, total_supply); + // Launch coin pool // Send total supply println!("launch token"); - let pool = launchpad.launch_token(token_address); + launchpad.launch_token(token_address); // Test buy coin println!("amount_to_paid",); // println!("all_keys {:?}", all_keys); + let amount_key_buy = 100_u256; + let amount_to_paid = launchpad .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, // BondingType::Basic, default_token.clone() @@ -1018,24 +1178,9 @@ mod tests { launchpad.buy_coin(token_address, amount_key_buy); - println!("buy amount_to_paid",); - let amount_key_buy = 100_u256; - println!("buy 100 coin",); - // println!("all_keys {:?}", all_keys); - let amount_to_paid = launchpad - .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, - // BondingType::Basic, default_token.clone() - ); - println!("test amount_to_paid {:?}", amount_to_paid); - stop_cheat_caller_address(launchpad.contract_address); - - 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 for 100 coin {}", allowance); + run_buy( + launchpad, erc20, memecoin, LIQUIDITY_SUPPLY, token_address, sender_address, + ); - launchpad.buy_coin(token_address, amount_key_buy); } } diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index ef80a4b6..494afb3a 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -12,7 +12,7 @@ pub mod types { // #[cfg(test)] // pub mod tests { // pub mod keys; -// pub mod launchpad; +// pub mod launchpad_tests; // } diff --git a/onchain/src/tests/launchpad.cairo b/onchain/src/tests/launchpad.cairo new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/onchain/src/tests/launchpad.cairo @@ -0,0 +1 @@ + diff --git a/onchain/src/types/launchpad_types.cairo b/onchain/src/types/launchpad_types.cairo index b86ded1c..3dc54e6a 100644 --- a/onchain/src/types/launchpad_types.cairo +++ b/onchain/src/types/launchpad_types.cairo @@ -54,6 +54,7 @@ pub struct TokenLaunch { pub liquidity_raised: u256, pub token_holded: u256, pub is_liquidity_launch: bool, + pub slope:u256 } #[derive(Drop, Serde, Copy, starknet::Store)] diff --git a/packages/afk_nostr_sdk/tsconfig copy 4.json b/packages/afk_nostr_sdk/tsconfig copy 4.json deleted file mode 100644 index 7a675f39..00000000 --- a/packages/afk_nostr_sdk/tsconfig copy 4.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES6", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "ES6", - "moduleResolution": "node", - "outDir": "./dist", - "rootDir": "./src", - "sourceMap": true, - "declaration": true, - "composite": true, - "jsx": "react", - "baseUrl": ".", - // "paths": { - // "@/*": ["src/*"] - // } - }, - "include": ["next-env.d.ts", "**/*.tsx", "src/**/*.ts", "src/**/*.tsx",], -} diff --git a/scripts/.env.exemple b/scripts/.env.exemple index d3c99421..6af50eaa 100644 --- a/scripts/.env.exemple +++ b/scripts/.env.exemple @@ -16,6 +16,7 @@ NODE_ENV=development ESCROW_CLASS_HASH=0x3f70abaaeef59cc900b597d45a2c97c08abb5260c03327cce9e45d4c7a8ad0 # Sepolia KEY_CLASS_HASH=0x788881563d2af51d1eeacc8607b2e95c60110eddab5b43223ec97cac6d7d885 +LAUNCHPAD_CLASS_HASH=0x67557d266996a9e6728aedc016edab392261771bee00034c402da9ee340ce # Env test and scripts IS_DEPLOY_CONTRACT=true REDECLARE_ACCOUNT=false # Set to true on devnet diff --git a/scripts/constants/index.ts b/scripts/constants/index.ts index b5c6fab3..d64c7a9e 100644 --- a/scripts/constants/index.ts +++ b/scripts/constants/index.ts @@ -69,6 +69,7 @@ export const TOKENS_ADDRESS = { ETH: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", STRK: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", USDC: "0x02f37c3e00e75ee4135b32bb60c37e0599af264076376a618f138d2f9929ac74", + BIG_TOKEN: "0x00148a15f9fbf4c015b927bf88608fbafb6d149abdd5ef5b3e3b296e6ac999a4", }, DEVNET: { ETH: "0x49D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7", diff --git a/scripts/deploy/launchpad.ts b/scripts/deploy/launchpad.ts new file mode 100644 index 00000000..ee0f61ea --- /dev/null +++ b/scripts/deploy/launchpad.ts @@ -0,0 +1,52 @@ +import { + provider, +} from "../utils/starknet"; +import { Account, cairo, constants } from "starknet"; +import { ESCROW_ADDRESS, TOKENS_ADDRESS, } from "../constants"; +import dotenv from "dotenv"; +import { prepareAndConnectContract } from "../utils/contract"; +import { createLaunchpad } from "../utils/launchpad"; +dotenv.config(); + +export const deployLaunchpad = async () => { + console.log("deployLaunchpad") + + let launchpad; + + let launchpad_address: string | undefined = ESCROW_ADDRESS[constants.StarknetChainId.SN_SEPOLIA] as any // change default address + const privateKey0 = process.env.DEV_PK as string; + const accountAddress0 = process.env.DEV_PUBLIC_KEY as string; + const account = new Account(provider, accountAddress0, privateKey0, "1"); + // const TOKEN_QUOTE_ADDRESS= TOKENS_ADDRESS[constants.StarknetChainId.SN_SEPOLIA].STRK; + const TOKEN_QUOTE_ADDRESS= TOKENS_ADDRESS[constants.StarknetChainId.SN_SEPOLIA].BIG_TOKEN; + const initial_key_price=cairo.uint256(1); + const step_increase_linear=cairo.uint256(1); + const threshold_liquidity=cairo.uint256(1000); + const threshold_marketcap=cairo.uint256(5000); + if (process.env.IS_DEPLOY_CONTRACT == "true") { + let launchpadContract = await createLaunchpad( + TOKEN_QUOTE_ADDRESS, + initial_key_price, + step_increase_linear, + cairo.felt("salt"), + threshold_liquidity, + threshold_marketcap, + + ); + console.log("escrow address", launchpadContract?.contract_address) + if (launchpadContract?.contract_address) { + launchpad_address = launchpadContract?.contract_address + launchpad = await prepareAndConnectContract(launchpad_address ?? launchpadContract?.contract_address, account) + } + + } else { + + } + + + return { + launchpad, launchpad_address + } +} + +deployLaunchpad() \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index c7e25e36..f8af10fc 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -9,6 +9,7 @@ "deploy:escrow": "ts-node ./deploy/escrow.ts", "deploy:key": "ts-node ./deploy/key.ts", "deploy:namespace": "ts-node ./deploy/namespace.ts", + "deploy:launchpad": "ts-node ./deploy/launchpad.ts", "dev": "ts-node index.ts", "relay": "ts-node src/index.ts", "relay:dev": "nodemon --ext *" diff --git a/scripts/utils/launchpad.ts b/scripts/utils/launchpad.ts new file mode 100644 index 00000000..d205de5f --- /dev/null +++ b/scripts/utils/launchpad.ts @@ -0,0 +1,131 @@ +import { + Account, + json, + Contract, + cairo, + uint256, + byteArray, + Uint256, +} from "starknet"; +import fs from "fs"; +import dotenv from "dotenv"; +import { provider } from "./starknet"; +import path from "path"; +import { finalizeEvent } from "nostr-tools"; + +dotenv.config(); +const PATH_LAUNCHPAD = path.resolve( + __dirname, + "../../onchain/target/dev/afk_LaunchpadMarketplace.contract_class.json" +); +const PATH_LAUNCHPAD_COMPILED = path.resolve( + __dirname, + "../../onchain/target/dev/afk_LaunchpadMarketplace.compiled_contract_class.json" +); + +/** @TODO spec need to be discuss. This function serve as an example */ +export const createLaunchpad = async ( + tokenAddress:string, + initial_key_price:Uint256, + step_increase_linear:Uint256, + coin_class_hash:string, + threshold_liquidity:Uint256, + threshold_marketcap:Uint256, +) => { + try { + // initialize existing predeployed account 0 of Devnet + const privateKey0 = process.env.DEV_PK as string; + const accountAddress0 = process.env.DEV_PUBLIC_KEY as string; + + + console.log("tokenAddress",tokenAddress) + console.log("initial_key_price",initial_key_price) + console.log("step_increase_linear",step_increase_linear) + console.log("coin_class_hash",coin_class_hash) + console.log("threshold_liquidity",threshold_liquidity) + console.log("threshold_marketcap",threshold_marketcap) + // Devnet or Sepolia account + const account0 = new Account(provider, accountAddress0, privateKey0, "1"); + let LaunchpadClassHash = process.env.LAUNCHPAD_CLASS_HASH as string; + + const compiledSierraAAaccount = json.parse( + fs.readFileSync(PATH_LAUNCHPAD).toString("ascii") + ); + const compiledAACasm = json.parse( + fs.readFileSync(PATH_LAUNCHPAD_COMPILED).toString("ascii") + ); + /** Get class hash account */ + + // const ch = hash.computeSierraContractClassHash(compiledSierraAAaccount); + // const compCH = hash.computeCompiledClassHash(compiledAACasm); + // let pubkeyUint = pubkeyToUint256(nostrPublicKey); + + //Devnet + // // fund account address before account creation + // const { data: answer } = await axios.post( + // "http://127.0.0.1:5050/mint", + // { + // address: AAcontractAddress, + // amount: 50_000_000_000_000_000_000, + // lite: true, + // }, + // { headers: { "Content-Type": "application/json" } } + // ); + // console.log("Answer mint =", answer); + + // deploy account + + // const AAaccount = new Account(provider, AAcontractAddress, AAprivateKey); + /** @description uncomment this to declare your account */ + // console.log("declare account"); + + if (process.env.REDECLARE_CONTRACT == "true") { + console.log("try declare account"); + const declareResponse = await account0.declare({ + contract: compiledSierraAAaccount, + casm: compiledAACasm, + }); + console.log("Declare deploy", declareResponse?.transaction_hash); + await provider.waitForTransaction(declareResponse?.transaction_hash); + const contractClassHash = declareResponse.class_hash; + LaunchpadClassHash = contractClassHash; + + const nonce = await account0?.getNonce(); + console.log("nonce", nonce); + } + + const { transaction_hash, contract_address } = + await account0.deployContract({ + classHash: LaunchpadClassHash, + constructorCalldata: [ + accountAddress0, + initial_key_price, + tokenAddress, + step_increase_linear, + coin_class_hash, + threshold_liquidity, + threshold_marketcap + ], + }); + + console.log("transaction_hash", transaction_hash); + console.log("contract_address", contract_address); + let tx = await account0?.waitForTransaction(transaction_hash); + + console.log("Tx deploy", tx); + await provider.waitForTransaction(transaction_hash); + console.log( + "✅ New contract Launchpad created.\n address =", + contract_address + ); + + // const contract = new Contract(compiledSierraAAaccount, contract_address, account0) + return { + contract_address, + tx, + // contract + }; + } catch (error) { + console.log("Error createLaunchpad= ", error); + } +}; From a4c5136e4390410842af9d5ecc63246ad8a98805 Mon Sep 17 00:00:00 2001 From: MSghais Date: Fri, 16 Aug 2024 02:38:10 +0200 Subject: [PATCH 5/8] ui launch + add buy amount with quote --- .../src/components/TokenLaunchCard/index.tsx | 189 ++++++++++++++++ .../src/components/TokenLaunchCard/styles.ts | 30 +++ apps/mobile/src/hooks/launchpad/useBuyCoin.ts | 92 ++++++++ .../launchpad/useBuyCoinByQuoteAmount.ts | 92 ++++++++ .../src/hooks/launchpad/useCreateToken.ts | 78 +++++++ .../src/hooks/launchpad/useDataCoins.ts | 141 ++++++++++++ .../src/hooks/launchpad/useInstantiateKeys.ts | 29 +++ .../src/hooks/launchpad/useQueryAllCoins.ts | 18 ++ .../src/hooks/launchpad/useQueryAllLaunch.ts | 22 ++ .../mobile/src/hooks/launchpad/useSellCoin.ts | 58 +++++ .../FormLaunchToken.tsx | 48 ++-- .../styles.ts | 0 .../FormLaunchTokenUnruggable.tsx | 133 +++++++++++ .../modules/LaunchTokenUnruggable/styles.ts | 20 ++ .../CreateChannel/FormCreateChannel/index.tsx | 6 +- .../src/screens/CreateChannel/index.tsx | 5 +- .../src/screens/CreateChannel/styles.ts | 1 + apps/mobile/src/screens/CreateForm/index.tsx | 4 +- apps/mobile/src/screens/Games/index.tsx | 9 + .../src/screens/KeysMarketplace/index.tsx | 3 +- .../LaunchToken/FormCreateChannel/index.tsx | 9 +- apps/mobile/src/screens/LaunchToken/index.tsx | 11 +- .../screens/Launchpad/LaunchpadComponent.tsx | 102 +++++++++ apps/mobile/src/screens/Launchpad/index.tsx | 55 +++++ apps/mobile/src/screens/Launchpad/styles.ts | 56 +++++ apps/mobile/src/types/keys.ts | 29 +++ apps/mobile/src/types/routes.ts | 7 + apps/mobile/src/types/tab.ts | 6 + onchain/src/launchpad/launchpad.cairo | 206 ++++++++++++++++++ packages/afk_nostr_sdk/src/index.ts | 27 --- 30 files changed, 1428 insertions(+), 58 deletions(-) create mode 100644 apps/mobile/src/components/TokenLaunchCard/index.tsx create mode 100644 apps/mobile/src/components/TokenLaunchCard/styles.ts create mode 100644 apps/mobile/src/hooks/launchpad/useBuyCoin.ts create mode 100644 apps/mobile/src/hooks/launchpad/useBuyCoinByQuoteAmount.ts create mode 100644 apps/mobile/src/hooks/launchpad/useCreateToken.ts create mode 100644 apps/mobile/src/hooks/launchpad/useDataCoins.ts create mode 100644 apps/mobile/src/hooks/launchpad/useInstantiateKeys.ts create mode 100644 apps/mobile/src/hooks/launchpad/useQueryAllCoins.ts create mode 100644 apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts create mode 100644 apps/mobile/src/hooks/launchpad/useSellCoin.ts rename apps/mobile/src/modules/{LaunchToken => LaunchTokenPump}/FormLaunchToken.tsx (76%) rename apps/mobile/src/modules/{LaunchToken => LaunchTokenPump}/styles.ts (100%) create mode 100644 apps/mobile/src/modules/LaunchTokenUnruggable/FormLaunchTokenUnruggable.tsx create mode 100644 apps/mobile/src/modules/LaunchTokenUnruggable/styles.ts create mode 100644 apps/mobile/src/screens/Launchpad/LaunchpadComponent.tsx create mode 100644 apps/mobile/src/screens/Launchpad/index.tsx create mode 100644 apps/mobile/src/screens/Launchpad/styles.ts diff --git a/apps/mobile/src/components/TokenLaunchCard/index.tsx b/apps/mobile/src/components/TokenLaunchCard/index.tsx new file mode 100644 index 00000000..f00da3e1 --- /dev/null +++ b/apps/mobile/src/components/TokenLaunchCard/index.tsx @@ -0,0 +1,189 @@ +import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk'; +import { useNavigation } from '@react-navigation/native'; +import { Image, ImageSourcePropType, Pressable, TextInput, View, } from 'react-native'; + +// import {useProfile} from '../../hooks'; +import { MainStackNavigationProps } from '../../types'; +import { Text } from '../Text'; +import { useProfile } from "afk_nostr_sdk" +import { KeysUser, TokenLaunchInterface } from '../../types/keys'; +import { Fraction } from '@uniswap/sdk-core'; +import { decimalsScale } from '../../utils/helpers'; +import { cairo, uint256 } from 'starknet'; +import { feltToAddress } from '../../utils/format'; +import { useSellKeys } from '../../hooks/keys/useSellKeys'; +import { useBuyKeys } from '../../hooks/keys/useBuyKeys'; +import { useAccount } from '@starknet-react/core'; +import { useState } from 'react'; +import { Button } from '../Button'; +import stylesheet from './styles'; +import { useStyles, useWaitConnection } from '../../hooks'; +import { useWalletModal } from '../../hooks/modals'; +import { Input } from '../Input'; + +export type LaunchCoinProps = { + imageProps?: ImageSourcePropType; + name?: string; + event?: NDKEvent; + profileProps?: NDKUserProfile; + launch?: TokenLaunchInterface +}; + +enum AmountType { + QUOTE_AMOUNT, + COIN_AMOUNT_TO_BUY +} +export const TokenLaunchCard: React.FC = ({ launch, imageProps, name, profileProps, event }) => { + const { data: profile } = useProfile({ publicKey: event?.pubkey }); + const account = useAccount() + + const styles = useStyles(stylesheet) + + const [amount, setAmount] = useState() + const [typeAmount, setTypeAmount] = useState(AmountType.QUOTE_AMOUNT) + + const { handleSellKeys } = useSellKeys() + const { handleBuyKeys } = useBuyKeys() + const waitConnection = useWaitConnection(); + const walletModal = useWalletModal(); + + const onConnect = async () => { + if (!account.address) { + walletModal.show(); + + const result = await waitConnection(); + if (!result) return; + } + }; + const sellKeys = async () => { + if (!amount) return; + + await onConnect() + if (!account || !account?.account) return; + + if (!launch?.owner) return; + + if (!launch?.token_quote) return; + + // handleSellKeys(account?.account, launch?.owner, Number(amount), launch?.token_quote, undefined) + handleSellKeys(account?.account, feltToAddress(BigInt(launch?.owner)), Number(amount), launch?.token_quote, undefined) + + } + + const buyCoin = async () => { + if (!amount) return; + + await onConnect() + + if (!account || !account?.account) return; + + if (!launch?.owner) return; + + if (!launch?.token_quote) return; + // handleBuyKeys(account?.account, launch?.owner, launch?.token_quote, Number(amount),) + handleBuyKeys(account?.account, feltToAddress(BigInt(launch?.owner)), Number(amount), launch?.token_quote,) + } + const navigation = useNavigation(); + // const handleNavigateToProfile = () => { + // if (!event?.id) return; + // navigation.navigate('Profile', { publicKey: event?.pubkey }); + // }; + let priceAmount; + if (launch?.price) { + priceAmount = new Fraction(String(launch.price), decimalsScale(18)).toFixed(18); + } + let created_at; + + if (launch?.created_at) { + created_at = new Fraction(String(launch.created_at), decimalsScale(18)).toFixed(18); + + } + + return ( + + + + {launch?.owner && + + Owner: {feltToAddress(BigInt(launch.owner))} + + + } + {/* + + + + + {profile?.name ?? profile?.nip05 ?? profile?.displayName ?? 'Anon AFK'} + + */} + + Supply: {Number(launch?.total_supply) / 10 ** 18} + + + Supply: {Number(launch?.total_supply)} + + + Price: {Number(launch?.price) / 10 ** 18} + + + {/* {launch?.created_at && + + Created at {Number(launch?.created_at) / 10 ** 18} + + } */} + + + + {launch?.token_quote && + + Token quote + + Quote token: {feltToAddress(BigInt(launch.token_quote?.token_address))} + + + Step increase: {Number(launch.token_quote?.step_increase_linear) / 10 ** 18} + + } + + + ); +}; diff --git a/apps/mobile/src/components/TokenLaunchCard/styles.ts b/apps/mobile/src/components/TokenLaunchCard/styles.ts new file mode 100644 index 00000000..4f4fbeeb --- /dev/null +++ b/apps/mobile/src/components/TokenLaunchCard/styles.ts @@ -0,0 +1,30 @@ +import { Spacing, ThemedStyleSheet } from '../../styles'; +export default ThemedStyleSheet((theme) => ({ + container: { + // alignItems: 'center', + backgroundColor: theme.colors.surface, + padding: Spacing.xsmall, + borderRadius: 8, + gap: Spacing.xsmall, + overflowWrap:"break-word", + width:300, + }, + imageContainer: { + // position: 'relative', + // display: 'flex', + // alignItems: 'center', + // justifyContent: 'center', + borderRadius: 15 + }, + text:{ + }, + image: { + position: 'absolute', + width: 35, + height: 35, + borderRadius: 15 + }, + name: { + paddingTop: Spacing.xxsmall, + }, +})) diff --git a/apps/mobile/src/hooks/launchpad/useBuyCoin.ts b/apps/mobile/src/hooks/launchpad/useBuyCoin.ts new file mode 100644 index 00000000..0a7e2225 --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useBuyCoin.ts @@ -0,0 +1,92 @@ +import {useAccount, useNetwork, useProvider} from '@starknet-react/core'; +import {AccountInterface, cairo, CallData, constants, RpcProvider, uint256} from 'starknet'; +import { LAUNCHPAD_ADDRESS} from '../../constants/contracts'; +import {TokenQuoteBuyKeys} from '../../types/keys'; +import {feltToAddress, formatFloatToUint256} from '../../utils/format'; +import {prepareAndConnectContract} from './useDataCoins'; + +export const useBuyCoin = () => { + const chain = useNetwork(); + const chainId = chain?.chain?.id; + const provider = new RpcProvider({nodeUrl:process.env.EXPO_PUBLIC_PROVIDER_URL}); + const handleBuyCoins = async ( + account: AccountInterface, + user_address: string, + amount: number, + tokenQuote: TokenQuoteBuyKeys, + contractAddress?: string, + ) => { + if (!account) return; + + const addressContract = contractAddress ?? LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA]; + console.log('addressContract', addressContract); + console.log('read asset'); + const asset = await prepareAndConnectContract( + provider, + feltToAddress(BigInt(tokenQuote?.token_address)), + // tokenQuote?.token_address?.toString(), + account, + ); + console.log('read launchpad_contract'); + + const launchpad_contract = await prepareAndConnectContract(provider, addressContract); + // const launchpad_contract = await prepareAndConnectContract(provider, addressContract, account); + + console.log('convert float'); + console.log('amount', amount); + let amountUint256 = formatFloatToUint256(amount); + amountUint256 = uint256.bnToUint256(BigInt('0x' + amount)); + console.log('amountuint256', amountUint256); + const buyKeysParams = { + user_address, // token address + amount: amountUint256, + // amount: cairo.uint256(amount), // amount int. Float need to be convert with bnToUint + // amount: uint256.bnToUint256(amount*10**18), // amount int. Float need to be convert with bnToUint + // amount: uint256.bnToUint256(BigInt(amount*10**18)), // amount int. Float need to be convert with bnToUint + }; + + console.log('read amountToPaid'); + + let amountToPaid; + try { + /** @TODO fix CORS issue */ + amountToPaid = await launchpad_contract.get_price_of_supply_key(user_address, amountUint256, false); + } catch (error) { + console.log('Error get amount to paid', error); + } + + console.log('amount to paid', amountToPaid); + // let txApprove = await asset.approve( + // addressContract, + // cairo.uint256(1), // change for decimals float => uint256.bnToUint256("0x"+alicePublicKey) + // ) + + const approveCall = { + contractAddress: asset?.address, + entrypoint: 'approve', + calldata: CallData.compile({ + address: addressContract, + amount: amountToPaid ? cairo.uint256(amountToPaid) : cairo.uint256(1), + }), + // calldata: [buyKeysParams.user_address, buyKeysParams.amount] + }; + + const buyCoinCall = { + contractAddress: addressContract, + entrypoint: 'buy_coin', + calldata: CallData.compile({ + user_address: buyKeysParams.user_address, + amount: buyKeysParams.amount, + }), + // calldata: [buyKeysParams.user_address, buyKeysParams.amount] + }; + + console.log('CabuyKeysCallll', buyCoinCall); + + const tx = await account?.execute([approveCall, buyCoinCall], undefined, {}); + console.log('tx hash', tx.transaction_hash); + const wait_tx = await account?.waitForTransaction(tx?.transaction_hash); + }; + + return {handleBuyCoins}; +}; diff --git a/apps/mobile/src/hooks/launchpad/useBuyCoinByQuoteAmount.ts b/apps/mobile/src/hooks/launchpad/useBuyCoinByQuoteAmount.ts new file mode 100644 index 00000000..fb980764 --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useBuyCoinByQuoteAmount.ts @@ -0,0 +1,92 @@ +import {useAccount, useNetwork, useProvider} from '@starknet-react/core'; +import {AccountInterface, cairo, CallData, constants, RpcProvider, uint256} from 'starknet'; +import { LAUNCHPAD_ADDRESS} from '../../constants/contracts'; +import {TokenQuoteBuyKeys} from '../../types/keys'; +import {feltToAddress, formatFloatToUint256} from '../../utils/format'; +import {prepareAndConnectContract} from './useDataCoins'; + +export const useBuyCoinByQuoteAmount = () => { + const chain = useNetwork(); + const chainId = chain?.chain?.id; + const provider = new RpcProvider({nodeUrl:process.env.EXPO_PUBLIC_PROVIDER_URL}); + const handleBuyCoins = async ( + account: AccountInterface, + user_address: string, + amount: number, + tokenQuote: TokenQuoteBuyKeys, + contractAddress?: string, + ) => { + if (!account) return; + + const addressContract = contractAddress ?? LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA]; + console.log('addressContract', addressContract); + console.log('read asset'); + const asset = await prepareAndConnectContract( + provider, + feltToAddress(BigInt(tokenQuote?.token_address)), + // tokenQuote?.token_address?.toString(), + account, + ); + console.log('read launchpad_contract'); + + const launchpad_contract = await prepareAndConnectContract(provider, addressContract); + // const launchpad_contract = await prepareAndConnectContract(provider, addressContract, account); + + console.log('convert float'); + console.log('amount', amount); + let amountUint256 = formatFloatToUint256(amount); + amountUint256 = uint256.bnToUint256(BigInt('0x' + amount)); + console.log('amountuint256', amountUint256); + const buyKeysParams = { + user_address, // token address + amount: amountUint256, + // amount: cairo.uint256(amount), // amount int. Float need to be convert with bnToUint + // amount: uint256.bnToUint256(amount*10**18), // amount int. Float need to be convert with bnToUint + // amount: uint256.bnToUint256(BigInt(amount*10**18)), // amount int. Float need to be convert with bnToUint + }; + + console.log('read amountToPaid'); + + let amountToPaid; + try { + /** @TODO fix CORS issue */ + amountToPaid = await launchpad_contract.get_price_of_supply_key(user_address, amountUint256, false); + } catch (error) { + console.log('Error get amount to paid', error); + } + + console.log('amount to paid', amountToPaid); + // let txApprove = await asset.approve( + // addressContract, + // cairo.uint256(1), // change for decimals float => uint256.bnToUint256("0x"+alicePublicKey) + // ) + + const approveCall = { + contractAddress: asset?.address, + entrypoint: 'approve', + calldata: CallData.compile({ + address: addressContract, + amount: amountToPaid ? cairo.uint256(amountToPaid) : cairo.uint256(1), + }), + // calldata: [buyKeysParams.user_address, buyKeysParams.amount] + }; + + const buyKeysCall = { + contractAddress: addressContract, + entrypoint: 'buy_coin_with_quote_amount', + calldata: CallData.compile({ + user_address: buyKeysParams.user_address, + quote_amount: buyKeysParams.amount, + }), + // calldata: [buyKeysParams.user_address, buyKeysParams.amount] + }; + + console.log('CabuyKeysCallll', buyKeysCall); + + const tx = await account?.execute([approveCall, buyKeysCall], undefined, {}); + console.log('tx hash', tx.transaction_hash); + const wait_tx = await account?.waitForTransaction(tx?.transaction_hash); + }; + + return {handleBuyCoins}; +}; diff --git a/apps/mobile/src/hooks/launchpad/useCreateToken.ts b/apps/mobile/src/hooks/launchpad/useCreateToken.ts new file mode 100644 index 00000000..13278c5d --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useCreateToken.ts @@ -0,0 +1,78 @@ +import { AccountInterface, CallData, Calldata, cairo, constants } from "starknet" +import { LAUNCHPAD_ADDRESS, UNRUGGABLE_FACTORY_ADDRESS } from "../../constants/contracts"; + +export type DeployTokenFormValues = { + recipient?:string; + name: string | undefined; + symbol: string | undefined; + initialSupply: number | undefined; + contract_address_salt: string | undefined +}; + +export const useCreateToken = () => { + + const deployToken = async (account: AccountInterface, data: DeployTokenFormValues) => { + const CONTRACT_ADDRESS_SALT_DEFAULT = data?.contract_address_salt ?? await account?.getChainId() == constants.StarknetChainId.SN_MAIN ? + "0x36d8be2991d685af817ef9d127ffb00fbb98a88d910195b04ec4559289a99f6" : + "0x36d8be2991d685af817ef9d127ffb00fbb98a88d910195b04ec4559289a99f6" + const deployCall = { + contractAddress: LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA], + entrypoint: 'create_token', + calldata: CallData.compile({ + owner: data?.recipient ?? account?.address, + name: data.name ?? "LFG", + symbol: data.symbol ?? "LFG", + initialSupply: cairo.uint256(data?.initialSupply ?? 100), + contract_address_salt:CONTRACT_ADDRESS_SALT_DEFAULT + }), + }; + + const tx = await account.execute(deployCall) + console.log('tx hash', tx.transaction_hash); + const wait_tx = await account?.waitForTransaction(tx?.transaction_hash); + return wait_tx + } + + const deployTokenAndLaunch = async (account: AccountInterface, data: DeployTokenFormValues) => { + const CONTRACT_ADDRESS_SALT_DEFAULT = data?.contract_address_salt ?? await account?.getChainId() == constants.StarknetChainId.SN_MAIN ? + "0x36d8be2991d685af817ef9d127ffb00fbb98a88d910195b04ec4559289a99f6" : + "0x36d8be2991d685af817ef9d127ffb00fbb98a88d910195b04ec4559289a99f6" + const deployCall = { + contractAddress: LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA], + entrypoint: 'create_and_launch_token', + calldata: CallData.compile({ + owner: account?.address, + name: data.name ?? "LFG", + symbol: data.symbol ?? "LFG", + initialSupply: cairo.uint256(data?.initialSupply ?? 100), + contract_address_salt:CONTRACT_ADDRESS_SALT_DEFAULT + }), + }; + + const tx = await account.execute(deployCall) + console.log('tx hash', tx.transaction_hash); + const wait_tx = await account?.waitForTransaction(tx?.transaction_hash); + return wait_tx + } + + const launchToken = async (account: AccountInterface, coin_address:string) => { + const deployCall = { + contractAddress: LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA], + entrypoint: 'launch_token', + calldata: CallData.compile({ + coin_address:coin_address}), + }; + + const tx = await account.execute(deployCall) + console.log('tx hash', tx.transaction_hash); + const wait_tx = await account?.waitForTransaction(tx?.transaction_hash); + return wait_tx + } + + return { + deployToken, + deployTokenAndLaunch, + launchToken + + } +} \ No newline at end of file diff --git a/apps/mobile/src/hooks/launchpad/useDataCoins.ts b/apps/mobile/src/hooks/launchpad/useDataCoins.ts new file mode 100644 index 00000000..769a4561 --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useDataCoins.ts @@ -0,0 +1,141 @@ +import { useAccount, useNetwork, useProvider } from '@starknet-react/core'; +import { AccountInterface, constants, Contract, ProviderInterface, RpcProvider } from 'starknet'; + +import { KEYS_ADDRESS, LAUNCHPAD_ADDRESS } from '../../constants/contracts'; +import { useQuery } from '@tanstack/react-query'; +import { CHAIN_ID } from '../../constants/env'; +/** @TODO determine paymaster master specs to send the TX */ +export const prepareAndConnectContract = async ( + provider: ProviderInterface, + contractAddress: string, + account?: AccountInterface, +) => { + // read abi of Test contract + console.log('contractAddress', contractAddress); + // console.log("provider",await provider.getChainId()) + + const { abi: testAbi } = await provider.getClassAt(contractAddress); + if (testAbi === undefined) { + throw new Error('no abi.'); + } + const contract = new Contract(testAbi, contractAddress, provider); + console.log('contract', contract); + + // Connect account with the contract + if (account) { + contract.connect(account); + } + return contract; +}; + +export const useDataCoins = () => { + const account = useAccount(); + const chain = useNetwork(); + const rpcProvider = useProvider(); + const chainId = chain?.chain?.id; + + // const provider = rpcProvider?.provider ?? new RpcProvider({ nodeUrl: 'http://127.0.0.1:5050' }); + // const provider = rpcProvider?.provider ?? new RpcProvider(); + const provider = new RpcProvider(); + + + const queryDataCoins = () => { + return useQuery({ + queryKey: ['get_all_coins', CHAIN_ID], + queryFn:async () => { + + const keys= await getAllCoins() + return keys + }, + placeholderData:[] + + + }) + + } + + const queryDataLaunch = () => { + return useQuery({ + queryKey: ['get_all_launched_coins', CHAIN_ID], + queryFn:async () => { + + const keys= await getAllCoins() + return keys + }, + placeholderData:[] + + + }) + + } + + + /** Indexer with Key contract event */ + const getAllCoins = async (account?: AccountInterface, contractAddress?: string) => { + console.log('getAllCoins'); + const addressContract = contractAddress ?? LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA]; + const contract = await prepareAndConnectContract(provider, addressContract, account); + // if (!account) return; + // console.log('get key all keys'); + const all_keys = await contract.get_all_launch(); + console.log('allkeys', all_keys); + return all_keys; + }; + + /** Indexer with Key contract event */ + const getAllLaunch = async (account?: AccountInterface, contractAddress?: string) => { + console.log('getAllLaunch'); + const addressContract = contractAddress ?? LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA]; + const contract = await prepareAndConnectContract(provider, addressContract, account); + // if (!account) return; + // console.log('get key all keys'); + const all_launch = await contract.get_all_launch(); + console.log('all_launch', all_launch); + return all_launch; + }; + + + const getMySharesOfUser = async ( + address_user: string, + account?: AccountInterface, + contractAddress?: string, + ) => { + try { + if (!account?.address) return; + const contract = await prepareAndConnectContract( + provider, + contractAddress ?? LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA], + account, + ); + // console.log('contract', contract); + // console.log('account connected', account?.address); + // console.log('share of address_user', address_user); + const share_user: any = await contract.get_share_key_of_user(account?.address, address_user); + // console.log('share_user', share_user); + return share_user; + } catch (e) { + console.log('Error get my shares of user', e); + } + }; + + const getCoinByAddress = async ( + address_user: string, + account?: AccountInterface, + contractAddress?: string, + ) => { + try { + // if (!account?.address) return; + const contract = await prepareAndConnectContract( + provider, + contractAddress ?? LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA], + account, + ); + const share_user: any = await contract.get_key_of_user(address_user); + return share_user; + } catch (e) { + console.log('Error get my key of user', e); + } + }; + + return { getAllCoins, getMySharesOfUser, getCoinByAddress, queryDataCoins, queryDataLaunch, getAllLaunch}; +}; diff --git a/apps/mobile/src/hooks/launchpad/useInstantiateKeys.ts b/apps/mobile/src/hooks/launchpad/useInstantiateKeys.ts new file mode 100644 index 00000000..d079c722 --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useInstantiateKeys.ts @@ -0,0 +1,29 @@ +import {useAccount, useNetwork} from '@starknet-react/core'; +import {AccountInterface, CallData, constants} from 'starknet'; + +import { LAUNCHPAD_ADDRESS} from '../../constants/contracts'; + +export const useInstantiateKeys = () => { + const chain = useNetwork(); + const chainId = chain?.chain?.id; + + const handleInstantiateKeys = async (account: AccountInterface, addressContract?: string) => { + if (!account) return; + + const contractAddress = addressContract ?? LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA]; + + const call = { + contractAddress, + entrypoint: 'instantiate_keys', + calldata: CallData.compile({}), + }; + + console.log('Call', call); + + const tx = await account?.execute([call], undefined, {}); + console.log('tx hash', tx.transaction_hash); + const wait_tx = await account?.waitForTransaction(tx?.transaction_hash); + }; + + return {handleInstantiateKeys}; +}; diff --git a/apps/mobile/src/hooks/launchpad/useQueryAllCoins.ts b/apps/mobile/src/hooks/launchpad/useQueryAllCoins.ts new file mode 100644 index 00000000..37b97891 --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useQueryAllCoins.ts @@ -0,0 +1,18 @@ +import { useNetwork } from '@starknet-react/core'; +import { useQuery } from '@tanstack/react-query'; +import { CHAIN_ID } from '../../constants/env'; +import { useDataCoins } from './useDataCoins'; + +export const useQueryAllCoins = () => { + const chain = useNetwork(); + const {getAllCoins} = useDataCoins() + return useQuery({ + queryKey: ['get_all_coins', CHAIN_ID], + queryFn:async () => { + + const coins= await getAllCoins() + return coins + }, + placeholderData:[] + }) +}; diff --git a/apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts b/apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts new file mode 100644 index 00000000..ac3c88c0 --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts @@ -0,0 +1,22 @@ +import { useAccount, useNetwork, useProvider } from '@starknet-react/core'; +import { AccountInterface, constants, Contract, ProviderInterface, RpcProvider } from 'starknet'; + +import { KEYS_ADDRESS } from '../../constants/contracts'; +import { useQuery } from '@tanstack/react-query'; +import { CHAIN_ID } from '../../constants/env'; +import { prepareAndConnectContract, useDataCoins } from './useDataCoins'; + +export const useQueryAllLaunch = () => { + const chain = useNetwork(); + const { getAllLaunch} = useDataCoins() + const chainId = chain?.chain?.id; + return useQuery({ + queryKey: ['get_all_launch', CHAIN_ID], + queryFn:async () => { + + const launchs= await getAllLaunch() + return launchs + }, + placeholderData:[] + }) +}; diff --git a/apps/mobile/src/hooks/launchpad/useSellCoin.ts b/apps/mobile/src/hooks/launchpad/useSellCoin.ts new file mode 100644 index 00000000..df50a77d --- /dev/null +++ b/apps/mobile/src/hooks/launchpad/useSellCoin.ts @@ -0,0 +1,58 @@ +import {useAccount, useNetwork, useProvider} from '@starknet-react/core'; +import {AccountInterface, CallData, constants, RpcProvider, uint256} from 'starknet'; + +import {KEYS_ADDRESS, LAUNCHPAD_ADDRESS} from '../../constants/contracts'; +import {TokenQuoteBuyKeys} from '../../types/keys'; +import {formatFloatToUint256} from '../../utils/format'; + +export const useSellCoin = () => { + const account = useAccount(); + const chain = useNetwork(); + const rpcProvider = useProvider(); + const chainId = chain?.chain?.id; + // const provider = rpcProvider?.provider ?? new RpcProvider(); + const provider = rpcProvider?.provider ?? new RpcProvider(); + + const handleSellCoins = async ( + account: AccountInterface, + user_address: string, + amount: number, + tokenQuote?: TokenQuoteBuyKeys, + contractAddress?: string, + ) => { + if (!account) return; + const addressContract = LAUNCHPAD_ADDRESS[constants.StarknetChainId.SN_SEPOLIA]; + console.log('addressContract', addressContract); + // let launchpad_contract = await prepareAndConnectContract( + // provider, + // addressContract, + // account + // ); + + let amountUint256 = formatFloatToUint256(amount); + amountUint256 = uint256.bnToUint256(BigInt('0x' + amount)); + + const sellKeysParams = { + user_address, // token address + amount: amountUint256, + // amount: cairo.uint256(amount), // amount int. Float need to be convert with bnToUint + }; + console.log('sellKeysParams', sellKeysParams); + + const call = { + contractAddress: addressContract, + entrypoint: 'sell_keys', + calldata: CallData.compile({ + user_address: sellKeysParams.user_address, + amount: sellKeysParams.amount, + }), + }; + + console.log('Call', call); + const tx = await account?.execute([call], undefined, {}); + console.log('tx hash', tx.transaction_hash); + const wait_tx = await account?.waitForTransaction(tx?.transaction_hash); + }; + + return {handleSellCoins}; +}; diff --git a/apps/mobile/src/modules/LaunchToken/FormLaunchToken.tsx b/apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx similarity index 76% rename from apps/mobile/src/modules/LaunchToken/FormLaunchToken.tsx rename to apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx index f3afa51a..bdf77fb3 100644 --- a/apps/mobile/src/modules/LaunchToken/FormLaunchToken.tsx +++ b/apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx @@ -1,20 +1,20 @@ import { useNavigation } from '@react-navigation/native'; import { useQueryClient } from '@tanstack/react-query'; import { Formik, FormikProps } from 'formik'; -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import { ScrollView, View } from 'react-native'; import { Button, SquareInput, Text } from '../../components'; import { useStyles, useWaitConnection } from '../../hooks'; -import {useProfile} from "afk_nostr_sdk" +import { useProfile } from "afk_nostr_sdk" import { useToast, useWalletModal } from '../../hooks/modals'; import stylesheet from '../../screens/CreateChannel/styles'; // import { useAuth } from '../../store/auth'; import { useAuth } from 'afk_nostr_sdk'; import { MainStackNavigationProps } from '../../types'; -import { DeployTokenFormValues, useDeployTokenUnruggable } from '../../hooks/unruggable/useDeploy'; import { useAccount } from '@starknet-react/core'; +import { useCreateToken, DeployTokenFormValues } from '../../hooks/launchpad/useCreateToken'; const UsernameInputLeft = ( @@ -22,6 +22,11 @@ const UsernameInputLeft = ( ); +enum TypeCreate { + LAUNCH, + CREATE, + CREATE_AND_LAUNCH +} type FormValues = DeployTokenFormValues; export const FormLaunchToken: React.FC = () => { const formikRef = useRef>(null); @@ -33,17 +38,20 @@ export const FormLaunchToken: React.FC = () => { const navigation = useNavigation(); const account = useAccount() const waitConnection = useWaitConnection() - const { deployTokenUnruggable } = useDeployTokenUnruggable() + const { deployToken } = useCreateToken() + + const [type, setType] = useState(TypeCreate.CREATE) if (profile.isLoading) return null; const initialFormValues: FormValues = { - name: 'My Man is AFK or !AFK', - symbol: 'MMNAFK', + name: 'My Man', + symbol: 'MY_MAN', // ticker: '', - initialSupply: 100, - contract_address_salt: "" + initialSupply: 100_000_000, + contract_address_salt: "", + recipient: account?.address }; - const onSubmitPress = () => { + const onSubmitPress = (type: TypeCreate) => { formikRef.current?.handleSubmit(); }; @@ -65,14 +73,19 @@ export const FormLaunchToken: React.FC = () => { } const data: DeployTokenFormValues = { + recipient: account?.address, name: values.name, symbol: values.symbol, initialSupply: values?.initialSupply, contract_address_salt: values.contract_address_salt }; if (!account || !account?.account) return; - deployTokenUnruggable(account?.account, data); - showToast({ type: 'success', title: 'Token launch created successfully' }); + let tx = await deployToken(account?.account, data); + + if (tx) { + showToast({ type: 'success', title: 'Token launch created successfully' }); + + } } catch (error) { showToast({ type: 'error', title: 'Failed to create token and launch' }); } @@ -83,7 +96,7 @@ export const FormLaunchToken: React.FC = () => { return ( - Launch your Unruggable Token + Launch your Token { /> + + + >Create & Launch coin diff --git a/apps/mobile/src/modules/LaunchToken/styles.ts b/apps/mobile/src/modules/LaunchTokenPump/styles.ts similarity index 100% rename from apps/mobile/src/modules/LaunchToken/styles.ts rename to apps/mobile/src/modules/LaunchTokenPump/styles.ts diff --git a/apps/mobile/src/modules/LaunchTokenUnruggable/FormLaunchTokenUnruggable.tsx b/apps/mobile/src/modules/LaunchTokenUnruggable/FormLaunchTokenUnruggable.tsx new file mode 100644 index 00000000..27f89f8c --- /dev/null +++ b/apps/mobile/src/modules/LaunchTokenUnruggable/FormLaunchTokenUnruggable.tsx @@ -0,0 +1,133 @@ +import { useNavigation } from '@react-navigation/native'; +import { useQueryClient } from '@tanstack/react-query'; +import { Formik, FormikProps } from 'formik'; +import { useRef } from 'react'; +import { ScrollView, View } from 'react-native'; + +import { Button, SquareInput, Text } from '../../components'; +import { useStyles, useWaitConnection } from '../../hooks'; +import {useProfile} from "afk_nostr_sdk" +import { useToast, useWalletModal } from '../../hooks/modals'; +import stylesheet from '../../screens/CreateChannel/styles'; +// import { useAuth } from '../../store/auth'; +import { useAuth } from 'afk_nostr_sdk'; + +import { MainStackNavigationProps } from '../../types'; +import { DeployTokenFormValues, useDeployTokenUnruggable } from '../../hooks/unruggable/useDeploy'; +import { useAccount } from '@starknet-react/core'; + +const UsernameInputLeft = ( + + @ + +); + +type FormValues = DeployTokenFormValues; +export const FormLaunchTokenUnruggable: React.FC = () => { + const formikRef = useRef>(null); + const styles = useStyles(stylesheet); + const publicKey = useAuth((state) => state.publicKey); + const profile = useProfile({ publicKey }); + const queryClient = useQueryClient(); + const { showToast } = useToast(); + const navigation = useNavigation(); + const account = useAccount() + const waitConnection = useWaitConnection() + const { deployTokenUnruggable } = useDeployTokenUnruggable() + if (profile.isLoading) return null; + const initialFormValues: FormValues = { + name: 'My Man is AFK or !AFK', + symbol: 'MMNAFK', + // ticker: '', + initialSupply: 100, + contract_address_salt: "" + }; + + const onSubmitPress = () => { + formikRef.current?.handleSubmit(); + }; + + const validateForm = (values: FormValues) => { + const errors = {} as Partial; + + // TODO: Do validation + + return errors; + }; + + const onFormSubmit = async (values: FormValues) => { + try { + console.log("onFormSubmit deploy") + if (!account.address) { + walletModal.show(); + const result = await waitConnection(); + if (!result) return; + } + + const data: DeployTokenFormValues = { + name: values.name, + symbol: values.symbol, + initialSupply: values?.initialSupply, + contract_address_salt: values.contract_address_salt + }; + if (!account || !account?.account) return; + deployTokenUnruggable(account?.account, data); + showToast({ type: 'success', title: 'Token launch created successfully' }); + } catch (error) { + showToast({ type: 'error', title: 'Failed to create token and launch' }); + } + }; + + const walletModal = useWalletModal(); + + + return ( + + Launch your Unruggable Token + + + + {({ handleChange, handleBlur, values, errors }) => ( + + + + + + + + + + + + )} + + + ); +}; diff --git a/apps/mobile/src/modules/LaunchTokenUnruggable/styles.ts b/apps/mobile/src/modules/LaunchTokenUnruggable/styles.ts new file mode 100644 index 00000000..5ae74348 --- /dev/null +++ b/apps/mobile/src/modules/LaunchTokenUnruggable/styles.ts @@ -0,0 +1,20 @@ +import {Spacing, ThemedStyleSheet} from '../../styles'; + +export default ThemedStyleSheet((theme) => ({ + container: { + flex: 1, + backgroundColor: theme.colors.background, + }, + + form: { + gap: Spacing.xsmall, + paddingVertical: Spacing.medium, + paddingHorizontal: Spacing.pagePadding, + }, + gap: { + height: Spacing.medium, + }, + publicKeyInput: { + color: theme.colors.primary, + }, +})); diff --git a/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx b/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx index 3e6d881a..6d67e7e8 100644 --- a/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx +++ b/apps/mobile/src/screens/CreateChannel/FormCreateChannel/index.tsx @@ -12,7 +12,8 @@ import { useFileUpload } from '../../../hooks/api'; import { useToast } from '../../../hooks/modals'; import { useCreateChannel, - useProfile + useProfile, + useSettingsStore } from "afk_nostr_sdk" // import { useAuth } from '../../../store/auth'; import { useAuth } from 'afk_nostr_sdk'; @@ -62,6 +63,7 @@ export const FormCreateChannel: React.FC = ({ showBackButton const createChannel = useCreateChannel(); const queryClient = useQueryClient(); const { showToast } = useToast(); + const {relays } = useSettingsStore() if (profile.isLoading) return null; @@ -106,7 +108,7 @@ export const FormCreateChannel: React.FC = ({ showBackButton github: profile.data?.github?.toString() ?? '', twitter: profile.data?.twitter?.toString() ?? '', tags: [], - relays: AFK_RELAYS, + relays: relays ?? AFK_RELAYS, }; const onSubmitPress = () => { diff --git a/apps/mobile/src/screens/CreateChannel/index.tsx b/apps/mobile/src/screens/CreateChannel/index.tsx index 8c1ea679..9ecc2397 100644 --- a/apps/mobile/src/screens/CreateChannel/index.tsx +++ b/apps/mobile/src/screens/CreateChannel/index.tsx @@ -9,7 +9,7 @@ import {ScrollView, TouchableOpacity, View} from 'react-native'; import {CopyIconStack} from '../../assets/icons'; import {Button, SquareInput, Text} from '../../components'; import {useStyles, useTheme} from '../../hooks'; -import {useProfile, +import {useProfile, useSettingsStore, } from "afk_nostr_sdk" import {useFileUpload} from '../../hooks/api'; import {useToast} from '../../hooks/modals'; @@ -55,6 +55,7 @@ export const CreateChannel: React.FC = () => { const fileUpload = useFileUpload(); const createChannel = useCreateChannel(); const queryClient = useQueryClient(); + const {relays } = useSettingsStore() const {showToast} = useToast(); const navigation = useNavigation(); @@ -101,7 +102,7 @@ export const CreateChannel: React.FC = () => { github: profile.data?.github?.toString() ?? '', twitter: profile.data?.twitter?.toString() ?? '', tags: [], - relays: AFK_RELAYS, + relays:relays ?? AFK_RELAYS, }; const onSubmitPress = () => { diff --git a/apps/mobile/src/screens/CreateChannel/styles.ts b/apps/mobile/src/screens/CreateChannel/styles.ts index 5ae74348..68ee49ea 100644 --- a/apps/mobile/src/screens/CreateChannel/styles.ts +++ b/apps/mobile/src/screens/CreateChannel/styles.ts @@ -4,6 +4,7 @@ export default ThemedStyleSheet((theme) => ({ container: { flex: 1, backgroundColor: theme.colors.background, + height:"100%" }, form: { diff --git a/apps/mobile/src/screens/CreateForm/index.tsx b/apps/mobile/src/screens/CreateForm/index.tsx index 80d8a1e6..95abf06b 100644 --- a/apps/mobile/src/screens/CreateForm/index.tsx +++ b/apps/mobile/src/screens/CreateForm/index.tsx @@ -5,12 +5,12 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { TextButton } from '../../components'; import TabSelector from '../../components/TabSelector'; import { useStyles } from '../../hooks'; -import { FormLaunchToken } from '../../modules/LaunchToken/FormLaunchToken'; import { CreateFormScreenProps } from '../../types'; import { SelectedTab, TABS_FORM_CREATE } from '../../types/tab'; import { FormCreateChannel } from '../CreateChannel/FormCreateChannel'; import { FormCreatePost } from '../CreatePost/FormPost'; import stylesheet from './styles'; +import { FormLaunchTokenUnruggable } from '../../modules/LaunchTokenUnruggable/FormLaunchTokenUnruggable'; export const CreateForm: React.FC = ({ navigation }) => { const styles = useStyles(stylesheet); @@ -46,7 +46,7 @@ export const CreateForm: React.FC = ({ navigation }) => { showBackButton={false} > ) : ( - selectedTab == SelectedTab.LAUNCH_TOKEN && + selectedTab == SelectedTab.LAUNCH_TOKEN && )} diff --git a/apps/mobile/src/screens/Games/index.tsx b/apps/mobile/src/screens/Games/index.tsx index a84d432a..d36ea639 100644 --- a/apps/mobile/src/screens/Games/index.tsx +++ b/apps/mobile/src/screens/Games/index.tsx @@ -9,6 +9,7 @@ import { SelectedTab, TABS_MENU } from '../../types/tab'; import stylesheet from './styles'; import { AllKeysComponent } from '../KeysMarketplace/AllKeysComponent'; import { SlinksMap } from '../Slink/SlinksMap'; +import { LaunchpadComponent } from '../Launchpad/LaunchpadComponent'; export const Games: React.FC = ({ navigation }) => { const theme = useTheme() @@ -51,6 +52,14 @@ export const Games: React.FC = ({ navigation }) => { } + + {selectedTab == SelectedTab.LAUNCHPAD_VIEW && + + + + + + } diff --git a/apps/mobile/src/screens/KeysMarketplace/index.tsx b/apps/mobile/src/screens/KeysMarketplace/index.tsx index 9d33bd6a..9388e9ab 100644 --- a/apps/mobile/src/screens/KeysMarketplace/index.tsx +++ b/apps/mobile/src/screens/KeysMarketplace/index.tsx @@ -4,7 +4,7 @@ import {useState} from 'react'; import {View, Text} from 'react-native'; import {Header} from '../../components'; import TabSelector from '../../components/TabSelector'; -import {useNostrContext} from '../../context/NostrContext'; +// import {useNostrContext} from '../../context/NostrContext'; import {useStyles, useTheme, useTips, useWaitConnection} from '../../hooks'; import {useClaim, useEstimateClaim} from '../../hooks/api'; import {useToast, useTransaction, useTransactionModal, useWalletModal} from '../../hooks/modals'; @@ -12,6 +12,7 @@ import {KeysMarketplaceSreenProps, MainStackNavigationProps} from '../../types'; import stylesheet from './styles'; import { AllKeysComponent } from './AllKeysComponent'; import { SelectedTab } from '../../types/tab'; +import { useNostrContext } from 'afk_nostr_sdk'; export const KeysMarketplace: React.FC = () => { const {theme} = useTheme(); diff --git a/apps/mobile/src/screens/LaunchToken/FormCreateChannel/index.tsx b/apps/mobile/src/screens/LaunchToken/FormCreateChannel/index.tsx index 8fd76ee1..ebaf0051 100644 --- a/apps/mobile/src/screens/LaunchToken/FormCreateChannel/index.tsx +++ b/apps/mobile/src/screens/LaunchToken/FormCreateChannel/index.tsx @@ -8,16 +8,14 @@ import { ScrollView, TouchableOpacity, View } from 'react-native'; import { CopyIconStack } from '../../../assets/icons'; import { Button, SquareInput, Text } from '../../../components'; import { useStyles, useTheme } from '../../../hooks'; -import { useProfile } from 'afk_nostr_sdk'; +import { useCreateChannel, useProfile, useSettingsStore } from 'afk_nostr_sdk'; import { useFileUpload } from '../../../hooks/api'; import { useToast } from '../../../hooks/modals'; -import { useCreateChannel } from '../../../hooks/nostr/channel/useCreateChannel'; // import { useAuth } from '../../../store/auth'; import { useAuth } from 'afk_nostr_sdk'; - -import { AFK_RELAYS } from '../../../utils/relay'; import { ChannelHead } from '../Head'; import stylesheet from './styles'; +import { AFK_RELAYS } from 'afk_nostr_sdk/src/utils/relay'; const UsernameInputLeft = ( @@ -55,6 +53,7 @@ export const FormCreateChannel: React.FC = () => { const createChannel = useCreateChannel(); const queryClient = useQueryClient(); const { showToast } = useToast(); + const {relays } = useSettingsStore() if (profile.isLoading) return null; @@ -99,7 +98,7 @@ export const FormCreateChannel: React.FC = () => { github: profile.data?.github?.toString() ?? '', twitter: profile.data?.twitter?.toString() ?? '', tags: [], - relays: AFK_RELAYS, + relays: relays ?? AFK_RELAYS, }; const onSubmitPress = () => { diff --git a/apps/mobile/src/screens/LaunchToken/index.tsx b/apps/mobile/src/screens/LaunchToken/index.tsx index 42701479..c574c755 100644 --- a/apps/mobile/src/screens/LaunchToken/index.tsx +++ b/apps/mobile/src/screens/LaunchToken/index.tsx @@ -8,17 +8,16 @@ import { ScrollView, TouchableOpacity, View } from 'react-native'; import { CopyIconStack } from '../../assets/icons'; import { Button, SquareInput, Text } from '../../components'; -import { useProfile, useStyles, useTheme } from '../../hooks'; +import { useStyles, useTheme } from '../../hooks'; import { useFileUpload } from '../../hooks/api'; import { useToast } from '../../hooks/modals'; -import { useCreateChannel } from '../../hooks/nostr/channel/useCreateChannel'; // import { useAuth } from '../../store/auth'; -import { useAuth } from 'afk_nostr_sdk'; +import { useAuth, useCreateChannel, useProfile, useSettingsStore } from 'afk_nostr_sdk'; import { CreateChannelScreenProps, MainStackNavigationProps } from '../../types'; -import { AFK_RELAYS } from '../../utils/relay'; import { ChannelHead } from './Head'; import stylesheet from './styles'; +import { AFK_RELAYS } from 'afk_nostr_sdk/src/utils/relay'; const UsernameInputLeft = ( @@ -47,6 +46,8 @@ export const CreateChannel: React.FC = () => { const { theme } = useTheme(); const styles = useStyles(stylesheet); + const {relays} = useSettingsStore() + const [profilePhoto, setProfilePhoto] = useState(); const [coverPhoto, setCoverPhoto] = useState(); @@ -101,7 +102,7 @@ export const CreateChannel: React.FC = () => { github: profile.data?.github?.toString() ?? '', twitter: profile.data?.twitter?.toString() ?? '', tags: [], - relays: AFK_RELAYS, + relays: relays ?? AFK_RELAYS, }; const onSubmitPress = () => { diff --git a/apps/mobile/src/screens/Launchpad/LaunchpadComponent.tsx b/apps/mobile/src/screens/Launchpad/LaunchpadComponent.tsx new file mode 100644 index 00000000..c43852c8 --- /dev/null +++ b/apps/mobile/src/screens/Launchpad/LaunchpadComponent.tsx @@ -0,0 +1,102 @@ +import { useState } from 'react'; +import { ActivityIndicator, FlatList, RefreshControl, View, Text } from 'react-native'; +import { Button, Divider, IconButton, Menu } from '../../components'; +import { useStyles, useTheme } from '../../hooks'; +import stylesheet from './styles'; +import { useQueryAllKeys } from '../../hooks/keys/useQueryAllKeys'; +import { KeyCardUser } from '../../components/KeyCardUser'; +import { useKeyModal } from '../../hooks/modals/useKeyModal'; +import { KeyModalAction } from '../../modules/KeyModal'; +import { useAccount } from '@starknet-react/core'; +import { useAuth } from 'afk_nostr_sdk'; +import { TokenLaunchCard } from '../../components/TokenLaunchCard'; +import { useQueryAllCoins } from '../../hooks/launchpad/useQueryAllCoins'; +import { useQueryAllLaunch } from '../../hooks/launchpad/useQueryAllLaunch'; +import { FormLaunchToken } from '../../modules/LaunchTokenPump/FormLaunchToken'; + + +interface AllKeysComponentInterface { + isButtonInstantiateEnable?: boolean +} +export const LaunchpadComponent: React.FC = ({ isButtonInstantiateEnable }) => { + const { theme } = useTheme(); + const styles = useStyles(stylesheet); + const account = useAccount() + const [loading, setLoading] = useState(false); + const queryDataLaunch = useQueryAllLaunch() + // console.log("queryDataLaunch", queryDataLaunch) + // const keys = useKeysEvents() + // console.log("keys", keys) + // const { ndk } = useNostrContext(); + // const navigation = useNavigation(); + const { show: showKeyModal } = useKeyModal(); + const [menuOpen, setMenuOpen] = useState(false); + + const { publicKey } = useAuth() + + return ( + + {queryDataLaunch?.isLoading && } + + {isButtonInstantiateEnable && + + } + {menuOpen && + + } + + } + // keyExtractor={(item, i) => {`${item.owner + item?.created_at}`}} + keyExtractor={(item, i) => i.toString()} + renderItem={({ item }) => { + // console.log("key item", item) + return ( + <> + + {/* + + + + */} + + + ); + }} + refreshControl={} + // onEndReached={() => queryDataLaunch.fetchNextPage()} + + /> + + {/* } + keyExtractor={(item) => item.event.transaction_hash} + renderItem={({ item }) => { + return ( + + + + + + + + + + + ); + }} + refreshControl={} + /> */} + + ); +}; diff --git a/apps/mobile/src/screens/Launchpad/index.tsx b/apps/mobile/src/screens/Launchpad/index.tsx new file mode 100644 index 00000000..adb21807 --- /dev/null +++ b/apps/mobile/src/screens/Launchpad/index.tsx @@ -0,0 +1,55 @@ +import {useNavigation} from '@react-navigation/native'; +import {useAccount, useProvider} from '@starknet-react/core'; +import {useState} from 'react'; +import {View, Text} from 'react-native'; +import {Header} from '../../components'; +import TabSelector from '../../components/TabSelector'; +import {useStyles, useTheme, useTips, useWaitConnection} from '../../hooks'; +import {useToast, useTransaction, useTransactionModal, useWalletModal} from '../../hooks/modals'; +import {LaunchpadScreenProps, MainStackNavigationProps} from '../../types'; +import stylesheet from './styles'; +import { useNostrContext } from 'afk_nostr_sdk'; +import { LaunchpadComponent } from './LaunchpadComponent'; +import { SelectedTab } from '../../types/tab'; + +export const LaunchpadScreen: React.FC = () => { + const {theme} = useTheme(); + const styles = useStyles(stylesheet); + const [loading, setLoading] = useState(false); + const {ndk} = useNostrContext(); + + const {provider} = useProvider(); + const account = useAccount(); + const sendTransaction = useTransaction(); + const waitConnection = useWaitConnection(); + const {show: showTransactionModal} = useTransactionModal(); + const {showToast} = useToast(); + const navigation = useNavigation(); + + + const [selectedTab, setSelectedTab] = useState(SelectedTab.TIPS); + const handleTabSelected = (tab: string | SelectedTab, screen?: string) => { + setSelectedTab(tab as any); + if (screen) { + navigation.navigate(screen as any); + } + }; + + return ( + + {/*
*/} + Launchpad to Pump it + Buy or sell a memecoin of content creator/community + + {/* {selectedTab == SelectedTab.TIPS ? ( + + ) : selectedTab == SelectedTab.CHANNELS ? ( + <> + + + ) : ( + <> + )} */} +
+ ); +}; diff --git a/apps/mobile/src/screens/Launchpad/styles.ts b/apps/mobile/src/screens/Launchpad/styles.ts new file mode 100644 index 00000000..195feedc --- /dev/null +++ b/apps/mobile/src/screens/Launchpad/styles.ts @@ -0,0 +1,56 @@ +import {Spacing, ThemedStyleSheet} from '../../styles'; + +export default ThemedStyleSheet((theme) => ({ + container: { + position: 'relative', + flex: 1, + backgroundColor: theme.colors.background, + color:theme.colors.text, + }, + + flatListContent: { + paddingHorizontal: Spacing.pagePadding, + paddingVertical: Spacing.medium, + }, + + separator: { + height: Spacing.xsmall, + }, + + tip: { + backgroundColor: theme.colors.surface, + padding: Spacing.xsmall, + borderRadius: 8, + gap: Spacing.xsmall, + }, + tokenInfo: { + flexDirection: 'row', + alignItems: 'center', + }, + token: { + flex: 1, + flexDirection: 'row', + gap: Spacing.xsmall, + }, + + senderInfo: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + gap: Spacing.medium, + }, + sender: { + flex: 1, + }, + + text: { + color: theme.colors.text, + fontSize:12, + backgroundColor:theme.colors.background + }, + + buttonIndicator: { + marginRight: Spacing.xsmall, + }, +})); diff --git a/apps/mobile/src/types/keys.ts b/apps/mobile/src/types/keys.ts index 5cff1b0f..08b9dfc6 100644 --- a/apps/mobile/src/types/keys.ts +++ b/apps/mobile/src/types/keys.ts @@ -10,6 +10,35 @@ export interface KeysUser { token_quote: TokenQuoteBuyKeys; } +export interface TokenLaunchInterface { + owner: string; + token_address: string; + price: Uint256; + initial_key_price: Uint256; + available_supply: Uint256; + bonding_curve_type?: BondingType; + total_supply: Uint256; + created_at: Uint256; + liquidity_raised: Uint256; + token_holded: Uint256; + is_liquidity_launch: boolean; + slope: Uint256; + token_quote: TokenQuoteBuyKeys; +} + +export interface Token { + owner: string; + token_address: string; + price: Uint256; + symbol: string; + name: string; + total_supply: Uint256; + initial_supply: Uint256; + created_at: Uint256; + slope: Uint256; + token_quote: TokenQuoteBuyKeys; +} + export interface TokenQuoteBuyKeys { token_address: string; price: Uint256; diff --git a/apps/mobile/src/types/routes.ts b/apps/mobile/src/types/routes.ts index 387627ae..daec9ee4 100644 --- a/apps/mobile/src/types/routes.ts +++ b/apps/mobile/src/types/routes.ts @@ -38,6 +38,7 @@ export type MainStackParams = { Home: undefined; Feed: undefined; Settings:undefined; + Launchpad:undefined; }; @@ -52,6 +53,7 @@ export type HomeBottomStackParams = { Home: undefined; Settings:undefined; Profile:{publicKey:string}; + Launchpad:undefined; // ChannelsFeed:undefined; // CreateChannel:undefined; @@ -194,6 +196,11 @@ export type SettingsScreenProps = CompositeScreenProps< >; +export type LaunchpadScreenProps = CompositeScreenProps< + NativeStackScreenProps, + NativeStackScreenProps +>; + // export type TipsMainScreenProps = CompositeScreenProps< // NativeStackScreenProps, // NativeStackScreenProps diff --git a/apps/mobile/src/types/tab.ts b/apps/mobile/src/types/tab.ts index 7e73e32d..66b561af 100644 --- a/apps/mobile/src/types/tab.ts +++ b/apps/mobile/src/types/tab.ts @@ -11,6 +11,7 @@ export enum SelectedTab { VIEW_KEYS_MARKETPLACE, LAUNCH_TOKEN_PUMP, SLINK, + LAUNCHPAD_VIEW } @@ -95,6 +96,11 @@ export const TABS_MENU: {screen?: string; title: string; tab: SelectedTab}[] = [ screen: 'KeysMarketplace', tab: SelectedTab.VIEW_KEYS_MARKETPLACE, }, + { + title: 'Pump', + screen: 'Launchpad', + tab: SelectedTab.LAUNCHPAD_VIEW, + }, { title: '?', screen: '?', diff --git a/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index cb418cc3..18ecc3a9 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -34,11 +34,15 @@ pub trait ILaunchpadMarketplace { ) -> ContractAddress; fn launch_token(ref self: TContractState, coin_address: ContractAddress); fn buy_coin(ref self: TContractState, coin_address: ContractAddress, amount: u256); + fn buy_coin_by_quote_amount(ref self: TContractState, coin_address: ContractAddress, quote_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, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256; + fn get_coin_amount_by_quote_amount( + self: @TContractState, coin_address: ContractAddress, quote_amount: u256, is_decreased: bool + ) -> u256; fn get_key_of_user(self: @TContractState, key_user: ContractAddress,) -> TokenLaunch; fn get_share_key_of_user( self: @TContractState, owner: ContractAddress, key_user: ContractAddress, @@ -434,6 +438,170 @@ mod LaunchpadMarketplace { ); } + // Buy coin by quote amount + fn buy_coin_with_quote_amount(ref self: ContractState, coin_address: ContractAddress, quote_amount: u256) { + let old_launch = self.launched_coins.read(coin_address); + assert!(!old_launch.owner.is_zero(), "coin not found"); + let memecoin = IERC20Dispatcher { contract_address: coin_address }; + let mut pool_coin = old_launch.clone(); + let total_supply_memecoin = memecoin.total_supply(); + assert!(amount < total_supply_memecoin, "too much"); + // TODO erc20 token transfer + 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 mut amount = self._get_coin_amount_by_quote_amount(coin_address, quote_amount, false); + + let mut total_price = self.get_price_of_supply_key(coin_address, amount, false); + // println!("total_price {:?}", total_price); + + let old_price = pool_coin.price.clone(); + // println!("total price cal {:?}", total_price); + let mut amount_protocol_fee: u256 = total_price * protocol_fee_percent / BPS; + // println!("amount_protocol_fee cal {:?}", amount_protocol_fee); + + // let amount_creator_fee = total_price * creator_fee_percent / BPS; + let mut remain_liquidity = total_price - amount_protocol_fee; + // println!("remain_liquidity cal {:?}", remain_liquidity); + + // Pay with quote token + // println!("amount_protocol_fee {:?}", amount_protocol_fee); + + let threshold_liquidity = self.threshold_liquidity.read(); + + + + // Transfer quote & coin + + // TOdo fix issue price + if total_price + old_launch.liquidity_raised.clone() > threshold_liquidity { + // println!( + // "total_price + old_launch.liquidity_raised.clone() > threshold_liquidity {:?}", + // total_price + old_launch.liquidity_raised.clone() > threshold_liquidity + // ); + + total_price = threshold_liquidity - old_launch.liquidity_raised.clone(); + // println!("total_price {:?}", total_price); + + amount_protocol_fee = total_price * protocol_fee_percent / BPS; + remain_liquidity = total_price - amount_protocol_fee; + + erc20 + .transfer_from( + get_caller_address(), + self.protocol_fee_destination.read(), + amount_protocol_fee + ); + // println!("remain_liquidity {:?}", remain_liquidity); + erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); + } else { + erc20 + .transfer_from( + get_caller_address(), + self.protocol_fee_destination.read(), + amount_protocol_fee + ); + // println!("remain_liquidity {:?}", remain_liquidity); + erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); + } + + // Sent coin + // println!("amount transfer to buyer {:?}", amount); + + let balance_contract = memecoin.balance_of(get_contract_address()); + // println!("buy amount balance_contract {:?}", balance_contract); + + let allowance = memecoin.allowance(pool_coin.owner.clone(), get_contract_address()); + // println!("amount allowance {:?}", allowance); + + // TODO Fixed + + if allowance >= amount && balance_contract < amount { + // println!("allowance ok {:?}", allowance); + memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + } + + if balance_contract < amount { + memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + } else if balance_contract >= amount { + let balance_contract = memecoin.balance_of(get_contract_address()); + // println!("buy amount balance_contract {:?}", balance_contract); + // TODO FIX + // println!("transfer direct amount {:?}", amount); + memecoin.transfer(get_caller_address(), amount); + // memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + } + + // Update share and key stats + let mut old_share = self.shares_by_users.read((get_caller_address(), coin_address)); + // println!("old_share {:?}", old_share.owner); + + let mut share_user = old_share.clone(); + if old_share.owner.is_zero() { + share_user = + SharesKeys { + owner: get_caller_address(), + key_address: coin_address, + amount_owned: amount, + amount_buy: amount, + amount_sell: 0, + created_at: get_block_timestamp(), + total_paid: total_price, + }; + let total_key_share = self.total_shares_keys.read(); + self.total_shares_keys.write(total_key_share + 1); + } else { + share_user.total_paid += total_price; + share_user.amount_owned += amount; + share_user.amount_buy += amount; + } + // pool_coin.price = total_price; + // pool_coin.price = total_price / amount; + pool_coin.liquidity_raised = pool_coin.liquidity_raised + total_price; + // pool_coin.total_supply += amount; + pool_coin.token_holded += amount; + + // Update state + self.shares_by_users.write((get_caller_address(), coin_address), share_user.clone()); + self.launched_coins.write(coin_address, pool_coin.clone()); + + // Check if liquidity threshold raise + let threshold = self.threshold_liquidity.read(); + let threshold_mc = self.threshold_market_cap.read(); + // println!("threshold {:?}", threshold); + // println!("pool_coin.liquidity_raised {:?}", pool_coin.liquidity_raised); + + let mc = (pool_coin.price * total_supply_memecoin); + // TODO add liquidity launch + // TOTAL_SUPPLY / 5 + // 20% go the liquidity + // 80% bought by others + if pool_coin.liquidity_raised >= threshold { + // println!("mc threshold reached"); + self._add_liquidity(coin_address); + } + + if mc >= threshold_mc { + // println!("mc threshold reached"); + self._add_liquidity(coin_address); + } + + self + .emit( + BuyToken { + caller: get_caller_address(), + key_user: coin_address, + amount: amount, + price: total_price, + protocol_fee: amount_protocol_fee, + creator_fee: 0, + timestamp: get_block_timestamp(), + last_price: old_price, + } + ); + } + fn sell_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { let old_pool = self.launched_coins.read(coin_address); assert(!old_pool.owner.is_zero(), 'coin not found'); @@ -542,6 +710,12 @@ mod LaunchpadMarketplace { self._get_price_of_supply_key(coin_address, amount, is_decreased) } + fn get_coin_amount_by_quote_amount( + self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool + ) -> u256 { + self._get_coin_amount_by_quote_amount(coin_address, amount, is_decreased) + } + fn get_key_of_user(self: @ContractState, key_user: ContractAddress,) -> TokenLaunch { self.launched_coins.read(key_user) } @@ -717,7 +891,37 @@ mod LaunchpadMarketplace { fn _get_linear_price(initial_price: u256, slope: u256, supply: u256) -> u256 { return initial_price + (slope * supply); } + fn _get_coin_amount_by_quote_amount( + self: @ContractState, coin_address: ContractAddress, quote_amount: u256, is_decreased: bool + + ) -> u256 { + + + let coin_amount=0; + let pool= self.launched_coins.read(coin_address); + + + let mut current_supply=pool.token_holded.clone(); + let total_supply=pool.total_supply.clone(); + let mut current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + + + + let mut amount= quote_amount.clone(); + + while amount >= current_price && current_supply < total_supply / LIQUIDITY_RATIO { + + amount-=current_price; + coin_amount+=1; + current_supply+=1; + current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + } + + + coin_amount + + } fn _calculate_pricing(ref self: ContractState, liquidity_available:u256) -> (u256, u256) { @@ -730,6 +934,8 @@ mod LaunchpadMarketplace { (slope, initial_price) } + + fn _get_price_of_supply_key( self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256 { diff --git a/packages/afk_nostr_sdk/src/index.ts b/packages/afk_nostr_sdk/src/index.ts index b7c248f1..c52b6ea0 100644 --- a/packages/afk_nostr_sdk/src/index.ts +++ b/packages/afk_nostr_sdk/src/index.ts @@ -1,30 +1,3 @@ export * from "./context" export * from "./hooks" export * from "./store" - - -// import { NostrContext, NostrProvider, useNostrContext } from "./context/NostrContext"; -// import { TanstackProvider } from "./context/TanstackProvider"; -// import * as hooks from "./hooks" -// import { NDKKind, NDKEvent, NDKUser } from "@nostr-dev-kit/ndk" -// import { AFK_RELAYS } from "./utils/relay"; - -// export default { -// hooks, NostrContext, -// TanstackProvider, -// context: { -// NostrContext, NostrProvider, TanstackProvider, -// useNostrContext, -// }, -// NDKEvent, NDKKind, NDKUser, -// AFK_RELAYS -// }; -// export default { -// hooks, NostrContext, TanstackProvider, -// context: { -// NostrContext, NostrProvider, TanstackProvider, -// useNostrContext, -// }, -// NDKEvent, NDKKind, NDKUser, -// AFK_RELAYS -// }; \ No newline at end of file From 4ddf9f7679a6f9f507273a9d54d2846f54dad41f Mon Sep 17 00:00:00 2001 From: MSghais Date: Fri, 16 Aug 2024 12:41:08 +0200 Subject: [PATCH 6/8] check class hash --- apps/mobile/src/hooks/launchpad/useCreateToken.ts | 2 +- apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/mobile/src/hooks/launchpad/useCreateToken.ts b/apps/mobile/src/hooks/launchpad/useCreateToken.ts index 13278c5d..3d4e458b 100644 --- a/apps/mobile/src/hooks/launchpad/useCreateToken.ts +++ b/apps/mobile/src/hooks/launchpad/useCreateToken.ts @@ -20,8 +20,8 @@ export const useCreateToken = () => { entrypoint: 'create_token', calldata: CallData.compile({ owner: data?.recipient ?? account?.address, - name: data.name ?? "LFG", symbol: data.symbol ?? "LFG", + name: data.name ?? "LFG", initialSupply: cairo.uint256(data?.initialSupply ?? 100), contract_address_salt:CONTRACT_ADDRESS_SALT_DEFAULT }), diff --git a/apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx b/apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx index bdf77fb3..5d3b260c 100644 --- a/apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx +++ b/apps/mobile/src/modules/LaunchTokenPump/FormLaunchToken.tsx @@ -47,7 +47,7 @@ export const FormLaunchToken: React.FC = () => { symbol: 'MY_MAN', // ticker: '', initialSupply: 100_000_000, - contract_address_salt: "", + contract_address_salt: undefined, recipient: account?.address }; From 0bfea961d70995d90f7a87bc12d89145637b561c Mon Sep 17 00:00:00 2001 From: MSGhais Date: Fri, 16 Aug 2024 13:26:19 +0200 Subject: [PATCH 7/8] refacto test --- apps/mobile/src/screens/Games/index.tsx | 4 +- onchain/.snfoundry_cache/.prev_tests_failed | 2 +- onchain/src/keys/keys.cairo | 192 ------- onchain/src/launchpad/launchpad.cairo | 559 +++++--------------- onchain/src/lib.cairo | 10 +- onchain/src/tests/keys.cairo | 2 - onchain/src/tests/keys_tests.cairo | 190 +++++++ onchain/src/tests/launchpad.cairo | 1 - onchain/src/tests/launchpad_tests.cairo | 440 +++++++++++++++ onchain/src/utils.cairo | 12 + 10 files changed, 791 insertions(+), 621 deletions(-) delete mode 100644 onchain/src/tests/keys.cairo create mode 100644 onchain/src/tests/keys_tests.cairo delete mode 100644 onchain/src/tests/launchpad.cairo create mode 100644 onchain/src/tests/launchpad_tests.cairo diff --git a/apps/mobile/src/screens/Games/index.tsx b/apps/mobile/src/screens/Games/index.tsx index d36ea639..526b340e 100644 --- a/apps/mobile/src/screens/Games/index.tsx +++ b/apps/mobile/src/screens/Games/index.tsx @@ -55,8 +55,8 @@ export const Games: React.FC = ({ navigation }) => { {selectedTab == SelectedTab.LAUNCHPAD_VIEW && - - + Coming soon + {/* */} } diff --git a/onchain/.snfoundry_cache/.prev_tests_failed b/onchain/.snfoundry_cache/.prev_tests_failed index 022ee682..70563d04 100644 --- a/onchain/.snfoundry_cache/.prev_tests_failed +++ b/onchain/.snfoundry_cache/.prev_tests_failed @@ -1 +1 @@ -afk::launchpad::launchpad::tests::launchpad_end_to_end +afk::tests::launchpad_tests::launchpad_tests::launchpad_integration diff --git a/onchain/src/keys/keys.cairo b/onchain/src/keys/keys.cairo index 21bc3a76..d92b43fe 100644 --- a/onchain/src/keys/keys.cairo +++ b/onchain/src/keys/keys.cairo @@ -695,195 +695,3 @@ mod KeysMarketplace { } } } - - -#[cfg(test)] -mod tests { - use afk::erc20::{ERC20, IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; - use afk::types::keys_types::{ - MINTER_ROLE, ADMIN_ROLE, KeysBonding, TokenQuoteBuyKeys, BondingType - }; - use core::array::SpanTrait; - use core::traits::Into; - use openzeppelin::account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; - use openzeppelin::utils::serde::SerializedAppend; - - use snforge_std::{ - declare, ContractClass, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, - Event, EventAssertions, start_cheat_caller_address, cheat_caller_address_global, - stop_cheat_caller_address, stop_cheat_caller_address_global, start_cheat_block_timestamp - }; - // const INITIAL_KEY_PRICE:u256=1/100; - - use starknet::{ - ContractAddress, get_caller_address, storage_access::StorageBaseAddress, - get_block_timestamp, get_contract_address - }; - // use afk::keys::{IKeysMarketplaceDispatcher, IKeysMarketplaceDispatcherTrait}; - use super::{IKeysMarketplaceDispatcher, IKeysMarketplaceDispatcherTrait}; - - // const INITIAL_KEY_PRICE:u256=1/100; - const INITIAL_KEY_PRICE: u256 = 1; - const STEP_LINEAR_INCREASE: u256 = 1; - - fn request_fixture() -> (ContractAddress, IERC20Dispatcher, IKeysMarketplaceDispatcher) { - // println!("request_fixture"); - let erc20_class = declare_erc20(); - let keys_class = declare_marketplace(); - request_fixture_custom_classes(erc20_class, keys_class) - } - - fn request_fixture_custom_classes( - erc20_class: ContractClass, escrow_class: ContractClass - ) -> (ContractAddress, IERC20Dispatcher, IKeysMarketplaceDispatcher) { - 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_marketplace( - escrow_class, - sender_address, - token_address.clone(), - INITIAL_KEY_PRICE, - STEP_LINEAR_INCREASE - ); - (sender_address, erc20, keys) - } - - fn declare_marketplace() -> ContractClass { - declare("KeysMarketplace").unwrap() - } - - fn declare_erc20() -> ContractClass { - declare("ERC20").unwrap() - } - - fn deploy_marketplace( - class: ContractClass, - admin: ContractAddress, - token_address: ContractAddress, - initial_key_price: u256, - step_increase_linear: u256 - ) -> IKeysMarketplaceDispatcher { - // 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); - let (contract_address, _) = class.deploy(@calldata).unwrap(); - IKeysMarketplaceDispatcher { 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 } - } - - // #[test] - // fn test_instantiate_keys() { - // let (sender_address, erc20, keys) = request_fixture(); - // let default_token = keys.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'); - // // println!("instantiate keys"); - // start_cheat_caller_address(keys.contract_address, sender_address); - // keys.instantiate_keys(); - // let mut key_user = keys.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'); - // // assert(key_user.token_quote == erc20.contract_address, 'not same token'); - // } - - #[test] - fn keys_end_to_end() { - let (sender_address, erc20, keys) = request_fixture(); - let amount_key_buy = 1_u256; - cheat_caller_address_global(sender_address); - start_cheat_caller_address(erc20.contract_address, sender_address); - // start_cheat_caller_address(key_address, sender_address); - erc20.approve(keys.contract_address, amount_key_buy); - // Call a view function of the contract - // Check default token used - let default_token = keys.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(keys.contract_address, sender_address); - - keys.instantiate_keys(); - // println!("get all_keys"); - - // let mut all_keys = keys.get_all_keys(); - let mut key_user = keys.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 = keys - .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(keys.contract_address, amount_to_paid); - - let allowance = erc20.allowance(sender_address, keys.contract_address); - println!("test allowance {}", allowance); - stop_cheat_caller_address(erc20.contract_address); - - start_cheat_caller_address(keys.contract_address, sender_address); - keys.buy_keys(sender_address, amount_key_buy); - - let mut key_user = keys.get_key_of_user(sender_address); - println!("test key_user total supply {:?}", key_user.total_supply); - - // Buy others key - stop_cheat_caller_address(keys.contract_address); - - let amount_key_buy = 3_u256; - - // println!("all_keys {:?}", all_keys); - let amount_to_paid = keys - .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(keys.contract_address, amount_to_paid); - - let allowance = erc20.allowance(sender_address, keys.contract_address); - println!("test allowance {}", allowance); - stop_cheat_caller_address(erc20.contract_address); - - start_cheat_caller_address(keys.contract_address, sender_address); - keys.buy_keys(sender_address, amount_key_buy); - let mut key_user = keys.get_key_of_user(sender_address); - - println!("test key_user total supply {:?}", key_user.total_supply); - } -} diff --git a/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index 18ecc3a9..1988cfce 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -3,6 +3,7 @@ use afk::types::launchpad_types::{ TokenQuoteBuyKeys, TokenLaunch, SharesKeys, BondingType, Token, CreateLaunch }; use starknet::ContractAddress; +use starknet::ClassHash; #[starknet::interface] pub trait ILaunchpadMarketplace { @@ -12,6 +13,7 @@ pub trait ILaunchpadMarketplace { 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_class_hash(ref self: TContractState, class_hash: ClassHash); fn set_protocol_fee_destination( ref self: TContractState, protocol_fee_destination: ContractAddress ); @@ -53,6 +55,7 @@ pub trait ILaunchpadMarketplace { #[starknet::contract] mod LaunchpadMarketplace { use afk::erc20::{ERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use afk::utils::{sqrt}; use core::num::traits::Zero; use openzeppelin::access::accesscontrol::{AccessControlComponent}; use openzeppelin::introspection::src5::SRC5Component; @@ -235,6 +238,12 @@ mod LaunchpadMarketplace { self.dollar_price_percentage.write(bps); } + + fn set_class_hash(ref self: ContractState, class_hash:ClassHash) { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + self.coin_class_hash.write(class_hash); + } + // Create keys for an user fn create_token( ref self: ContractState, @@ -278,6 +287,8 @@ mod LaunchpadMarketplace { } // Buy a coin to a bonding curve + // Amount is the number of coin you want to buy + // The function calculates the price of quote_token you need to buy the token fn buy_coin(ref self: ContractState, coin_address: ContractAddress, amount: u256) { let old_launch = self.launched_coins.read(coin_address); assert!(!old_launch.owner.is_zero(), "coin not found"); @@ -288,7 +299,6 @@ mod LaunchpadMarketplace { // TODO erc20 token transfer 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(); // Update Launch pool with new values @@ -308,7 +318,34 @@ mod LaunchpadMarketplace { // println!("amount_protocol_fee {:?}", amount_protocol_fee); let threshold_liquidity = self.threshold_liquidity.read(); + + // Sent coin + println!("amount transfer to buyer {:?}", amount); + + let balance_contract = memecoin.balance_of(get_contract_address()); + println!("buy amount balance_contract {:?}", balance_contract); + + let allowance = memecoin.allowance(pool_coin.owner.clone(), get_contract_address()); + println!("amount allowance {:?}", allowance); + + // TODO Fixed + + if allowance >= amount + // && balance_contract < amount + { + println!("allowance ok {:?}", allowance); + memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + } + else if balance_contract >= amount { + let balance_contract = memecoin.balance_of(get_contract_address()); + println!("buy amount balance_contract {:?}", balance_contract); + // TODO FIX + println!("transfer direct amount {:?}", amount); + memecoin.transfer(get_caller_address(), amount); + // memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + } + let erc20 = IERC20Dispatcher { contract_address: quote_token_address }; // TOdo fix issue price if total_price + old_launch.liquidity_raised.clone() > threshold_liquidity { @@ -342,32 +379,17 @@ mod LaunchpadMarketplace { erc20.transfer_from(get_caller_address(), get_contract_address(), remain_liquidity); } - // Sent coin - // println!("amount transfer to buyer {:?}", amount); - - let balance_contract = memecoin.balance_of(get_contract_address()); - // println!("buy amount balance_contract {:?}", balance_contract); - - let allowance = memecoin.allowance(pool_coin.owner.clone(), get_contract_address()); - // println!("amount allowance {:?}", allowance); - - // TODO Fixed - - if allowance >= amount && balance_contract < amount { - // println!("allowance ok {:?}", allowance); - memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); - } - if balance_contract < amount { - memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); - } else if balance_contract >= amount { - let balance_contract = memecoin.balance_of(get_contract_address()); - // println!("buy amount balance_contract {:?}", balance_contract); - // TODO FIX - // println!("transfer direct amount {:?}", amount); - memecoin.transfer(get_caller_address(), amount); - // memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); - } + // if balance_contract < amount { + // memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + // } else if balance_contract >= amount { + // let balance_contract = memecoin.balance_of(get_contract_address()); + // println!("buy amount balance_contract {:?}", balance_contract); + // // TODO FIX + // println!("transfer direct amount {:?}", amount); + // memecoin.transfer(get_caller_address(), amount); + // // memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); + // } // Update share and key stats let mut old_share = self.shares_by_users.read((get_caller_address(), coin_address)); @@ -439,13 +461,13 @@ mod LaunchpadMarketplace { } // Buy coin by quote amount - fn buy_coin_with_quote_amount(ref self: ContractState, coin_address: ContractAddress, quote_amount: u256) { + fn buy_coin_by_quote_amount(ref self: ContractState, coin_address: ContractAddress, quote_amount: u256) { let old_launch = self.launched_coins.read(coin_address); assert!(!old_launch.owner.is_zero(), "coin not found"); let memecoin = IERC20Dispatcher { contract_address: coin_address }; let mut pool_coin = old_launch.clone(); let total_supply_memecoin = memecoin.total_supply(); - assert!(amount < total_supply_memecoin, "too much"); + assert!(quote_amount < total_supply_memecoin, "too much"); // TODO erc20 token transfer let token_quote = old_launch.token_quote.clone(); let quote_token_address = token_quote.token_address.clone(); @@ -470,8 +492,6 @@ mod LaunchpadMarketplace { let threshold_liquidity = self.threshold_liquidity.read(); - - // Transfer quote & coin // TOdo fix issue price @@ -518,7 +538,7 @@ mod LaunchpadMarketplace { // TODO Fixed if allowance >= amount && balance_contract < amount { - // println!("allowance ok {:?}", allowance); + println!("allowance ok {:?}", allowance); memecoin.transfer_from(pool_coin.owner.clone(), get_caller_address(), amount); } @@ -703,7 +723,7 @@ mod LaunchpadMarketplace { fn get_default_token(self: @ContractState) -> TokenQuoteBuyKeys { self.default_token.read() } - + // The function calculates the amiunt of quote_token you need to buy a coin in the pool fn get_price_of_supply_key( self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool ) -> u256 { @@ -711,9 +731,9 @@ mod LaunchpadMarketplace { } fn get_coin_amount_by_quote_amount( - self: @ContractState, coin_address: ContractAddress, amount: u256, is_decreased: bool + self: @ContractState, coin_address: ContractAddress, quote_amount: u256, is_decreased: bool ) -> u256 { - self._get_coin_amount_by_quote_amount(coin_address, amount, is_decreased) + self._get_coin_amount_by_quote_amount(coin_address, quote_amount, is_decreased) } fn get_key_of_user(self: @ContractState, key_user: ContractAddress,) -> TokenLaunch { @@ -888,41 +908,115 @@ mod LaunchpadMarketplace { fn _add_liquidity(ref self: ContractState, coin_address: ContractAddress) {} // Function to calculate the price for the next token to be minted - fn _get_linear_price(initial_price: u256, slope: u256, supply: u256) -> u256 { + fn _get_linear_price(self: @ContractState, initial_price: u256, slope: u256, supply: u256) -> u256 { return initial_price + (slope * supply); } + fn _get_coin_amount_by_quote_amount( self: @ContractState, coin_address: ContractAddress, quote_amount: u256, is_decreased: bool ) -> u256 { + let pool_coin= self.launched_coins.read(coin_address); + + let mut coin_amount=0; + let slope= pool_coin.slope; + let mut a = slope/2; + let initial_price=pool_coin.initial_key_price.clone(); + let mut current_supply=pool_coin.token_holded.clone(); - let coin_amount=0; - let pool= self.launched_coins.read(coin_address); + let sold_supply=current_supply.clone(); + let mut b=initial_price+slope* sold_supply-slope/2; + // let c = -quote_amount; + let c = quote_amount; - let mut current_supply=pool.token_holded.clone(); - let total_supply=pool.total_supply.clone(); + // Solving the quadratic equation: ax^2 + bx + c = 0 + // x = (-b ± sqrt(b^2 - 4ac)) / 2a - let mut current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + // TODO how do negative number + let discriminant=(b*b) + (4*a*c); + assert!(discriminant > 0, "no real root"); + let sqrt_discriminant= sqrt(discriminant); + // TODO B need to be negative - let mut amount= quote_amount.clone(); + let n1= (b + sqrt_discriminant) / (2*a); + let n2= (b - sqrt_discriminant) / (2*a); - while amount >= current_price && current_supply < total_supply / LIQUIDITY_RATIO { + // let n1= (-b + sqrt_discriminant) / (2*a); + // let n2= (-b - sqrt_discriminant) / (2*a); - amount-=current_price; - coin_amount+=1; - current_supply+=1; - current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + + if n1 > n2 { + n1 + } else { + n1 } - coin_amount + // let total_supply=pool_coin.total_supply.clone(); + + // let mut current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + + // let mut amount= quote_amount.clone(); + + // println!("amount {:?}",amount); + // println!("current_price {:?}",amount); + // println!("total_supply {:?}",total_supply); + // println!("slope {:?}",slope); + // println!("current_supply {:?}",current_supply); + + // while amount >= current_price && current_supply < total_supply / LIQUIDITY_RATIO { + + // amount-=current_price; + // coin_amount+=1; + // current_supply+=1; + // current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + // }; + + + // coin_amount } + // fn _get_coin_amount_by_quote_amount( + // self: @ContractState, coin_address: ContractAddress, quote_amount: u256, is_decreased: bool + + // ) -> u256 { + + + // let mut coin_amount=0; + // let pool_coin= self.launched_coins.read(coin_address); + + + // let mut current_supply=pool_coin.token_holded.clone(); + // let total_supply=pool_coin.total_supply.clone(); + + // let mut current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + + // let mut amount= quote_amount.clone(); + + // println!("amount {:?}",amount); + // println!("current_price {:?}",amount); + // println!("total_supply {:?}",total_supply); + // println!("slope {:?}",slope); + // println!("current_supply {:?}",current_supply); + + // while amount >= current_price && current_supply < total_supply / LIQUIDITY_RATIO { + + // amount-=current_price; + // coin_amount+=1; + // current_supply+=1; + // current_price= self._get_linear_price(pool_coin.initial_key_price, pool_coin.slope, current_supply); + // }; + + + // coin_amount + + // } + fn _calculate_pricing(ref self: ContractState, liquidity_available:u256) -> (u256, u256) { let threshold_liquidity = self.threshold_liquidity.read(); @@ -1019,374 +1113,3 @@ mod LaunchpadMarketplace { } } - -#[cfg(test)] -mod tests { - use afk::erc20::{ERC20, IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; - use afk::types::launchpad_types::{MINTER_ROLE, ADMIN_ROLE, TokenQuoteBuyKeys, BondingType}; - use core::array::SpanTrait; - use core::num::traits::Zero; - use core::traits::Into; - use openzeppelin::account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; - use openzeppelin::utils::serde::SerializedAppend; - - use snforge_std::{ - declare, ContractClass, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, - Event, EventAssertions, start_cheat_caller_address, cheat_caller_address_global, - stop_cheat_caller_address, stop_cheat_caller_address_global, start_cheat_block_timestamp - }; - use starknet::syscalls::deploy_syscall; - - // const INITIAL_KEY_PRICE:u256=1/100; - - use starknet::{ - ContractAddress, get_caller_address, storage_access::StorageBaseAddress, - get_block_timestamp, get_contract_address, ClassHash - }; - // use afk::keys::{IKeysMarketplaceDispatcher, IKeysMarketplaceDispatcherTrait}; - use super::{ILaunchpadMarketplaceDispatcher, ILaunchpadMarketplaceDispatcherTrait}; - - fn DEFAULT_INITIAL_SUPPLY() -> u256 { - // 21_000_000 * pow_256(10, 18) - 100_000_000 - // * pow_256(10, 18) - } - - // const INITIAL_KEY_PRICE:u256=1/100; - const INITIAL_SUPPLY_DEFAULT: u256 = 100_000_000; - const INITIAL_KEY_PRICE: u256 = 1 / 10_000; - const STEP_LINEAR_INCREASE: u256 = 1; - // const THRESHOLD_LIQUIDITY: u256 = 10; - const THRESHOLD_LIQUIDITY: u256 = 10_000; - const THRESHOLD_MARKET_CAP: u256 = 50_000; - const RATIO_SUPPLY_LAUNCH: u256 = 5; - const LIQUIDITY_SUPPLY: u256 = INITIAL_SUPPLY_DEFAULT / RATIO_SUPPLY_LAUNCH; - const BUYABLE: u256 = INITIAL_SUPPLY_DEFAULT / RATIO_SUPPLY_LAUNCH; - - - fn SALT() -> felt252 { - 'salty'.try_into().unwrap() - } - - // Constants - fn OWNER() -> ContractAddress { - // 'owner'.try_into().unwrap() - 123.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 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 launchpad = deploy_launchpad( - launch_class, - sender_address, - token_address.clone(), - INITIAL_KEY_PRICE, - STEP_LINEAR_INCREASE, - erc20_class.class_hash, - THRESHOLD_LIQUIDITY, - THRESHOLD_MARKET_CAP - ); - // let launchpad = deploy_launchpad( - // launch_class, - // sender_address, - // token_address.clone(), - // INITIAL_KEY_PRICE * pow_256(10,18), - // // INITIAL_KEY_PRICE, - // // STEP_LINEAR_INCREASE, - // STEP_LINEAR_INCREASE * pow_256(10,18), - // erc20_class.class_hash, - // THRESHOLD_LIQUIDITY * pow_256(10,18), - // // THRESHOLD_LIQUIDITY, - // THRESHOLD_MARKET_CAP * pow_256(10,18), - // // THRESHOLD_MARKET_CAP - // ); - (sender_address, erc20, launchpad) - } - - fn deploy_launchpad( - class: ContractClass, - admin: ContractAddress, - token_address: ContractAddress, - initial_key_price: u256, - step_increase_linear: u256, - coin_class_hash: ClassHash, - threshold_liquidity: u256, - threshold_marketcap: u256, - ) -> ILaunchpadMarketplaceDispatcher { - // println!("deploy marketplace"); - let mut calldata = array![admin.into()]; - calldata.append_serde(initial_key_price); - calldata.append_serde(token_address); - calldata.append_serde(step_increase_linear); - calldata.append_serde(coin_class_hash); - calldata.append_serde(threshold_liquidity); - calldata.append_serde(threshold_marketcap); - let (contract_address, _) = class.deploy(@calldata).unwrap(); - ILaunchpadMarketplaceDispatcher { contract_address } - } - - fn declare_launchpad() -> ContractClass { - declare("LaunchpadMarketplace").unwrap() - } - - fn declare_erc20() -> ContractClass { - declare("ERC20").unwrap() - } - - - 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 run_buy( - launchpad: ILaunchpadMarketplaceDispatcher, - erc20: IERC20Dispatcher, - memecoin: IERC20Dispatcher, - amount_key_buy: u256, - token_address: ContractAddress, - sender_address: ContractAddress, - ) { - let amount_to_paid = launchpad - .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, - ); - println!("test amount_to_paid erc20 {:?}", amount_to_paid); - - start_cheat_caller_address(erc20.contract_address, sender_address); - - erc20.approve(launchpad.contract_address, amount_to_paid); - - let allowance = erc20.allowance(sender_address, launchpad.contract_address); - println!("test allowance erc20 {}", allowance); - stop_cheat_caller_address(erc20.contract_address); - - start_cheat_caller_address(launchpad.contract_address, sender_address); - println!("buy coin",); - - let allowance = memecoin.allowance(sender_address, launchpad.contract_address); - println!("test allowance meme coin{}", allowance); - - launchpad.buy_coin(token_address, amount_key_buy); - } - - #[test] - fn launchpad_end_to_end() { - println!("launchpad_enter_end_to_end"); - let (sender_address, erc20, launchpad) = request_fixture(); - let amount_key_buy = 1_u256; - cheat_caller_address_global(sender_address); - start_cheat_caller_address(erc20.contract_address, sender_address); - // Call a view function of the contract - // Check default token used - let default_token = launchpad.get_default_token(); - assert(default_token.token_address == erc20.contract_address, 'no default token'); - assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); - - start_cheat_caller_address(launchpad.contract_address, sender_address); - - println!("create and launch token"); - let token_address = launchpad - .create_and_launch_token( - // owner: OWNER(), - symbol: SYMBOL(), - name: NAME(), - initial_supply: DEFAULT_INITIAL_SUPPLY(), - contract_address_salt: SALT(), - ); - println!("test token_address {:?}", token_address); - - let memecoin = IERC20Dispatcher { contract_address: token_address }; - - // Final buy - let res = run_buy( - launchpad, erc20, memecoin, INITIAL_SUPPLY_DEFAULT - LIQUIDITY_SUPPLY, token_address, sender_address, - ); - - } - - #[test] - fn launchpad_all_coin() { - println!("launchpad_enter_end_to_end"); - let (sender_address, erc20, launchpad) = request_fixture(); - let amount_key_buy = 1_u256; - cheat_caller_address_global(sender_address); - start_cheat_caller_address(erc20.contract_address, sender_address); - // Call a view function of the contract - // Check default token used - let default_token = launchpad.get_default_token(); - assert(default_token.token_address == erc20.contract_address, 'no default token'); - assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); - - start_cheat_caller_address(launchpad.contract_address, sender_address); - - println!("create and launch token"); - let token_address = launchpad - .create_and_launch_token( - // owner: OWNER(), - symbol: SYMBOL(), - name: NAME(), - initial_supply: DEFAULT_INITIAL_SUPPLY(), - contract_address_salt: SALT(), - ); - println!("test token_address {:?}", token_address); - - let memecoin = IERC20Dispatcher { contract_address: token_address }; - - // Final buy - let res = run_buy( - launchpad, erc20, memecoin, LIQUIDITY_SUPPLY, token_address, sender_address, - ); - - - } - - - #[test] - fn launchpad_integration() { - println!("launchpad_integration"); - - let (sender_address, erc20, launchpad) = request_fixture(); - let amount_key_buy = 1_u256; - cheat_caller_address_global(sender_address); - start_cheat_caller_address(erc20.contract_address, sender_address); - // Call a view function of the contract - // Check default token used - let default_token = launchpad.get_default_token(); - assert(default_token.token_address == erc20.contract_address, 'no default token'); - assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); - - start_cheat_caller_address(launchpad.contract_address, sender_address); - - let token_address = launchpad - .create_token( - recipient: OWNER(), - // owner: OWNER(), - symbol: SYMBOL(), - name: NAME(), - initial_supply: DEFAULT_INITIAL_SUPPLY(), - contract_address_salt: SALT(), - ); - println!("test token_address {:?}", token_address); - - let memecoin = IERC20Dispatcher { contract_address: token_address }; - start_cheat_caller_address(memecoin.contract_address, sender_address); - - let balance_contract = memecoin.balance_of(launchpad.contract_address); - println!("test balance_contract {:?}", balance_contract); - - let total_supply = memecoin.total_supply(); - println!(" memecoin total_supply {:?}", total_supply); - memecoin.approve(launchpad.contract_address, total_supply); - - let allowance = memecoin.allowance(sender_address, launchpad.contract_address); - println!("test allowance meme coin{}", allowance); - memecoin.transfer(launchpad.contract_address, total_supply); - - - // Launch coin pool - // Send total supply - println!("launch token"); - launchpad.launch_token(token_address); - // Test buy coin - println!("amount_to_paid",); - // println!("all_keys {:?}", all_keys); - let amount_key_buy = 100_u256; - - let amount_to_paid = launchpad - .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, - // BondingType::Basic, default_token.clone() - ); - println!("test amount_to_paid {:?}", amount_to_paid); - - start_cheat_caller_address(erc20.contract_address, sender_address); - - erc20.approve(launchpad.contract_address, amount_to_paid); - - let allowance = erc20.allowance(sender_address, launchpad.contract_address); - println!("test allowance {}", allowance); - stop_cheat_caller_address(erc20.contract_address); - - start_cheat_caller_address(launchpad.contract_address, sender_address); - println!("buy coin",); - - launchpad.buy_coin(token_address, amount_key_buy); - - run_buy( - launchpad, erc20, memecoin, LIQUIDITY_SUPPLY, token_address, sender_address, - ); - - } -} diff --git a/onchain/src/lib.cairo b/onchain/src/lib.cairo index 494afb3a..6cab2420 100644 --- a/onchain/src/lib.cairo +++ b/onchain/src/lib.cairo @@ -9,10 +9,10 @@ pub mod types { pub mod keys_types; pub mod launchpad_types; } -// #[cfg(test)] -// pub mod tests { -// pub mod keys; -// pub mod launchpad_tests; -// } +#[cfg(test)] +pub mod tests { + pub mod keys_tests; + pub mod launchpad_tests; +} diff --git a/onchain/src/tests/keys.cairo b/onchain/src/tests/keys.cairo deleted file mode 100644 index 2046b3a3..00000000 --- a/onchain/src/tests/keys.cairo +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(test)] -pub mod tests {} diff --git a/onchain/src/tests/keys_tests.cairo b/onchain/src/tests/keys_tests.cairo new file mode 100644 index 00000000..39c9d322 --- /dev/null +++ b/onchain/src/tests/keys_tests.cairo @@ -0,0 +1,190 @@ +#[cfg(test)] +mod keys_tests { + use afk::erc20::{ERC20, IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use afk::types::keys_types::{ + MINTER_ROLE, ADMIN_ROLE, KeysBonding, TokenQuoteBuyKeys, BondingType + }; + use core::array::SpanTrait; + use core::traits::Into; + use openzeppelin::account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; + use openzeppelin::utils::serde::SerializedAppend; + + use snforge_std::{ + declare, ContractClass, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, + Event, EventAssertions, start_cheat_caller_address, cheat_caller_address_global, + stop_cheat_caller_address, stop_cheat_caller_address_global, start_cheat_block_timestamp + }; + // const INITIAL_KEY_PRICE:u256=1/100; + + use starknet::{ + ContractAddress, get_caller_address, storage_access::StorageBaseAddress, + get_block_timestamp, get_contract_address + }; + use afk::keys::keys::{IKeysMarketplaceDispatcher, IKeysMarketplaceDispatcherTrait}; + + // const INITIAL_KEY_PRICE:u256=1/100; + const INITIAL_KEY_PRICE: u256 = 1; + const STEP_LINEAR_INCREASE: u256 = 1; + + fn request_fixture() -> (ContractAddress, IERC20Dispatcher, IKeysMarketplaceDispatcher) { + // println!("request_fixture"); + let erc20_class = declare_erc20(); + let keys_class = declare_marketplace(); + request_fixture_custom_classes(erc20_class, keys_class) + } + + fn request_fixture_custom_classes( + erc20_class: ContractClass, escrow_class: ContractClass + ) -> (ContractAddress, IERC20Dispatcher, IKeysMarketplaceDispatcher) { + 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_marketplace( + escrow_class, + sender_address, + token_address.clone(), + INITIAL_KEY_PRICE, + STEP_LINEAR_INCREASE + ); + (sender_address, erc20, keys) + } + + fn declare_marketplace() -> ContractClass { + declare("KeysMarketplace").unwrap() + } + + fn declare_erc20() -> ContractClass { + declare("ERC20").unwrap() + } + + fn deploy_marketplace( + class: ContractClass, + admin: ContractAddress, + token_address: ContractAddress, + initial_key_price: u256, + step_increase_linear: u256 + ) -> IKeysMarketplaceDispatcher { + // 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); + let (contract_address, _) = class.deploy(@calldata).unwrap(); + IKeysMarketplaceDispatcher { 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 } + } + + // #[test] + // fn test_instantiate_keys() { + // let (sender_address, erc20, keys) = request_fixture(); + // let default_token = keys.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'); + // // println!("instantiate keys"); + // start_cheat_caller_address(keys.contract_address, sender_address); + // keys.instantiate_keys(); + // let mut key_user = keys.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'); + // // assert(key_user.token_quote == erc20.contract_address, 'not same token'); + // } + + #[test] + fn keys_end_to_end() { + let (sender_address, erc20, keys) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // start_cheat_caller_address(key_address, sender_address); + erc20.approve(keys.contract_address, amount_key_buy); + // Call a view function of the contract + // Check default token used + let default_token = keys.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(keys.contract_address, sender_address); + + keys.instantiate_keys(); + // println!("get all_keys"); + + // let mut all_keys = keys.get_all_keys(); + let mut key_user = keys.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 = keys + .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(keys.contract_address, amount_to_paid); + + let allowance = erc20.allowance(sender_address, keys.contract_address); + println!("test allowance {}", allowance); + stop_cheat_caller_address(erc20.contract_address); + + start_cheat_caller_address(keys.contract_address, sender_address); + keys.buy_keys(sender_address, amount_key_buy); + + let mut key_user = keys.get_key_of_user(sender_address); + println!("test key_user total supply {:?}", key_user.total_supply); + + // Buy others key + stop_cheat_caller_address(keys.contract_address); + + let amount_key_buy = 3_u256; + + // println!("all_keys {:?}", all_keys); + let amount_to_paid = keys + .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(keys.contract_address, amount_to_paid); + + let allowance = erc20.allowance(sender_address, keys.contract_address); + println!("test allowance {}", allowance); + stop_cheat_caller_address(erc20.contract_address); + + start_cheat_caller_address(keys.contract_address, sender_address); + keys.buy_keys(sender_address, amount_key_buy); + let mut key_user = keys.get_key_of_user(sender_address); + + println!("test key_user total supply {:?}", key_user.total_supply); + } +} + diff --git a/onchain/src/tests/launchpad.cairo b/onchain/src/tests/launchpad.cairo deleted file mode 100644 index 8b137891..00000000 --- a/onchain/src/tests/launchpad.cairo +++ /dev/null @@ -1 +0,0 @@ - diff --git a/onchain/src/tests/launchpad_tests.cairo b/onchain/src/tests/launchpad_tests.cairo new file mode 100644 index 00000000..ec0b9ef4 --- /dev/null +++ b/onchain/src/tests/launchpad_tests.cairo @@ -0,0 +1,440 @@ + + + +#[cfg(test)] +mod launchpad_tests { + use afk::erc20::{ERC20, IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use afk::types::launchpad_types::{MINTER_ROLE, ADMIN_ROLE, TokenQuoteBuyKeys, BondingType}; + use core::array::SpanTrait; + use core::num::traits::Zero; + use core::traits::Into; + use openzeppelin::account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; + use openzeppelin::utils::serde::SerializedAppend; + + use snforge_std::{ + declare, ContractClass, ContractClassTrait, spy_events, SpyOn, EventSpy, EventFetcher, + Event, EventAssertions, start_cheat_caller_address, cheat_caller_address_global, + stop_cheat_caller_address, stop_cheat_caller_address_global, start_cheat_block_timestamp + }; + use starknet::syscalls::deploy_syscall; + + // const INITIAL_KEY_PRICE:u256=1/100; + + use starknet::{ + ContractAddress, get_caller_address, storage_access::StorageBaseAddress, + get_block_timestamp, get_contract_address, ClassHash + }; + use afk::launchpad::launchpad::{ILaunchpadMarketplaceDispatcher, ILaunchpadMarketplaceDispatcherTrait}; + + fn DEFAULT_INITIAL_SUPPLY() -> u256 { + // 21_000_000 * pow_256(10, 18) + 100_000_000 + // * pow_256(10, 18) + } + + // const INITIAL_KEY_PRICE:u256=1/100; + const INITIAL_SUPPLY_DEFAULT: u256 = 100_000_000; + const INITIAL_KEY_PRICE: u256 = 1 / 10_000; + const STEP_LINEAR_INCREASE: u256 = 1; + // const THRESHOLD_LIQUIDITY: u256 = 10; + const THRESHOLD_LIQUIDITY: u256 = 10_000; + const THRESHOLD_MARKET_CAP: u256 = 50_000; + const RATIO_SUPPLY_LAUNCH: u256 = 5; + const LIQUIDITY_SUPPLY: u256 = INITIAL_SUPPLY_DEFAULT / RATIO_SUPPLY_LAUNCH; + const BUYABLE: u256 = INITIAL_SUPPLY_DEFAULT / RATIO_SUPPLY_LAUNCH; + + + fn SALT() -> felt252 { + 'salty'.try_into().unwrap() + } + + // Constants + fn OWNER() -> ContractAddress { + // 'owner'.try_into().unwrap() + 123.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 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 launchpad = deploy_launchpad( + launch_class, + sender_address, + token_address.clone(), + INITIAL_KEY_PRICE, + STEP_LINEAR_INCREASE, + erc20_class.class_hash, + THRESHOLD_LIQUIDITY, + THRESHOLD_MARKET_CAP + ); + // let launchpad = deploy_launchpad( + // launch_class, + // sender_address, + // token_address.clone(), + // INITIAL_KEY_PRICE * pow_256(10,18), + // // INITIAL_KEY_PRICE, + // // STEP_LINEAR_INCREASE, + // STEP_LINEAR_INCREASE * pow_256(10,18), + // erc20_class.class_hash, + // THRESHOLD_LIQUIDITY * pow_256(10,18), + // // THRESHOLD_LIQUIDITY, + // THRESHOLD_MARKET_CAP * pow_256(10,18), + // // THRESHOLD_MARKET_CAP + // ); + (sender_address, erc20, launchpad) + } + + fn deploy_launchpad( + class: ContractClass, + admin: ContractAddress, + token_address: ContractAddress, + initial_key_price: u256, + step_increase_linear: u256, + coin_class_hash: ClassHash, + threshold_liquidity: u256, + threshold_marketcap: u256, + ) -> ILaunchpadMarketplaceDispatcher { + // println!("deploy marketplace"); + let mut calldata = array![admin.into()]; + calldata.append_serde(initial_key_price); + calldata.append_serde(token_address); + calldata.append_serde(step_increase_linear); + calldata.append_serde(coin_class_hash); + calldata.append_serde(threshold_liquidity); + calldata.append_serde(threshold_marketcap); + let (contract_address, _) = class.deploy(@calldata).unwrap(); + ILaunchpadMarketplaceDispatcher { contract_address } + } + + fn declare_launchpad() -> ContractClass { + declare("LaunchpadMarketplace").unwrap() + } + + fn declare_erc20() -> ContractClass { + declare("ERC20").unwrap() + } + + + 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 run_buy( + launchpad: ILaunchpadMarketplaceDispatcher, + erc20: IERC20Dispatcher, + memecoin: IERC20Dispatcher, + amount_key_buy: u256, + token_address: ContractAddress, + sender_address: ContractAddress, + ) { + let amount_to_paid = launchpad + .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, + ); + println!("test amount_to_paid erc20 {:?}", amount_to_paid); + + start_cheat_caller_address(erc20.contract_address, sender_address); + + erc20.approve(launchpad.contract_address, amount_to_paid); + + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance erc20 {}", allowance); + stop_cheat_caller_address(erc20.contract_address); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + println!("buy coin",); + + let allowance = memecoin.allowance(sender_address, launchpad.contract_address); + println!("test allowance meme coin{}", allowance); + + launchpad.buy_coin(token_address, amount_key_buy); + } + + + + fn run_buy_by_amount( + launchpad: ILaunchpadMarketplaceDispatcher, + erc20: IERC20Dispatcher, + memecoin: IERC20Dispatcher, + amount_quote: u256, + token_address: ContractAddress, + sender_address: ContractAddress, + ) { + let amount_to_paid = launchpad + .get_price_of_supply_key(token_address, amount_quote, false, // 1, + ); + println!("test amount_to_paid erc20 {:?}", amount_to_paid); + + start_cheat_caller_address(erc20.contract_address, sender_address); + + erc20.approve(launchpad.contract_address, amount_to_paid); + + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance erc20 {}", allowance); + stop_cheat_caller_address(erc20.contract_address); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + println!("buy coin",); + + let allowance = memecoin.allowance(sender_address, launchpad.contract_address); + println!("test allowance meme coin{}", allowance); + + launchpad.buy_coin_by_quote_amount(token_address, amount_quote); + } + + #[test] + fn launchpad_buy_with_amount() { + println!("launchpad_enter_end_to_end"); + let (sender_address, erc20, launchpad) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // Call a view function of the contract + // Check default token used + let default_token = launchpad.get_default_token(); + assert(default_token.token_address == erc20.contract_address, 'no default token'); + assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + + println!("create and launch token"); + let token_address = launchpad + .create_and_launch_token( + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), + ); + println!("test token_address {:?}", token_address); + + let memecoin = IERC20Dispatcher { contract_address: token_address }; + + // Final buy + let res = run_buy_by_amount( + launchpad, erc20, memecoin, THRESHOLD_LIQUIDITY, token_address, sender_address, + ); + + } + + + #[test] + fn launchpad_end_to_end() { + println!("launchpad_enter_end_to_end"); + let (sender_address, erc20, launchpad) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // Call a view function of the contract + // Check default token used + let default_token = launchpad.get_default_token(); + assert(default_token.token_address == erc20.contract_address, 'no default token'); + assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + + println!("create and launch token"); + let token_address = launchpad + .create_and_launch_token( + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), + ); + println!("test token_address {:?}", token_address); + + let memecoin = IERC20Dispatcher { contract_address: token_address }; + + // Final buy + let res = run_buy( + launchpad, erc20, memecoin, INITIAL_SUPPLY_DEFAULT - LIQUIDITY_SUPPLY, token_address, sender_address, + ); + + } + + #[test] + fn launchpad_all_coin() { + println!("launchpad_enter_end_to_end"); + let (sender_address, erc20, launchpad) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // Call a view function of the contract + // Check default token used + let default_token = launchpad.get_default_token(); + assert(default_token.token_address == erc20.contract_address, 'no default token'); + assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + + println!("create and launch token"); + let token_address = launchpad + .create_and_launch_token( + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), + ); + println!("test token_address {:?}", token_address); + + let memecoin = IERC20Dispatcher { contract_address: token_address }; + + // Final buy + let res = run_buy( + launchpad, erc20, memecoin, LIQUIDITY_SUPPLY, token_address, sender_address, + ); + + + } + + + #[test] + fn launchpad_integration() { + println!("launchpad_integration"); + + let (sender_address, erc20, launchpad) = request_fixture(); + let amount_key_buy = 1_u256; + cheat_caller_address_global(sender_address); + start_cheat_caller_address(erc20.contract_address, sender_address); + // Call a view function of the contract + // Check default token used + let default_token = launchpad.get_default_token(); + assert(default_token.token_address == erc20.contract_address, 'no default token'); + assert(default_token.initial_key_price == INITIAL_KEY_PRICE, 'no init price'); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + + let token_address = launchpad + .create_token( + recipient: OWNER(), + // owner: OWNER(), + symbol: SYMBOL(), + name: NAME(), + initial_supply: DEFAULT_INITIAL_SUPPLY(), + contract_address_salt: SALT(), + ); + println!("test token_address {:?}", token_address); + + let memecoin = IERC20Dispatcher { contract_address: token_address }; + start_cheat_caller_address(memecoin.contract_address, sender_address); + + let balance_contract = memecoin.balance_of(launchpad.contract_address); + println!("test balance_contract {:?}", balance_contract); + + let total_supply = memecoin.total_supply(); + println!(" memecoin total_supply {:?}", total_supply); + memecoin.approve(launchpad.contract_address, total_supply); + + let allowance = memecoin.allowance(sender_address, launchpad.contract_address); + println!("test allowance meme coin{}", allowance); + memecoin.transfer(launchpad.contract_address, total_supply); + + + // Launch coin pool + // Send total supply + println!("launch token"); + launchpad.launch_token(token_address); + // Test buy coin + println!("amount_to_paid",); + // println!("all_keys {:?}", all_keys); + let amount_key_buy = 100_u256; + + let amount_to_paid = launchpad + .get_price_of_supply_key(token_address, amount_key_buy, false, // 1, + // BondingType::Basic, default_token.clone() + ); + println!("test amount_to_paid {:?}", amount_to_paid); + + start_cheat_caller_address(erc20.contract_address, sender_address); + + erc20.approve(launchpad.contract_address, amount_to_paid); + + let allowance = erc20.allowance(sender_address, launchpad.contract_address); + println!("test allowance {}", allowance); + stop_cheat_caller_address(erc20.contract_address); + + start_cheat_caller_address(launchpad.contract_address, sender_address); + println!("buy coin",); + + launchpad.buy_coin(token_address, amount_key_buy); + + run_buy( + launchpad, erc20, memecoin, LIQUIDITY_SUPPLY, token_address, sender_address, + ); + + } +} diff --git a/onchain/src/utils.cairo b/onchain/src/utils.cairo index e15c182e..5d0af131 100644 --- a/onchain/src/utils.cairo +++ b/onchain/src/utils.cairo @@ -193,3 +193,15 @@ pub fn is_valid_stark_signature( false } } + +pub fn sqrt(y:u256) -> u256 { + let mut z = (y+1)/2; + let mut x=y; + + while z < x { + x=z; + z=(y/z+z)/2; + }; + + x +} \ No newline at end of file From a87c6def7616aa2c21a05b581e2483488c1f6b97 Mon Sep 17 00:00:00 2001 From: MSGhais Date: Fri, 16 Aug 2024 13:29:43 +0200 Subject: [PATCH 8/8] typo --- apps/mobile/src/app/Router.tsx | 4 ++-- apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts | 4 ++-- apps/mobile/src/screens/Settings/index.tsx | 2 +- onchain/src/launchpad/launchpad.cairo | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/mobile/src/app/Router.tsx b/apps/mobile/src/app/Router.tsx index 30aa60f1..24ad3c85 100644 --- a/apps/mobile/src/app/Router.tsx +++ b/apps/mobile/src/app/Router.tsx @@ -29,7 +29,7 @@ import { Games } from '../screens/Games'; import { useAuth } from 'afk_nostr_sdk'; import { createDrawerNavigator } from '@react-navigation/drawer'; import { Navbar } from '../components/Navbar'; -import { Setttings } from '../screens/Settings'; +import { Settings } from '../screens/Settings'; const DrawerStack = createDrawerNavigator(); const RootStack = createNativeStackNavigator(); @@ -203,7 +203,7 @@ const MainNavigator: React.FC = () => { - + ); }; diff --git a/apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts b/apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts index ac3c88c0..ba257118 100644 --- a/apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts +++ b/apps/mobile/src/hooks/launchpad/useQueryAllLaunch.ts @@ -14,8 +14,8 @@ export const useQueryAllLaunch = () => { queryKey: ['get_all_launch', CHAIN_ID], queryFn:async () => { - const launchs= await getAllLaunch() - return launchs + const launches= await getAllLaunch() + return launches }, placeholderData:[] }) diff --git a/apps/mobile/src/screens/Settings/index.tsx b/apps/mobile/src/screens/Settings/index.tsx index b026842c..e650ad2d 100644 --- a/apps/mobile/src/screens/Settings/index.tsx +++ b/apps/mobile/src/screens/Settings/index.tsx @@ -12,7 +12,7 @@ import { HeaderScreen } from '../../components/HeaderScreen'; import { useAuth, useSettingsStore } from 'afk_nostr_sdk'; import { AFK_RELAYS } from 'afk_nostr_sdk/src/utils/relay'; -export const Setttings: React.FC = ({ navigation }) => { +export const Settings: React.FC = ({ navigation }) => { const styles = useStyles(stylesheet); const { showToast } = useToast(); const { theme, toggleTheme } = useTheme(); diff --git a/onchain/src/launchpad/launchpad.cairo b/onchain/src/launchpad/launchpad.cairo index 1988cfce..20775261 100644 --- a/onchain/src/launchpad/launchpad.cairo +++ b/onchain/src/launchpad/launchpad.cairo @@ -904,7 +904,7 @@ mod LaunchpadMarketplace { ); } - // TODO add liquidity to Ekubo, Jediswap and others exhanges enabled + // TODO add liquidity to Ekubo, Jediswap and others exchanges enabled fn _add_liquidity(ref self: ContractState, coin_address: ContractAddress) {} // Function to calculate the price for the next token to be minted