feat: can create subscription record

This commit is contained in:
0xzio
2024-08-21 13:27:00 +08:00
parent 9a8b66eb6f
commit 863775fa8f
41 changed files with 893 additions and 909 deletions

View File

@@ -1,38 +0,0 @@
'use client'
import { Badge } from '@/components/ui/badge'
import { UserAvatar } from '@/components/UserAvatar'
import { useHolders } from '@/hooks/useHolders'
import { PostWithSpace } from '@/hooks/usePost'
import { cn } from '@/lib/utils'
interface Props {
post: PostWithSpace
}
export function AvatarList({ post }: Props) {
const { holders, isLoading } = useHolders(post.id)
if (isLoading) return null
return (
<div className="flex items-center cursor-pointer relative">
<div className="flex z-1">
{holders.slice(0, 4).map((item) => (
<UserAvatar
key={item.id}
user={item.user as any}
className={cn('w-8 h-8 -ml-2 border-2 border-white')}
/>
))}
</div>
<Badge
className="-ml-2 z-10 text-neutral-500"
size="sm"
variant="secondary"
>
{holders.length} holders
</Badge>
</div>
)
}

View File

@@ -1,54 +0,0 @@
'use client'
import { Skeleton } from '@/components/ui/skeleton'
import { UserAvatar } from '@/components/UserAvatar'
import { useHolders } from '@/hooks/useHolders'
import { PostWithSpace } from '@/hooks/usePost'
import { precision } from '@/lib/math'
import { getEnsAvatar, shortenAddress } from '@/lib/utils'
import { Space } from '@prisma/client'
interface Props {
space: Space
post: PostWithSpace
}
export function HolderList({ space, post }: Props) {
const { holders, isLoading } = useHolders(post.id)
if (isLoading) {
return (
<div className="grid gap-2">
{Array(10)
.fill('')
.map((_, index) => (
<Skeleton key={index} className="h-8" />
))}
</div>
)
}
if (holders.length === 0) {
return (
<div className="flex flex-col items-center text-gray-600 text-sm">
No holders yet
</div>
)
}
return (
<div className="space-y-3 mt-4">
{holders.map((item) => (
<div key={item.id} className="flex justify-between">
<div className="flex gap-2 items-center">
<UserAvatar className="w-7 h-7" user={item.user as any} />
<div>{item.user.ensName || shortenAddress(item.user.address)}</div>
</div>
<div>
<span className="font-bold">{item.amount}</span> Keys
</div>
</div>
))}
</div>
)
}

View File

@@ -1,48 +0,0 @@
'use client'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { PostWithSpace } from '@/hooks/usePost'
import { Space } from '@prisma/client'
import { AvatarList } from './AvatarList'
import { HolderList } from './HolderList'
import { PostTradeList } from './PostTradeList'
interface Props {
space: Space
post: PostWithSpace
}
export function PostTradeModal({ space, post }: Props) {
return (
<Dialog>
<DialogTrigger>
<AvatarList post={post} />
</DialogTrigger>
<DialogContent className="w-[700px] sm:w-[740px] min-h-[60vh]">
<DialogHeader>
{/* <DialogTitle>#</DialogTitle> */}
<Tabs defaultValue="keys" className="">
<TabsList className="flex w-full">
<TabsTrigger value="keys">Key Holders</TabsTrigger>
<TabsTrigger value="trades">Trade History</TabsTrigger>
</TabsList>
<div className="pt-5">
<TabsContent value="keys">
<HolderList space={space} post={post} />
</TabsContent>
<TabsContent value="trades">
<PostTradeList post={post} />
</TabsContent>
</div>
</Tabs>
</DialogHeader>
</DialogContent>
</Dialog>
)
}

View File

@@ -1,16 +0,0 @@
'use client'
import { Badge } from '@/components/ui/badge'
import { UserAvatar } from '@/components/UserAvatar'
import { PostWithSpace } from '@/hooks/usePost'
import { usePostTrades } from '@/hooks/usePostTrades'
import { precision } from '@/lib/math'
import { cn, shortenAddress } from '@/lib/utils'
interface Props {
post: PostWithSpace
}
export function PostTradeList({ post }: Props) {
return <div className="space-y-3 mt-4"></div>
}

View File

@@ -1,8 +0,0 @@
import { atom, useAtom } from 'jotai'
const tradeDialogAtom = atom<boolean>(false)
export function useTradeDialog() {
const [isOpen, setIsOpen] = useAtom(tradeDialogAtom)
return { isOpen, setIsOpen }
}

View File

@@ -13,7 +13,6 @@ import { handlers } from './handlers'
import { ImageCreation } from './ImageCreation'
import { PostActionBar } from './PostActionBar'
import { PostCreation } from './PostCreation'
import { PostTradeModal } from './PostTradeDialog/PostTradeDialog'
import { PromotionCard } from './PromotionCard'
export const dynamic = 'force-dynamic'

View File

@@ -4,10 +4,10 @@ import { ReactNode } from 'react'
import { NavbarWrapper } from '@/components/Navbar/NavbarWrapper'
import { useChainSpace, useQueryChainSpace } from '@/hooks/useChainSpace'
import { SpaceInfo } from '../Space/SpaceInfo'
import { TradeList } from '../Space/TradeList'
import { Transaction } from '../Transaction'
export default function Layout({ children }: { children: ReactNode }) {
const { isLoading, data } = useQueryChainSpace()
const { space } = useChainSpace()
if (!space) return null
@@ -21,7 +21,11 @@ export default function Layout({ children }: { children: ReactNode }) {
{children}
</div>
<Transaction />
<div className="w-[360px]">
<Transaction />
<div className="text-base font-bold mt-8">Trades</div>
<TradeList />
</div>
</div>
</div>
)

View File

@@ -1,10 +1,10 @@
'use client'
import { useSpace } from '@/hooks/useSpace'
import { SpaceTradeList } from '../../Space/SpaceTradeList'
import { SubscriptionRecordList } from '../../Space/SubscriptionRecordList'
export default function Page() {
const { space } = useSpace()
if (!space) return
return <SpaceTradeList space={space} />
return <SubscriptionRecordList space={space} />
}

View File

@@ -3,6 +3,7 @@
import { MemberDialog } from '@/components/MemberDialog/MemberDialog'
import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'
import { UpdatePriceDialog } from '@/components/UpdatePriceDialog/UpdatePriceDialog'
import { useSpace } from '@/hooks/useSpace'
import { cn } from '@/lib/utils'
import { useSession } from 'next-auth/react'
@@ -11,7 +12,6 @@ import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { ClaimShareRewards } from './ClaimShareRewards'
import { MemberButton } from './MemberButton'
import { UpdatePriceDialog } from '@/components/UpdatePriceDialog/UpdatePriceDialog'
interface Props {}
@@ -97,7 +97,7 @@ export function SpaceInfo({}: Props) {
<div className="">
<div className="flex gap-2">
<MemberDialog space={space} />
<UpdatePriceDialog/>
<UpdatePriceDialog />
<MemberButton />
</div>
</div>
@@ -118,7 +118,7 @@ export function SpaceInfo({}: Props) {
</Link> */}
<Link href={Paths.trades} className={linkClassName(Paths.trades)}>
Trades
Activities
</Link>
</div>
</div>

View File

@@ -3,7 +3,8 @@
import { Badge } from '@/components/ui/badge'
import { UserAvatar } from '@/components/UserAvatar'
import { SECONDS_PER_DAY } from '@/domains/Space'
import { useSpaceTrades } from '@/hooks/useSpaceTrades'
import { useSubscriptionRecords } from '@/hooks/useSpaceTrades'
import { SubscriptionType } from '@/lib/constants'
import { precision } from '@/lib/math'
import { cn, getEnsAvatar, shortenAddress } from '@/lib/utils'
import { Space } from '@prisma/client'
@@ -12,12 +13,12 @@ interface Props {
space: Space
}
export function SpaceTradeList({ space }: Props) {
const { trades } = useSpaceTrades(space.id)
export function SubscriptionRecordList({ space }: Props) {
const { records } = useSubscriptionRecords(space.id)
return (
<div className="space-y-3 mt-4">
{trades.map((item) => (
{records.map((item) => (
<div key={item.id} className="flex items-center gap-2">
<div className="flex gap-2 items-center">
<UserAvatar user={item.user as any} />
@@ -30,11 +31,13 @@ export function SpaceTradeList({ space }: Props) {
<div>
<Badge
className={cn(
item.type === 'BUY' && 'bg-green-500',
item.type === 'SELL' && 'bg-red-500',
item.type === SubscriptionType.SUBSCRIBE && 'bg-green-500',
item.type === SubscriptionType.UNSUBSCRIBE && 'bg-red-500',
)}
>
{item.type === 'BUY' ? 'Subscribe' : 'Unsubscribe'}
{item.type === SubscriptionType.SUBSCRIBE
? 'Subscribe'
: 'Unsubscribe'}
</Badge>
</div>
<div>

View File

@@ -0,0 +1,52 @@
'use client'
import { UserAvatar } from '@/components/UserAvatar'
import { useSpace } from '@/hooks/useSpace'
import { useTrades } from '@/hooks/useTrades'
import { TradeType } from '@/lib/constants'
import { precision } from '@/lib/math'
import { cn } from '@/lib/utils'
interface Props {}
export function TradeList({}: Props) {
const { space } = useSpace()
const { records: trades } = useTrades()
return (
<div className="space-y-3 mt-4">
{trades.map((item) => (
<div key={item.id} className="flex items-center gap-2 text-sm">
<div className="flex gap-2 items-center">
<UserAvatar className="w-6 h-6" user={item.user as any} />
<div className="text-sm">
{item.user.ensName
? item.user.ensName
: item.user.address.slice(0, 5)}
</div>
</div>
<div>
<div
className={cn(
item.type === TradeType.BUY && 'text-green-500',
item.type === TradeType.SELL && 'text-red-500',
)}
>
{item.type === TradeType.BUY ? 'Bought' : 'sold'}
</div>
</div>
<div>
<span className="font-bold">
{precision
.toDecimal(
item.type === TradeType.BUY ? item.amountOut : item.amountIn,
)
.toFixed(2)}{' '}
</span>
<span>{space.symbolName}</span>
</div>
</div>
))}
</div>
)
}

View File

@@ -1,88 +1,65 @@
'use client'
import { useEffect, useMemo, useState } from 'react';
import { useAddress } from '@/hooks/useAddress';
import { useTokenKxy } from '@/hooks/useTokenKxy';
import { precision } from '@/lib/math';
import { Address } from 'viem';
import { useAccount, useBalance } from 'wagmi';
import { useSpace } from "@/hooks/useSpace"
import { useSpaceTokenBalance } from '@/components/spaceToken/hooks/useSpaceTokenBalance';
import { Buy } from '@/components/spaceToken/Buy';
import { Sell } from '@/components/spaceToken/Sell';
import { useEffect, useMemo, useState } from 'react'
import { BuyPanel } from '@/components/spaceToken/BuyPanel'
import { SellPanel } from '@/components/spaceToken/SellPanel'
import { useAddress } from '@/hooks/useAddress'
import { useSpace } from '@/hooks/useSpace'
import { precision } from '@/lib/math'
import { Address } from 'viem'
import { useAccount, useBalance } from 'wagmi'
enum Direction {
buy = 1,
sell = 2
sell = 2,
}
export function Transaction() {
const { space } = useSpace()
const address = useAddress()
const { isConnected } = useAccount()
const { data: balanceData } = useBalance({ address })
const { isLoading, data: tokenBalance } = useSpaceTokenBalance()
const [direction, setDirection] = useState<Direction>(Direction.buy)
const { updateTokenKxy } = useTokenKxy()
const ethBalance = useMemo<string>(() => {
if (balanceData?.value) {
// Numerical precision issues: precision.toDecimal(tokenBalance).toString()
return precision.toExactDecimalString(balanceData?.value)
}
return '0.00'
}, [balanceData])
const onSwitch = (direction: Direction) => {
setDirection(direction)
}
useEffect(() => {
if (space) {
updateTokenKxy(space.spaceAddress as Address)
}
}, [space])
return (
<div className="w-[350px]">
<div className="p-4 rounded-lg border border-neutral-100">
<div className="rounded-lg">
<div className='flex mb-[12px]'>
<div className="flex mb-[12px]">
<button
onClick={() => onSwitch(Direction.buy)}
className={`mr-[10px] text-[#222222] py-[6px] px-[16px] rounded-[16px] ${direction === Direction.buy ? 'bg-[#22222212]' : ''
}`}
className={`mr-[10px] text-[#222222] py-[6px] px-[16px] rounded-[16px] ${
direction === Direction.buy ? 'bg-[#22222212]' : ''
}`}
>
Buy
</button>
<button
onClick={() => onSwitch(Direction.sell)}
className={`py-[6px] px-[16px] rounded-[16px] ${direction === Direction.sell ? 'bg-[#22222212]' : '' }`}
className={`py-[6px] px-[16px] rounded-[16px] ${
direction === Direction.sell ? 'bg-[#22222212]' : ''
}`}
>
Sell
</button>
</div>
<div style={{
display: direction === Direction.buy ? 'block' : 'none'
}}>
<Buy
tokenBalance={tokenBalance}
ethBalance={ethBalance}
isConnected={isConnected}
space={space}
/>
<div
style={{
display: direction === Direction.buy ? 'block' : 'none',
}}
>
<BuyPanel isConnected={isConnected} space={space} />
</div>
<div style={{
display: direction === Direction.sell ? 'block' : 'none'
}}>
<Sell
tokenBalance={tokenBalance}
ethBalance={ethBalance}
isConnected={isConnected}
space={space}
/>
<div
style={{
display: direction === Direction.sell ? 'block' : 'none',
}}
>
<SellPanel isConnected={isConnected} space={space} />
</div>
</div>
</div>

View File

@@ -2,6 +2,7 @@
import { PropsWithChildren } from 'react'
import { useAddress } from '@/hooks/useAddress'
import { useQueryChainSpace } from '@/hooks/useChainSpace'
import { useQueryEthBalance } from '@/hooks/useEthBalance'
import { useQueryEthPrice } from '@/hooks/useEthPrice'
import { CreateSpaceDialog } from '../CreateSpaceDialog/CreateSpaceDialog'
@@ -11,6 +12,7 @@ import { Sidebar } from './Sidebar/Sidebar'
export function DashboardLayout({ children }: PropsWithChildren) {
useQueryEthPrice()
useQueryEthBalance()
useQueryChainSpace()
return (
<div className="mx-auto h-screen">

View File

@@ -24,47 +24,66 @@ export function SpaceMenu() {
pathname === '/~/create-space'
if (!space || isNotSpace) return null
return (
<div className="relative">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="flex items-center justify-between px-2 hover:bg-sidebar/50 cursor-pointer font-semibold h-10 w-[240px] rounded-lg mt-0.5">
<div className="flex items-center gap-2">
<Image
src={
space.logo! ||
'https://public.blob.vercel-storage.com/eEZHAoPTOBSYGBE3/JRajRyC-PhBHEinQkupt02jqfKacBVHLWJq7Iy.png'
}
alt=""
width={24}
height={24}
className="w-6 h-6 rounded-full"
/>
<div className="flex items-center justify-between px-2 hover:bg-sidebar/50 cursor-pointer font-semibold h-10 w-[240px] rounded-lg mt-0.5">
<div className="flex items-center gap-2">
<Image
src={
space.logo! ||
'https://public.blob.vercel-storage.com/eEZHAoPTOBSYGBE3/JRajRyC-PhBHEinQkupt02jqfKacBVHLWJq7Iy.png'
}
alt=""
width={24}
height={24}
className="w-6 h-6 rounded-full"
/>
<div>{space.name}</div>
<ChevronDown size={16} className="text-neutral-600" />
</div>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
alignOffset={10}
className="w-[200px]"
>
<DropdownMenuItem
key={space.id}
className="cursor-pointer flex gap-2 items-center"
disabled={space.userId !== session?.userId}
onClick={() => {
push(`/~/space/${space.id}/settings`)
}}
>
<Settings size={18} className="inline-flex" />
<div className="">Space settings</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div>{space.name}</div>
{/* <ChevronDown size={16} className="text-neutral-600" /> */}
</div>
</div>
)
// return (
// <div className="relative">
// <DropdownMenu>
// <DropdownMenuTrigger asChild>
// <div className="flex items-center justify-between px-2 hover:bg-sidebar/50 cursor-pointer font-semibold h-10 w-[240px] rounded-lg mt-0.5">
// <div className="flex items-center gap-2">
// <Image
// src={
// space.logo! ||
// 'https://public.blob.vercel-storage.com/eEZHAoPTOBSYGBE3/JRajRyC-PhBHEinQkupt02jqfKacBVHLWJq7Iy.png'
// }
// alt=""
// width={24}
// height={24}
// className="w-6 h-6 rounded-full"
// />
// <div>{space.name}</div>
// <ChevronDown size={16} className="text-neutral-600" />
// </div>
// </div>
// </DropdownMenuTrigger>
// <DropdownMenuContent
// align="start"
// alignOffset={10}
// className="w-[200px]"
// >
// <DropdownMenuItem
// key={space.id}
// className="cursor-pointer flex gap-2 items-center"
// disabled={space.userId !== session?.userId}
// onClick={() => {
// push(`/~/space/${space.id}/settings`)
// }}
// >
// <Settings size={18} className="inline-flex" />
// <div className="">Space settings</div>
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// </div>
// )
}

View File

@@ -3,17 +3,14 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { useQueryEthBalance } from '@/hooks/useEthBalance'
import { RouterOutputs } from '@/server/_app'
import { useMutation } from '@tanstack/react-query'
import { ProfileAvatar } from '../Profile/ProfileAvatar'
import { AmountInput } from './AmountInput'
import { MemberForm } from './MemberForm'
import { useMemberDialog } from './useMemberDialog'
import { useSubscribe } from './useSubscribe'
interface Props {
space: RouterOutputs['space']['byId']
@@ -26,6 +23,7 @@ export function MemberDialog({ space }: Props) {
return (
<Dialog open={isOpen} onOpenChange={(v) => setIsOpen(v)}>
<DialogContent className="sm:max-w-[425px]">
<DialogDescription></DialogDescription>
<DialogHeader>
<DialogTitle>Subscription</DialogTitle>
<div className="text-sm text-neutral-600">

View File

@@ -19,7 +19,7 @@ import { useChainSpace } from '@/hooks/useChainSpace'
import { useQueryEthBalance } from '@/hooks/useEthBalance'
import { useSubscription } from '@/hooks/useSubscription'
import { useTokenBalance } from '@/hooks/useTokenBalance'
import { TradeType } from '@/lib/constants'
import { SubscriptionType, TradeType } from '@/lib/constants'
import { precision } from '@/lib/math'
import { RouterOutputs } from '@/server/_app'
import { zodResolver } from '@hookform/resolvers/zod'
@@ -45,7 +45,7 @@ const FormSchema = z.object({
})
export function MemberForm({ space }: Props) {
const { isOpen, setIsOpen } = useMemberDialog()
const { setIsOpen } = useMemberDialog()
const [loading, setLoading] = useState(false)
useQueryEthBalance()
const trade = useSubscribe(space)
@@ -56,13 +56,13 @@ export function MemberForm({ space }: Props) {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
type: TradeType.BUY,
type: SubscriptionType.SUBSCRIBE,
token: 'ETH',
times: '180',
times: '180', // 180 days by default
},
})
const isSubscribe = form.watch('type') === 'BUY'
const isSubscribe = form.watch('type') === SubscriptionType.SUBSCRIBE
const token = form.watch('token')
const times = form.watch('times')
@@ -86,7 +86,7 @@ export function MemberForm({ space }: Props) {
}, [isSubscribe, token, space.symbolName])
async function onSubmit(data: z.infer<typeof FormSchema>) {
const isSubscribe = data.type === 'BUY'
const isSubscribe = data.type === SubscriptionType.SUBSCRIBE
setLoading(true)
const amount = getAmount(data.token, data.times, isSubscribe)
@@ -128,13 +128,13 @@ export function MemberForm({ space }: Props) {
>
<ToggleGroupItem
className="data-[state=on]:bg-white ring-black bg-accent text-sm font-semibold flex-1 h-full"
value={TradeType.BUY}
value={SubscriptionType.SUBSCRIBE}
>
Subscribe
</ToggleGroupItem>
<ToggleGroupItem
value={TradeType.SELL}
value={SubscriptionType.UNSUBSCRIBE}
className="data-[state=on]:bg-white ring-black bg-accent text-sm font-semibold flex-1 h-full"
>
Unsubscribe

View File

@@ -3,21 +3,18 @@ import { useMembers } from '@/hooks/useMembers'
import { refetchSpaces } from '@/hooks/useSpaces'
import { useSubscription } from '@/hooks/useSubscription'
import { spaceAbi } from '@/lib/abi'
import { TradeType } from '@/lib/constants'
import { SubscriptionType } from '@/lib/constants'
import { extractErrorMessage } from '@/lib/extractErrorMessage'
import { precision } from '@/lib/math'
import { api } from '@/lib/trpc'
import { wagmiConfig } from '@/lib/wagmi'
import { RouterOutputs } from '@/server/_app'
import { Post } from '@prisma/client'
import {
readContract,
waitForTransactionReceipt,
writeContract,
} from '@wagmi/core'
import { useSearchParams } from 'next/navigation'
import { toast } from 'sonner'
import { Address, isAddress, zeroAddress } from 'viem'
import { Address } from 'viem'
import { useWriteContract } from 'wagmi'
export function useSubscribe(space: RouterOutputs['space']['byId']) {
@@ -32,9 +29,18 @@ export function useSubscribe(space: RouterOutputs['space']['byId']) {
isSubscribe: boolean,
duration: number,
) => {
const tradeType = isSubscribe ? TradeType.BUY : TradeType.SELL
const subscriptionType = isSubscribe
? SubscriptionType.SUBSCRIBE
: SubscriptionType.UNSUBSCRIBE
try {
if (isSubscribe) {
console.log(
'=====amount:',
amount,
'space.spaceAddress:',
space.spaceAddress,
)
const hash = await writeContractAsync({
address: space.spaceAddress as Address,
abi: spaceAbi,
@@ -62,9 +68,8 @@ export function useSubscribe(space: RouterOutputs['space']['byId']) {
functionName: 'getSubscription',
args: [address],
})
console.log('========subscription.info:', info)
await api.trade.tradeSpaceKey.mutate({
await api.subscriptionRecord.upsertSubscription.mutate({
spaceId: space.id,
tradeDuration: duration,
start: Number(info.start),
@@ -73,7 +78,7 @@ export function useSubscribe(space: RouterOutputs['space']['byId']) {
amount: String(info.amount),
consumed: String(info.consumed),
type: tradeType,
type: subscriptionType,
})
await Promise.all([
@@ -82,9 +87,9 @@ export function useSubscribe(space: RouterOutputs['space']['byId']) {
refetchSpaces(),
])
toast.success('Buy Key successful!')
toast.success('Subscribe successful!')
} catch (error) {
console.log('=======>>>>error:', error)
console.log('=======>>>>error:', JSON.stringify(error))
const msg = extractErrorMessage(error)
toast.error(msg)
} finally {

View File

@@ -0,0 +1,47 @@
import { ReactNode } from 'react'
import { matchNumber } from '@/lib/utils'
interface Props {
symbolName: string
icon: ReactNode
disabled?: boolean
value: string
onChange: (value: string) => void
}
export const AmountInput = ({
symbolName,
icon,
value,
onChange,
disabled = false,
}: Props) => {
return (
<div className="flex items-center gap-1 h-9">
<input
type="text"
value={value}
disabled={disabled}
onChange={(e) => {
let value = e.target.value
if ((e.nativeEvent as any)?.data === '。') {
value = value.replace('。', '.')
}
if (!matchNumber(value, 8) && value.length) {
if (/^\.\d+$/.test(value)) {
onChange?.('0' + value)
e.preventDefault()
}
return
}
onChange(e.target.value)
}}
placeholder="0.0"
className="font-bold text-2xl text-black pl-0 w-full border-none focus:border-none outline-none bg-transparent h-full"
/>
{icon}
<span className="text-lg font-semibold">{symbolName}</span>
</div>
)
}

View File

@@ -1,139 +0,0 @@
import { ChangeEvent, useMemo, useState } from 'react'
import { useTokenKxy } from '@/hooks/useTokenKxy'
import { precision } from '@/lib/math'
import { Space } from '@prisma/client'
import { Address } from 'viem'
import { Button } from '../ui/button'
import { BuyBtn } from './BuyBtn'
import { formatAmount } from './hooks/useSpaceTokenBalance'
interface Props {
ethBalance: string
tokenBalance: bigint | undefined
isConnected: boolean
space: Space
}
export const Buy = ({
space,
ethBalance,
tokenBalance,
isConnected,
}: Props) => {
const [ethAmount, setEthAmount] = useState<string>('0')
const [purchasedAmount, setPurchasedAmount] = useState<string>('0')
const { updateTokenKxy, getBuyTokenAmount } = useTokenKxy()
const isAmountValid =
parseFloat(ethAmount) > 0 && parseFloat(purchasedAmount) > 0
const isInsufficientBalance = parseFloat(ethBalance) < parseFloat(ethAmount)
const validateAndSetEthAmount = (value: string) => {
// Validate and format input
if (/^\d*\.?\d*$/.test(value) && !value.startsWith('.')) {
const formattedValue = formatAmount(value)
setEthAmount(formattedValue)
const decimalAmount = getBuyTokenAmount(
precision.toExactDecimalBigint(value),
)
if (!Number(value) || !decimalAmount) {
setPurchasedAmount('')
} else {
setPurchasedAmount(precision.toExactDecimalString(decimalAmount))
}
}
}
const validateAndsetPurchasedAmount = (value: string) => {
// Validate and format input
// if (/^\d*\.?\d*$/.test(value) && !value.startsWith('.')) { }
}
const handleMax = () => {
setEthAmount(ethBalance)
const decimalAmount = getBuyTokenAmount(
precision.toExactDecimalBigint(ethBalance),
)
if (!ethAmount || !decimalAmount) {
setPurchasedAmount('')
} else {
setPurchasedAmount(precision.toExactDecimalString(decimalAmount))
}
}
const displayBalance = useMemo(() => {
if (tokenBalance) {
return Number(precision.toExactDecimalString(tokenBalance)).toFixed(4)
}
return '0.0000'
}, [tokenBalance])
return (
<>
<div className="mb-2 bg-gray-100 rounded-[16px] p-[16px] border border-transparent hover:border-[#18181b] transition-colors duration-300">
<div className="text-[12px]">Sell</div>
<div className="flex font-[600] items-center gap-1">
<input
type="text"
value={ethAmount}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
validateAndSetEthAmount(e.target.value)
}
placeholder="Amount in ETH"
className="p-2 font-[600] text-[24px] text-[#222222] pl-0 bg-gray-100 rounded w-full border-none focus:border-none outline-none"
/>
<img src="/eth.png" alt="ETH" className="w-[20px] h-auto" />
<span className="text-[18px]">ETH</span>
</div>
<div className="text-right text-[#222222]">
Balance: {ethBalance}
<Button
onClick={handleMax}
disabled={!ethBalance}
className="h-[20px] cursor-pointer text-white px-[4px] rounded ml-2"
>
Max
</Button>
</div>
</div>
<div className="mb-4 bg-gray-100 rounded-[16px] p-[16px] border border-transparent hover:border-[#18181b] transition-colors duration-300">
<div className="text-[12px]">Buy</div>
<div className="flex font-[600] items-center gap-1">
<input
type="text"
value={purchasedAmount}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
validateAndsetPurchasedAmount(e.target.value)
}
placeholder="Amount in PenX"
className="p-2 font-[500] text-[24px] text-[#222222] pl-0 bg-gray-100 rounded w-full border-none focus:border-none outline-none"
/>
<img
src="https://cryptologos.cc/logos/bitcoin-btc-logo.png"
alt="PenX"
className="w-[20px] h-auto"
/>
<span className="text-[18px]">{space?.name}</span>
</div>
<div className="text-right text-[#222222]">
Balance: {displayBalance}
</div>
</div>
<BuyBtn
ethAmount={ethAmount}
isConnected={isConnected}
handleSwap={() => {
setEthAmount('')
setPurchasedAmount('')
updateTokenKxy(space.spaceAddress as Address)
}}
isInsufficientBalance={isInsufficientBalance}
isAmountValid={isAmountValid}
space={space}
/>
</>
)
}

View File

@@ -1,6 +1,10 @@
import { useQueryEthBalance } from '@/hooks/useEthBalance'
import { useTrades } from '@/hooks/useTrades'
import { spaceAbi } from '@/lib/abi'
import { TradeType } from '@/lib/constants'
import { extractErrorMessage } from '@/lib/extractErrorMessage'
import { precision } from '@/lib/math'
import { api } from '@/lib/trpc'
import { wagmiConfig } from '@/lib/wagmi'
import { Space } from '@prisma/client'
import { waitForTransactionReceipt } from '@wagmi/core'
@@ -14,6 +18,7 @@ import { useSpaceTokenBalance } from './hooks/useSpaceTokenBalance'
interface Props {
ethAmount: string
purchasedAmount: string
handleSwap: () => void
isInsufficientBalance: boolean
isAmountValid: boolean
@@ -23,6 +28,7 @@ interface Props {
export const BuyBtn = ({
ethAmount,
purchasedAmount,
isInsufficientBalance,
isAmountValid,
handleSwap,
@@ -31,10 +37,12 @@ export const BuyBtn = ({
}: Props) => {
const { writeContractAsync, isPending } = useWriteContract()
const balance = useSpaceTokenBalance()
const { refetch: refetchEth } = useQueryEthBalance()
const trade = useTrades()
const onBuy = async () => {
try {
const value = precision.token(parseFloat(ethAmount), 18)
const value = precision.token(ethAmount, 18)
const hash = await writeContractAsync({
address: space.spaceAddress as Address,
abi: spaceAbi,
@@ -43,7 +51,18 @@ export const BuyBtn = ({
})
await waitForTransactionReceipt(wagmiConfig, { hash })
await balance.refetch()
await Promise.all([
api.trade.create.mutate({
spaceId: space.id,
type: TradeType.BUY,
amountIn: String(value),
amountOut: precision.token(purchasedAmount).toString(),
}),
balance.refetch(),
refetchEth(),
])
trade.refetch()
handleSwap()
toast.success(`${space?.name} bought successfully!`)
} catch (error) {
@@ -56,7 +75,7 @@ export const BuyBtn = ({
<>
{isConnected ? (
<Button
className="w-full h-[58px]"
className="w-full h-[50px]"
disabled={!isAmountValid || isInsufficientBalance || isPending}
onClick={() => onBuy()}
>

View File

@@ -0,0 +1,113 @@
import { useState } from 'react'
import { useChainSpace, useQueryChainSpace } from '@/hooks/useChainSpace'
import { useEthBalance } from '@/hooks/useEthBalance'
import { precision } from '@/lib/math'
import { toFloorFixed } from '@/lib/utils'
import { Space } from '@prisma/client'
import { Button } from '../ui/button'
import { AmountInput } from './AmountInput'
import { BuyBtn } from './BuyBtn'
import { EthBalance } from './EthBalance'
import { SpaceTokenBalance } from './SpaceTokenBalance'
interface Props {
isConnected: boolean
space: Space
}
export const BuyPanel = ({ space, isConnected }: Props) => {
const [ethAmount, setEthAmount] = useState<string>('')
const [purchasedAmount, setPurchasedAmount] = useState<string>('')
const { refetch } = useQueryChainSpace()
const { ethBalance } = useEthBalance()
const { space: chainSpace } = useChainSpace()
const isAmountValid =
parseFloat(ethAmount) > 0 && parseFloat(purchasedAmount) > 0
const isInsufficientBalance = ethBalance.valueDecimal < parseFloat(ethAmount)
const handleEthChange = (value: string) => {
setEthAmount(value)
if (!value) {
return setPurchasedAmount('')
}
const tokenAmountDecimal = precision.toDecimal(
chainSpace.getTokenAmount(precision.token(value)),
)
setPurchasedAmount(toFloorFixed(tokenAmountDecimal, 4).toString())
}
const validateAndSetPurchasedAmount = (value: string) => {
// Validate and format input
// if (/^\d*\.?\d*$/.test(value) && !value.startsWith('.')) { }
}
const handleMax = () => {
setEthAmount(toFloorFixed(ethBalance.valueDecimal, 6).toString())
const tokenAmountDecimal = precision.toDecimal(
chainSpace.getTokenAmount(ethBalance.value),
)
setPurchasedAmount(toFloorFixed(tokenAmountDecimal, 4).toString())
}
return (
<>
<div className="mb-2 bg-gray-100 rounded-xl p-4">
<div className="text-sm">Sell</div>
<AmountInput
symbolName="ETH"
icon={<img src="/eth.png" alt="ETH" className="w-5 h-auto" />}
value={ethAmount}
onChange={(value) => handleEthChange(value)}
/>
<div className="flex items-center justify-end gap-2 h-6">
<EthBalance></EthBalance>
<Button
onClick={handleMax}
disabled={!ethBalance}
className="h-6 cursor-pointer text-xs text-white rounded-md px-2"
>
Max
</Button>
</div>
</div>
<div className="mb-4 bg-gray-100 rounded-xl p-4">
<div className="text-sm">Buy</div>
<AmountInput
symbolName={space.symbolName}
disabled
icon={
<img
src={space.logo || ''}
alt={space.symbolName}
className="w-5 h-auto"
/>
}
value={purchasedAmount}
onChange={(value) => validateAndSetPurchasedAmount(value)}
/>
<div className="flex items-center justify-end gap-2 h-6">
<SpaceTokenBalance />
</div>
</div>
<BuyBtn
ethAmount={ethAmount}
purchasedAmount={purchasedAmount}
isConnected={isConnected}
handleSwap={() => {
setEthAmount('')
setPurchasedAmount('')
refetch()
}}
isInsufficientBalance={isInsufficientBalance}
isAmountValid={isAmountValid}
space={space}
/>
</>
)
}

View File

@@ -0,0 +1,17 @@
'use client'
import { useEthBalance } from '@/hooks/useEthBalance'
import { Skeleton } from '../ui/skeleton'
export const EthBalance = () => {
const { ethBalance } = useEthBalance()
if (!ethBalance.valueDecimal) return <Skeleton></Skeleton>
return (
<div className="flex items-center gap-1">
<span className="i-[iconoir--wallet-solid] w-5 h-5 bg-neutral-400"></span>
<div className="text-sm text-neutral-500">
{ethBalance.valueFormatted}
</div>
</div>
)
}

View File

@@ -1,140 +0,0 @@
import { ChangeEvent, useMemo, useState } from 'react'
import { Button } from '../ui/button'
import { SellBtn } from './SellBtn'
import { precision } from '@/lib/math'
import { useTokenKxy } from '@/hooks/useTokenKxy'
import { Address } from 'viem'
import { Space } from '@prisma/client'
import { formatAmount } from './hooks/useSpaceTokenBalance'
interface Props {
ethBalance: string
tokenBalance: bigint | undefined
isConnected: boolean
space: Space
}
export const Sell = ({ space, ethBalance, tokenBalance, isConnected }: Props) => {
const [ethAmount, setEthAmount] = useState<string>('0')
const [purchasedAmount, setPurchasedAmount] = useState<string>('0')
// const isAmountValid = parseFloat(ethAmount) > 0 && parseFloat(purchasedAmount) > 0
// TODO: please add eth judgment logic
const isAmountValid = parseFloat(purchasedAmount) > 0
const { updateTokenKxy, getSellEthAmount } = useTokenKxy()
const validateAndSetEthAmount = (value: string) => {
// Validate and format input
// if (/^\d*\.?\d*$/.test(value) && !value.startsWith('.')) { }
}
const validateAndsetPurchasedAmount = (value: string) => {
// Validate and format input
if (/^\d*\.?\d*$/.test(value) && !value.startsWith('.')) {
const formattedValue = formatAmount(value)
setPurchasedAmount(formattedValue)
const decimalAmount = getSellEthAmount(precision.toExactDecimalBigint(value))
if (!Number(value) || !decimalAmount) {
setEthAmount('')
} else {
setEthAmount(precision.toExactDecimalString(decimalAmount))
}
}
}
const handleMax = () => {
if (tokenBalance) {
setPurchasedAmount(precision.toExactDecimalString(tokenBalance))
const decimalAmount = getSellEthAmount(tokenBalance)
if (!ethAmount || !decimalAmount) {
setEthAmount('')
} else {
setEthAmount(precision.toExactDecimalString(decimalAmount))
}
}
}
const { decimalBalance, displayBalance } = useMemo(() => {
if (tokenBalance) {
const decimal = precision.toExactDecimalString(tokenBalance);
return {
decimalBalance: Number(decimal),
displayBalance: Number(decimal).toFixed(4),
};
}
return {
decimalBalance: 0,
displayBalance: '0.0000',
};
}, [tokenBalance]);
const isInsufficientBalance = decimalBalance < parseFloat(purchasedAmount)
return <>
<div className="mb-2 bg-gray-100 rounded-[16px] p-[16px] border border-transparent hover:border-[#18181b] transition-colors duration-300">
<div className="text-[12px]">Sell</div>
<div className="flex font-[600] items-center gap-1">
<input
type="text"
value={purchasedAmount}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
validateAndsetPurchasedAmount(e.target.value)
}
placeholder="Amount in PenX"
className="p-2 font-[500] text-[24px] text-[#222222] pl-0 bg-gray-100 rounded w-full border-none focus:border-none outline-none"
/>
<img
src="https://cryptologos.cc/logos/bitcoin-btc-logo.png"
alt="PenX"
className="w-[20px] h-auto"
/>
<span className="text-[18px]">{space?.name}</span>
</div>
<div className="text-right text-[#222222]">
Balance: {displayBalance}
<Button
onClick={handleMax}
disabled={decimalBalance <= 0}
className="h-[20px] cursor-pointer text-white px-[4px] rounded ml-2"
>
Max
</Button>
</div>
</div>
<div className="mb-4 bg-gray-100 rounded-[16px] p-[16px] border border-transparent hover:border-[#18181b] transition-colors duration-300">
<div className="text-[12px]">Buy</div>
<div className="flex font-[600] items-center gap-1">
<input
type="text"
value={ethAmount}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
validateAndSetEthAmount(e.target.value)
}
placeholder="Amount in ETH"
className="p-2 font-[600] text-[24px] text-[#222222] pl-0 bg-gray-100 rounded w-full border-none focus:border-none outline-none"
/>
<img src="/eth.png" alt="ETH" className="w-[20px] h-auto" />
<span className="text-[18px]">ETH</span>
</div>
<div className="text-right text-[#222222]">
Balance: {ethBalance}
</div>
</div>
<SellBtn
ethAmount={ethAmount}
purchasedAmount={purchasedAmount}
isConnected={isConnected}
handleSwap={() => {
setEthAmount('')
setPurchasedAmount('')
updateTokenKxy(space.spaceAddress as Address)
}}
isInsufficientBalance={isInsufficientBalance}
isAmountValid={isAmountValid}
space={space}
/>
</>
}

View File

@@ -1,6 +1,10 @@
import { useEthBalance, useQueryEthBalance } from '@/hooks/useEthBalance'
import { useTrades } from '@/hooks/useTrades'
import { erc20Abi, spaceAbi } from '@/lib/abi'
import { TradeType } from '@/lib/constants'
import { extractErrorMessage } from '@/lib/extractErrorMessage'
import { precision } from '@/lib/math'
import { api } from '@/lib/trpc'
import { wagmiConfig } from '@/lib/wagmi'
import { Space } from '@prisma/client'
import { waitForTransactionReceipt } from '@wagmi/core'
@@ -23,6 +27,7 @@ interface Props {
}
export const SellBtn = ({
ethAmount,
purchasedAmount,
isInsufficientBalance,
isAmountValid,
@@ -32,6 +37,8 @@ export const SellBtn = ({
}: Props) => {
const { writeContractAsync, isPending } = useWriteContract()
const balance = useSpaceTokenBalance()
const { refetch: refetchEth } = useQueryEthBalance()
const trade = useTrades()
const onSell = async () => {
try {
@@ -54,7 +61,18 @@ export const SellBtn = ({
})
await waitForTransactionReceipt(wagmiConfig, { hash })
await balance.refetch()
await Promise.all([
api.trade.create.mutate({
spaceId: space.id,
type: TradeType.SELL,
amountIn: String(value),
amountOut: precision.token(ethAmount).toString(),
}),
balance.refetch(),
refetchEth(),
])
trade.refetch()
handleSwap()
toast.success(`${space?.name} sell successfully!`)
} catch (error) {
@@ -68,14 +86,14 @@ export const SellBtn = ({
<>
{isConnected ? (
<Button
className="w-full h-[58px]"
className="w-full h-[50px]"
disabled={!isAmountValid || isInsufficientBalance || isPending}
onClick={() => onSell()}
>
{isPending || balance.isPending ? (
<LoadingDots color="white" />
) : isInsufficientBalance ? (
`Insufficient ${space.name} balance`
`Insufficient ${space.symbolName} balance`
) : isAmountValid ? (
'Sell'
) : (

View File

@@ -0,0 +1,133 @@
import { ChangeEvent, useMemo, useState } from 'react'
import { useChainSpace, useQueryChainSpace } from '@/hooks/useChainSpace'
import { useEthBalance, useQueryEthBalance } from '@/hooks/useEthBalance'
import { precision } from '@/lib/math'
import { toFloorFixed } from '@/lib/utils'
import { Space } from '@prisma/client'
import { Address } from 'viem'
import { Button } from '../ui/button'
import { AmountInput } from './AmountInput'
import { EthBalance } from './EthBalance'
import {
formatAmount,
useSpaceTokenBalance,
} from './hooks/useSpaceTokenBalance'
import { SellBtn } from './SellBtn'
import { SpaceTokenBalance } from './SpaceTokenBalance'
interface Props {
isConnected: boolean
space: Space
}
export const SellPanel = ({ space, isConnected }: Props) => {
const [ethAmount, setEthAmount] = useState<string>('')
const [purchasedAmount, setPurchasedAmount] = useState<string>('')
const { space: chainSpace } = useChainSpace()
const { refetch: refetchChainSpace } = useQueryChainSpace()
// const isAmountValid = parseFloat(ethAmount) > 0 && parseFloat(purchasedAmount) > 0
// TODO: please add eth judgment logic
const isAmountValid = parseFloat(purchasedAmount) > 0
const { data: tokenBalance } = useSpaceTokenBalance()
const { ethBalance } = useEthBalance()
const validateAndSetEthAmount = (value: string) => {
// Validate and format input
// if (/^\d*\.?\d*$/.test(value) && !value.startsWith('.')) { }
}
const handleTokenChange = (value: string) => {
setPurchasedAmount(value)
if (!value) {
return setEthAmount('')
}
const ethAmountDecimal = precision.toDecimal(
chainSpace.getEthAmount(precision.token(value)),
)
setEthAmount(toFloorFixed(ethAmountDecimal, 4).toString())
}
const handleMax = () => {
if (!tokenBalance) return
setPurchasedAmount(
toFloorFixed(precision.toDecimal(tokenBalance), 4).toString(),
)
const ethAmountDecimal = precision.toDecimal(
chainSpace.getEthAmount(tokenBalance),
)
setEthAmount(toFloorFixed(ethAmountDecimal, 4).toString())
}
const isInsufficientBalance =
precision.toDecimal(tokenBalance! || '0') < parseFloat(purchasedAmount)
return (
<>
<div className="mb-2 bg-gray-100 rounded-xl p-4">
<div className="text-sm">Sell</div>
<AmountInput
symbolName={space.symbolName}
icon={
<img
src={space.logo || ''}
alt={space.symbolName}
className="w-5 h-auto"
/>
}
value={purchasedAmount}
onChange={(value) => handleTokenChange(value)}
/>
<div className="flex items-center justify-end gap-2 h-6">
<SpaceTokenBalance />
<Button
onClick={handleMax}
disabled={
typeof tokenBalance === undefined ||
precision.toDecimal(tokenBalance!) <= 0
}
className="h-6 cursor-pointer text-xs text-white rounded-md px-2"
>
Max
</Button>
</div>
</div>
<div className="mb-4 bg-gray-100 rounded-xl p-4">
<div className="text-sm">Buy</div>
<AmountInput
symbolName="ETH"
disabled
icon={<img src="/eth.png" alt="ETH" className="w-5 h-auto" />}
value={ethAmount}
onChange={(value) => validateAndSetEthAmount(value)}
/>
<div className="flex items-center justify-end gap-2 h-6">
<EthBalance />
</div>
</div>
<SellBtn
ethAmount={ethAmount}
purchasedAmount={purchasedAmount}
isConnected={isConnected}
handleSwap={() => {
setEthAmount('')
setPurchasedAmount('')
refetchChainSpace()
}}
isInsufficientBalance={isInsufficientBalance}
isAmountValid={isAmountValid}
space={space}
/>
</>
)
}

View File

@@ -0,0 +1,18 @@
'use client'
import { precision } from '@/lib/math'
import { Skeleton } from '../ui/skeleton'
import { useSpaceTokenBalance } from './hooks/useSpaceTokenBalance'
export const SpaceTokenBalance = () => {
const { isLoading, data } = useSpaceTokenBalance()
if (isLoading) return <Skeleton></Skeleton>
return (
<div className="flex items-center gap-1">
<span className="i-[iconoir--wallet-solid] w-5 h-5 bg-neutral-400"></span>
<div className="text-sm text-neutral-500">
{precision.toDecimal(data!).toFixed(4)}
</div>
</div>
)
}

View File

@@ -83,7 +83,7 @@ export class Space {
getTokenPricePerSecond() {
const ethPricePerSecond = this.getEthPricePerSecond()
const { tokenAmount } = this.getTokenAmount(ethPricePerSecond)
const tokenAmount = this.getTokenAmount(ethPricePerSecond)
return tokenAmount
}
@@ -93,10 +93,7 @@ export class Space {
const newX = this.x + ethAmountAfterFee
const newY = this.k / newX
const tokenAmount = this.y - newY
return {
tokenAmount,
tokenAmountFormatted: toFloorFixed(Number(tokenAmount), 2),
}
return tokenAmount
}
getEthAmount(tokenAmount: bigint) {
@@ -105,9 +102,6 @@ export class Space {
const newY = this.y + tokenAmountAfterFee
const newX = this.k / newY
const ethAmount = this.x - newX
return {
ethAmount,
ethAmountFormatted: toFloorFixed(Number(ethAmount), 2),
}
return ethAmount
}
}

View File

@@ -1,7 +1,7 @@
import { trpc } from '@/lib/trpc'
export function useSpaceTrades(spaceId: string) {
const { data: trades = [], ...rest } =
trpc.trade.listBySpaceId.useQuery(spaceId)
return { trades, ...rest }
export function useSubscriptionRecords(spaceId: string) {
const { data: records = [], ...rest } =
trpc.subscriptionRecord.listBySpaceId.useQuery(spaceId)
return { records, ...rest }
}

View File

@@ -1,63 +0,0 @@
import { spaceAbi } from '@/lib/abi'
import { wagmiConfig } from '@/lib/wagmi'
import { readContract, readContracts } from '@wagmi/core'
import { atom, useAtom } from 'jotai'
import { Address } from 'viem'
interface IKxy {
k: bigint
x: bigint
y: bigint
}
const tokenKxyAtom = atom<IKxy>({
k: BigInt(0),
x: BigInt(0),
y: BigInt(0),
})
const FEE_RATE = BigInt(1)
export function useTokenKxy() {
const [tokenKxy, setTokenKxy] = useAtom(tokenKxyAtom)
const updateTokenKxy = async (spaceAddress: Address) => {
try {
const { x, y, k } = await readContract(wagmiConfig, {
address: spaceAddress,
abi: spaceAbi,
functionName: 'getSpaceInfo',
})
setTokenKxy({ k, x, y })
} catch (error) {
console.error('Failed to update kxy values:', error)
}
}
function getBuyTokenAmount(ethAmount: bigint): bigint {
const fee = (ethAmount * FEE_RATE) / BigInt(100)
const ethAmountAfterFee = ethAmount - fee
const newX = tokenKxy.x + ethAmountAfterFee
const newY = tokenKxy.k / newX
const tokenAmount = tokenKxy.y - newY
return tokenAmount
}
function getSellEthAmount(tokenAmount: bigint): bigint {
const fee = (tokenAmount * FEE_RATE) / BigInt(100)
const tokenAmountAfterFee = tokenAmount - fee
const newY = tokenKxy.y + tokenAmountAfterFee
const newX = tokenKxy.k / newY
const ethAmount = tokenKxy.x - newX
return ethAmount
}
const getTokenKxy = (): IKxy => {
return tokenKxy
}
return { getBuyTokenAmount, getTokenKxy, updateTokenKxy, getSellEthAmount }
}

13
hooks/useTrades.ts Normal file
View File

@@ -0,0 +1,13 @@
import { trpc } from '@/lib/trpc'
import { useSpace } from './useSpace'
export function useTrades() {
const { space } = useSpace()
const { data: trades = [], ...rest } = trpc.trade.listBySpaceId.useQuery(
space.id,
{
enabled: !!space?.id,
},
)
return { records: trades, ...rest }
}

View File

@@ -3,6 +3,11 @@ export enum TradeType {
SELL = 'SELL',
}
export enum SubscriptionType {
SUBSCRIBE = 'SUBSCRIBE',
UNSUBSCRIBE = 'UNSUBSCRIBE',
}
export enum TradeSource {
MEMBER = 'MEMBER',
SPONSOR = 'SPONSOR',

View File

@@ -34,7 +34,10 @@ export const precision = {
return BigInt(parseInt(times(Math.pow(10, decimal), value) as any, 10))
},
toDecimal(value: bigint | number, decimals: number = Decimals.TOKEN) {
toDecimal(
value: bigint | number | string = '',
decimals: number = Decimals.TOKEN,
) {
if (!value) return 0
return div(value.toString(), Math.pow(10, decimals))
},
@@ -43,8 +46,11 @@ export const precision = {
},
// Maintaining Numeric Precision
toExactDecimalString(value: bigint, decimals: number = Decimals.TOKEN): string {
if (!value) return "0"
toExactDecimalString(
value: bigint,
decimals: number = Decimals.TOKEN,
): string {
if (!value) return '0'
const factor = BigInt(Math.pow(10, decimals))
const integerPart = value / factor
const fractionalPart = value % factor
@@ -52,10 +58,13 @@ export const precision = {
},
// Maintaining Numeric Precision
toExactDecimalBigint(decimalValue: string, decimals: number = Decimals.TOKEN): bigint {
const [integerPart, fractionalPart = ""] = decimalValue.split(".")
toExactDecimalBigint(
decimalValue: string,
decimals: number = Decimals.TOKEN,
): bigint {
const [integerPart, fractionalPart = ''] = decimalValue.split('.')
const integerBigInt = BigInt(integerPart) * BigInt(Math.pow(10, decimals))
const fractionalBigInt = BigInt(fractionalPart.padEnd(decimals, "0"))
const fractionalBigInt = BigInt(fractionalPart.padEnd(decimals, '0'))
return integerBigInt + fractionalBigInt
}
},
}

View File

@@ -49,10 +49,10 @@ export const siweConfig = createSIWEConfig({
}
},
// onSignIn: (session?: SIWESession) => {
// location.href = '/'
// location.reload()
// },
onSignIn: (session?: SIWESession) => {
// location.href = '/'
location.reload()
},
signOut: async () => {
try {
await signOut({

View File

@@ -24,7 +24,16 @@ const metadata = {
// Create wagmiConfig
const chains = [
arbitrumSepolia,
{
...arbitrumSepolia,
rpcUrls: {
default: {
http: [
'https://arb-sepolia.g.alchemy.com/v2/HI5irVjoVHajk9VrB7FAkpt2Inz1iymo',
],
},
},
},
// baseSepolia,
// mainnet,
// optimism,

View File

@@ -6,7 +6,7 @@ const { env } = require('./server/env')
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['app.localhost:3000'],
// allowedOrigins: ['app.localhost:3000'],
},
},

View File

@@ -42,10 +42,10 @@
"@vercel/analytics": "^1.3.1",
"@vercel/blob": "^0.15.0",
"@vercel/kv": "^1.0.1",
"@vercel/postgres": "^0.5.1",
"@vercel/postgres": "^0.9.0",
"@wagmi/core": "^2.13.4",
"@web3modal/siwe": "^5.1.0",
"@web3modal/wagmi": "^5.1.0",
"@web3modal/siwe": "^5.1.1",
"@web3modal/wagmi": "^5.1.1",
"ai": "^2.2.22",
"big.js": "^6.2.1",
"c": "^1.1.1",

317
pnpm-lock.yaml generated
View File

@@ -96,17 +96,17 @@ dependencies:
specifier: ^1.0.1
version: 1.0.1
'@vercel/postgres':
specifier: ^0.5.1
version: 0.5.1
specifier: ^0.9.0
version: 0.9.0
'@wagmi/core':
specifier: ^2.13.4
version: 2.13.4(@types/react@18.2.37)(react@18.2.0)(typescript@5.2.2)(viem@2.18.2)
'@web3modal/siwe':
specifier: ^5.1.0
version: 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
specifier: ^5.1.1
version: 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/wagmi':
specifier: ^5.1.0
version: 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(@wagmi/connectors@5.1.1)(@wagmi/core@2.13.4)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0)(viem@2.18.2)(vue@3.3.8)(wagmi@2.12.1)
specifier: ^5.1.1
version: 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(@wagmi/connectors@5.1.1)(@wagmi/core@2.13.4)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0)(viem@2.18.2)(vue@3.3.8)(wagmi@2.12.1)
ai:
specifier: ^2.2.22
version: 2.2.22(react@18.2.0)(solid-js@1.8.5)(svelte@4.2.3)(vue@3.3.8)
@@ -2685,10 +2685,10 @@ packages:
tslib: 2.6.2
dev: false
/@neondatabase/serverless@0.6.0:
resolution: {integrity: sha512-qXxBRYN0m2v8kVQBfMxbzNGn2xFAhTXFibzQlE++NfJ56Shz3m7+MyBBtXDlEH+3Wfa6lToDXf1MElocY4sJ3w==}
/@neondatabase/serverless@0.9.4:
resolution: {integrity: sha512-D0AXgJh6xkf+XTlsO7iwE2Q1w8981E1cLCPAALMU2YKtkF/1SF6BiAzYARZFYo175ON+b1RNIy9TdSFHm5nteg==}
dependencies:
'@types/pg': 8.6.6
'@types/pg': 8.11.6
dev: false
/@next/env@14.0.2:
@@ -5399,12 +5399,12 @@ packages:
resolution: {integrity: sha512-5PjwB0uP2XDp3nt5u5NJAG2DORHIRClPzWT/TTZhJ2Ekwe8M5bA9tvPdi9NO/n2uvu2/ictat8kgqvLfcIE1SA==}
dev: false
/@types/pg@8.6.6:
resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
/@types/pg@8.11.6:
resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==}
dependencies:
'@types/node': 20.9.0
pg-protocol: 1.6.0
pg-types: 2.2.0
pg-types: 4.0.2
dev: false
/@types/prop-types@15.7.10:
@@ -5593,14 +5593,14 @@ packages:
'@upstash/redis': 1.25.1
dev: false
/@vercel/postgres@0.5.1:
resolution: {integrity: sha512-JKl8QOBIDnifhkxAhIKtY0A5Tb8oWBf2nzZhm0OH7Ffjsl0hGVnDL2w1/FCfpX8xna3JAWM034NGuhZfTFdmiw==}
/@vercel/postgres@0.9.0:
resolution: {integrity: sha512-WiI2g3+ce2g1u1gP41MoDj2DsMuQQ+us7vHobysRixKECGaLHpfTI7DuVZmHU087ozRAGr3GocSyqmWLLo+fig==}
engines: {node: '>=14.6'}
dependencies:
'@neondatabase/serverless': 0.6.0
'@neondatabase/serverless': 0.9.4
bufferutil: 4.0.8
utf-8-validate: 6.0.3
ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)
utf-8-validate: 6.0.4
ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
dev: false
/@vue/compiler-core@3.3.8:
@@ -5866,8 +5866,8 @@ packages:
- utf-8-validate
dev: false
/@walletconnect/core@2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-QekYQlpxyn2bcQXMkMxo0+v7nUOQKyu3j5ZKzTg/HGU1eSgTRLIvYIEkC8VVflIgOw7meOAb5pFChX51wShksQ==}
/@walletconnect/core@2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-9MWVt33MFrLiAeK9nqY/B30/y0M4uiq8v9EXenIBQdlgkmXM++RTcOnn7u7EAbthGgzx3WLPRm4ViwIb+rI/Cg==}
engines: {node: '>=18'}
dependencies:
'@walletconnect/heartbeat': 1.2.2
@@ -5881,8 +5881,8 @@ packages:
'@walletconnect/relay-auth': 1.0.4
'@walletconnect/safe-json': 1.0.2
'@walletconnect/time': 1.0.2
'@walletconnect/types': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/types': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
events: 3.3.0
lodash.isequal: 4.5.0
uint8arrays: 3.1.0
@@ -5946,18 +5946,18 @@ packages:
- utf-8-validate
dev: false
/@walletconnect/ethereum-provider@2.15.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-qQyHVwJtPo7RZJIIn7KAp20t2pthhr9P2Nnhv196+49dTMJ5Es962cgouA410XA7DSQkom+4esiXR2AR5SsrAA==}
/@walletconnect/ethereum-provider@2.15.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-3ssEAKc/rLYshwyE2ZIaoTxzi/p9Ws+kj/FIsd1Ed/CC37Rl5l/KYHaRJtevWeni9s4dGqyqKsYkJ0VwwUcnfQ==}
dependencies:
'@walletconnect/jsonrpc-http-connection': 1.0.8
'@walletconnect/jsonrpc-provider': 1.0.14
'@walletconnect/jsonrpc-types': 1.0.4
'@walletconnect/jsonrpc-utils': 1.0.8
'@walletconnect/modal': 2.6.2(@types/react@18.2.37)(react@18.2.0)
'@walletconnect/sign-client': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/types': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/universal-provider': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/sign-client': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/types': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/universal-provider': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
events: 3.3.0
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -6168,17 +6168,17 @@ packages:
- utf-8-validate
dev: false
/@walletconnect/sign-client@2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-efwrPfIwKWKeku44TGBCnQqPZGCILI1wBKK9bTF0F0/qrLR/zRe6RWpM3/L4+jOMr/BktxPZ5lRozBh+c2U7Pg==}
/@walletconnect/sign-client@2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-YnLNEmCHgZ8yBpE3hwZnHD/bVznVMguSAlwLBNOoWUH2f4d9mR8bqa6KeVXqZ3e8mVHcxKTJTjTJ3oQMLyKIjw==}
dependencies:
'@walletconnect/core': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/core': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/events': 1.0.1
'@walletconnect/heartbeat': 1.2.2
'@walletconnect/jsonrpc-utils': 1.0.8
'@walletconnect/logger': 2.1.2
'@walletconnect/time': 1.0.2
'@walletconnect/types': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/types': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
events: 3.3.0
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -6231,8 +6231,8 @@ packages:
- uWebSockets.js
dev: false
/@walletconnect/types@2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-hLffDKKe70jIrK+YcLkAnzi6vqNki1SDBWjV+M/72mKcU2KzXxk0G2STFsWsQDx8DoqxMiuGehd0DlD1jwQmBg==}
/@walletconnect/types@2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-4WkMsHD8ioZI5GmxNT0qMlz6msI7ZajBcTyDxfRncaNZVau0C+Btw1U4jWO+gxwJVDJY+Ue/cb1QKJ5BanZsyw==}
dependencies:
'@walletconnect/events': 1.0.1
'@walletconnect/heartbeat': 1.2.2
@@ -6289,17 +6289,17 @@ packages:
- utf-8-validate
dev: false
/@walletconnect/universal-provider@2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-+jIuYyLfud1XRYPWt/3wYiD7DYUOSZk26qbtvZFMj1m947NRnZGzp+0gt1ORi7NInEtX3R0fUhMOYKnPwadp6g==}
/@walletconnect/universal-provider@2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-JvKwHoE/ugWSKOmrEr03go1V79N0bbYV6w24Lqlzz4VAoReZZo8TDKsya7UkJ1L5HUCgKVP+AVktuJv8khzJ6w==}
dependencies:
'@walletconnect/jsonrpc-http-connection': 1.0.8
'@walletconnect/jsonrpc-provider': 1.0.14
'@walletconnect/jsonrpc-types': 1.0.4
'@walletconnect/jsonrpc-utils': 1.0.8
'@walletconnect/logger': 2.1.2
'@walletconnect/sign-client': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/types': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/sign-client': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/types': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/utils': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
events: 3.3.0
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -6355,8 +6355,8 @@ packages:
- uWebSockets.js
dev: false
/@walletconnect/utils@2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-xaazgCMyr5fUPm2QuZ76G+W8beDfKMILqJ3INL6wyuaLil2YQNdsCSvWMNhSP+EZeKD3SUqqBmQaM/maP0YHTg==}
/@walletconnect/utils@2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1):
resolution: {integrity: sha512-i5AR8XpZdcX8ghaCjYV13Er/KAGe56c1mLaG9c2cv9kmnZMZijeMdInjX/flnSM1RFDUiZXvKPMUNwlCL4NsWw==}
dependencies:
'@stablelib/chacha20poly1305': 1.0.1
'@stablelib/hkdf': 1.0.1
@@ -6366,11 +6366,10 @@ packages:
'@walletconnect/relay-api': 1.0.11
'@walletconnect/safe-json': 1.0.2
'@walletconnect/time': 1.0.2
'@walletconnect/types': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/types': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@walletconnect/window-getters': 1.0.1
'@walletconnect/window-metadata': 1.0.1
detect-browser: 5.3.0
elliptic: 6.5.7
query-string: 7.1.3
uint8arrays: 3.1.0
transitivePeerDependencies:
@@ -6403,32 +6402,32 @@ packages:
tslib: 1.14.1
dev: false
/@web3modal/common@5.1.0:
resolution: {integrity: sha512-TIYncrKDnFKE+q0mudzVCvGOZdNWa0kzkp+iL0zpbHm9sL+ceV4eOCip09Xcvrb5HeaBv1ROgAMPGLmpVCO3AA==}
/@web3modal/common@5.1.1:
resolution: {integrity: sha512-3rXsfFhaGPdr0F1iQ0andnZ4o9Dtq23m4ZC+IkEZ9nYjQ3U8i1KkI7jlSL3Ziq5jSTy1/uBcr154Db4frb6+jQ==}
dependencies:
bignumber.js: 9.1.2
dayjs: 1.11.10
dev: false
/@web3modal/core@5.1.0(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-dasGgDnhAFKvdm4OwC4iiWsCwx+kPjrZBEqnz1vxY1Z27aQx19eke4xN+2IYjN/TCRG+m+5BoeCqyNomHEjaug==}
/@web3modal/core@5.1.1(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-76ywvsayfj7LcdrxfQBMOj62HRULnH3tIvIO9khQm/8EAjqTo23kKMPxNxd5kctw8EN6C0qonTFAzxY46v6QHA==}
dependencies:
'@web3modal/common': 5.1.0
'@web3modal/wallet': 5.1.0
'@web3modal/common': 5.1.1
'@web3modal/wallet': 5.1.1
valtio: 1.11.2(@types/react@18.2.37)(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
- react
dev: false
/@web3modal/polyfills@5.1.0:
resolution: {integrity: sha512-Vj0+htFFi053oU4n10vfkNDCDv0VladWW7WdpbneiYs8nVThIP1+aGil5DG1iL0JsModgwaWgJwKwDKbwlCFtA==}
/@web3modal/polyfills@5.1.1:
resolution: {integrity: sha512-Y91SBc12r2x1V3Y3OsNZM5zmNLBUEd3mIzJ9fGrBu38q6Fe6XzxVr6VEWCPiK7g/y1eA++1AdbiVZq587zeuRg==}
dependencies:
buffer: 6.0.3
dev: false
/@web3modal/scaffold-react@5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-XfgRiIVCvhAPBBjXJIKMQIdQXSqfLza8XtylakqiFtgh9B8taFJOCH4vomi+WucAvGAtxGju4IyEub7afSIvog==}
/@web3modal/scaffold-react@5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-u88KVr/7qKmCGaIVEJvUQhGf3VaqPKzyqWDaymLgo6IvsTCnnsoVZdejpkxh1FY28vOehsBD2QMh/ruPPzpnRw==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
@@ -6438,7 +6437,7 @@ packages:
react-dom:
optional: true
dependencies:
'@web3modal/scaffold': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/scaffold': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
@@ -6459,15 +6458,15 @@ packages:
- uWebSockets.js
dev: false
/@web3modal/scaffold-ui@5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-X2WjL6TtuCFw2qeVClVjmYDWbBQDK7JV0CiO57FUBo8xLMhYcj1pSQ1G+zOvikkWaGWwiOyKxJWzVsfrDeihOQ==}
/@web3modal/scaffold-ui@5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-Pi1TAPPtw3Xxb3BD8EDsRVhtKh9UPNa6ACTrDrKnAeXBPSWMRI2GBZOTrfrqfJzixsjoFfH+79J/D2JDvvXCEA==}
dependencies:
'@web3modal/common': 5.1.0
'@web3modal/core': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/siwe': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/ui': 5.1.0
'@web3modal/wallet': 5.1.0
'@web3modal/common': 5.1.1
'@web3modal/core': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/siwe': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/ui': 5.1.1
'@web3modal/wallet': 5.1.1
lit: 3.1.0
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -6488,29 +6487,29 @@ packages:
- uWebSockets.js
dev: false
/@web3modal/scaffold-utils@5.1.0(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-q4SwEWgUT03VJEMvOuegm+k89eWU/BYq/2bJYIyO5bf0hpVCTLDk2MG9nNJ1wafJdYv8iCAe5xWhfdv8OKPC3Q==}
/@web3modal/scaffold-utils@5.1.1(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-oHZ9LKidN36sFDFaTcBgkfhKRxSx+wYPpLtL7hdN782w5MI6M/rhnefmUogKblWfXSmZ3GvDVbpG3QLvKEPIZg==}
dependencies:
'@coinbase/wallet-sdk': 4.0.3
'@web3modal/core': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/polyfills': 5.1.0
'@web3modal/wallet': 5.1.0
'@web3modal/core': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/polyfills': 5.1.1
'@web3modal/wallet': 5.1.1
valtio: 1.11.2(@types/react@18.2.37)(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
- react
dev: false
/@web3modal/scaffold-vue@5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)(vue@3.3.8):
resolution: {integrity: sha512-d0Ar9b+lXVVlhzFVAACFrYbzCEen5hdzgmevRibKvhkvYnKZjYweTSldTNt8z0qnn1JNZMOA23/9Q4gGMJHACA==}
/@web3modal/scaffold-vue@5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)(vue@3.3.8):
resolution: {integrity: sha512-NiwLGZpPEgHHf22KD2Xvd7mK70RKK0kfQOB8sm3W0LSfxh5wfrY1WrhwXR0XDs9mSFN2fD4bxEDbUwvcTXD/XQ==}
peerDependencies:
vue: '>=3'
peerDependenciesMeta:
vue:
optional: true
dependencies:
'@web3modal/core': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/core': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
vue: 3.3.8(typescript@5.2.2)
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -6531,16 +6530,16 @@ packages:
- uWebSockets.js
dev: false
/@web3modal/scaffold@5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-92SO2gQF1N3TWJBIor1ju2RoBvubCbhm/zdVG1nGYvyIf3k9ulX2crZAmg0P8CE8Cc3rrLPty2wW10hncbdv7g==}
/@web3modal/scaffold@5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-E51ksfT0EWyttUmIjnkKDzTo5WafrzcrScXVeuMySYGk0ldN4yh70A8Y82qWcCtPRQ8hzb8MrMB4F26P67BGlA==}
dependencies:
'@web3modal/common': 5.1.0
'@web3modal/core': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-ui': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/siwe': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/ui': 5.1.0
'@web3modal/wallet': 5.1.0
'@web3modal/common': 5.1.1
'@web3modal/core': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-ui': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/siwe': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/ui': 5.1.1
'@web3modal/wallet': 5.1.1
lit: 3.1.0
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -6561,15 +6560,15 @@ packages:
- uWebSockets.js
dev: false
/@web3modal/siwe@5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-HUS/lBswY+AUiYvDbUaJX1RFoDH4HLFouUDv+Q4DhpY8qOT2vT8IdxkVyKezDAtXRcak7L/qSzLdgSv5ijDcDw==}
/@web3modal/siwe@5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0):
resolution: {integrity: sha512-tuWIKuXP0cRL+7DI3pkK0UqMWPwxptfI+ojinndQfwc972sFNGGZZLxeYWCShWqbUnMw0eVQOjoJCXDF9zXTtA==}
dependencies:
'@walletconnect/utils': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@web3modal/common': 5.1.0
'@web3modal/core': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/ui': 5.1.0
'@web3modal/wallet': 5.1.0
'@walletconnect/utils': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@web3modal/common': 5.1.1
'@web3modal/core': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/ui': 5.1.1
'@web3modal/wallet': 5.1.1
lit: 3.1.0
valtio: 1.11.2(@types/react@18.2.37)(react@18.2.0)
transitivePeerDependencies:
@@ -6591,15 +6590,15 @@ packages:
- uWebSockets.js
dev: false
/@web3modal/ui@5.1.0:
resolution: {integrity: sha512-msjkOm/PLkfAVglQ8ZsuCLNuaf4IbkWkjZneO1uU+F2+7sCaGZHskFVmJQBbhwN347m0DTU8KgDP0BvFppW4Aw==}
/@web3modal/ui@5.1.1:
resolution: {integrity: sha512-OSAWbltEBc54dVE2WDdqYFMd6Fg1ivh46BHAn1D5bCjGEgGHdm7j8ZHukcKzP9e+raNz8Ih7ShuPKC+NtMc9Dw==}
dependencies:
lit: 3.1.0
qrcode: 1.5.3
dev: false
/@web3modal/wagmi@5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(@wagmi/connectors@5.1.1)(@wagmi/core@2.13.4)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0)(viem@2.18.2)(vue@3.3.8)(wagmi@2.12.1):
resolution: {integrity: sha512-3SrBSSriv8sDgnBNpK1eo8ykbvIAw9Zmv2rfQ3KMkltW/rTTFTLX9mSbALnV+Tj6ZjhQLnVArtH8FX3FztffQA==}
/@web3modal/wagmi@5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(@wagmi/connectors@5.1.1)(@wagmi/core@2.13.4)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0)(viem@2.18.2)(vue@3.3.8)(wagmi@2.12.1):
resolution: {integrity: sha512-BxQHx1En8+FPSrh+h9P++ixd+LvihCsxZwG0SvlgORTrvGCq/CaO4EgoMUsXek9r5apatwXiJHiqWyIhWDnJsQ==}
peerDependencies:
'@wagmi/connectors': '>=4'
'@wagmi/core': '>=2.0.0'
@@ -6618,16 +6617,16 @@ packages:
dependencies:
'@wagmi/connectors': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(@wagmi/core@2.13.4)(ioredis@5.4.1)(react-dom@18.2.0)(react-native@0.74.3)(react@18.2.0)(typescript@5.2.2)(viem@2.18.2)(zod@3.23.8)
'@wagmi/core': 2.13.4(@types/react@18.2.37)(react@18.2.0)(typescript@5.2.2)(viem@2.18.2)
'@walletconnect/ethereum-provider': 2.15.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@walletconnect/utils': 2.15.0(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@web3modal/common': 5.1.0
'@web3modal/polyfills': 5.1.0
'@web3modal/scaffold': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/scaffold-react': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.0(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-vue': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)(vue@3.3.8)
'@web3modal/siwe': 5.1.0(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/wallet': 5.1.0
'@walletconnect/ethereum-provider': 2.15.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@walletconnect/utils': 2.15.1(@vercel/kv@1.0.1)(ioredis@5.4.1)
'@web3modal/common': 5.1.1
'@web3modal/polyfills': 5.1.1
'@web3modal/scaffold': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/scaffold-react': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react-dom@18.2.0)(react@18.2.0)
'@web3modal/scaffold-utils': 5.1.1(@types/react@18.2.37)(react@18.2.0)
'@web3modal/scaffold-vue': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)(vue@3.3.8)
'@web3modal/siwe': 5.1.1(@types/react@18.2.37)(@vercel/kv@1.0.1)(ioredis@5.4.1)(react@18.2.0)
'@web3modal/wallet': 5.1.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
viem: 2.18.2(typescript@5.2.2)(zod@3.23.8)
@@ -6654,12 +6653,12 @@ packages:
- utf-8-validate
dev: false
/@web3modal/wallet@5.1.0:
resolution: {integrity: sha512-5LdsFMp6wozdvEchfnagsAH/u1moqfRFnBb2iA2gDQZWH0Y+M9UIkw6iRUw0QFnae+uB4ALyUJ3Te0L8VLWmhQ==}
/@web3modal/wallet@5.1.1:
resolution: {integrity: sha512-i0UOh5GuXy0qJbI9bdAf4iPto7ZPX99rjbyiFs3VAR79DM5Ja4f7f2fgBaZy7LVcUr/Gk5LCGyr5Dffmn7d/YQ==}
dependencies:
'@walletconnect/logger': 2.1.2
'@web3modal/common': 5.1.0
'@web3modal/polyfills': 5.1.0
'@web3modal/common': 5.1.1
'@web3modal/polyfills': 5.1.1
zod: 3.22.4
dev: false
@@ -8322,18 +8321,6 @@ packages:
minimalistic-crypto-utils: 1.0.1
dev: false
/elliptic@6.5.7:
resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==}
dependencies:
bn.js: 4.12.0
brorand: 1.1.0
hash.js: 1.1.7
hmac-drbg: 1.0.1
inherits: 2.0.4
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
dev: false
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: false
@@ -8363,7 +8350,7 @@ packages:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.4
engine.io-parser: 5.2.3
ws: 8.17.1
ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
xmlhttprequest-ssl: 2.0.0
transitivePeerDependencies:
- bufferutil
@@ -10153,7 +10140,7 @@ packages:
peerDependencies:
ws: '*'
dependencies:
ws: 8.17.1
ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
dev: false
/iterator.prototype@1.1.2:
@@ -10400,7 +10387,7 @@ packages:
whatwg-encoding: 2.0.0
whatwg-mimetype: 3.0.0
whatwg-url: 11.0.0
ws: 8.17.1
ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
xml-name-validator: 4.0.0
transitivePeerDependencies:
- bufferutil
@@ -12169,6 +12156,10 @@ packages:
es-abstract: 1.22.3
dev: true
/obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
dev: false
/ofetch@1.3.4:
resolution: {integrity: sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw==}
dependencies:
@@ -12452,19 +12443,26 @@ packages:
engines: {node: '>=4.0.0'}
dev: false
/pg-numeric@1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
dev: false
/pg-protocol@1.6.0:
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
dev: false
/pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
/pg-types@4.0.2:
resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==}
engines: {node: '>=10'}
dependencies:
pg-int8: 1.0.1
postgres-array: 2.0.0
postgres-bytea: 1.0.0
postgres-date: 1.0.7
postgres-interval: 1.2.0
pg-numeric: 1.0.2
postgres-array: 3.0.2
postgres-bytea: 3.0.0
postgres-date: 2.1.0
postgres-interval: 3.0.0
postgres-range: 1.1.4
dev: false
/picocolors@1.0.0:
@@ -12672,26 +12670,30 @@ packages:
picocolors: 1.0.1
source-map-js: 1.2.0
/postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
/postgres-array@3.0.2:
resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
engines: {node: '>=12'}
dev: false
/postgres-bytea@1.0.0:
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
engines: {node: '>=0.10.0'}
dev: false
/postgres-date@1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
dev: false
/postgres-interval@1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
/postgres-bytea@3.0.0:
resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
engines: {node: '>= 6'}
dependencies:
xtend: 4.0.2
obuf: 1.1.2
dev: false
/postgres-date@2.1.0:
resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==}
engines: {node: '>=12'}
dev: false
/postgres-interval@3.0.0:
resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
engines: {node: '>=12'}
dev: false
/postgres-range@1.1.4:
resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==}
dev: false
/preact-render-to-string@5.2.6(preact@10.19.1):
@@ -15436,8 +15438,8 @@ packages:
node-gyp-build: 4.6.1
dev: false
/utf-8-validate@6.0.3:
resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==}
/utf-8-validate@6.0.4:
resolution: {integrity: sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==}
engines: {node: '>=6.14.2'}
requiresBuild: true
dependencies:
@@ -15566,7 +15568,7 @@ packages:
isows: 1.0.4(ws@8.17.1)
typescript: 5.2.2
webauthn-p256: 0.0.5
ws: 8.17.1
ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -15893,8 +15895,8 @@ packages:
optional: true
dev: false
/ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3):
resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
/ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -15906,20 +15908,7 @@ packages:
optional: true
dependencies:
bufferutil: 4.0.8
utf-8-validate: 6.0.3
dev: false
/ws@8.17.1:
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
utf-8-validate: 6.0.4
dev: false
/xml-name-validator@4.0.0:

View File

@@ -11,59 +11,61 @@ generator client {
}
model User {
id String @id @default(uuid())
address String @unique
name String?
ensName String?
email String? @unique
emailVerified DateTime?
image String?
bio String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
spaces Space[]
posts Post[]
members Member[]
trades Trade[]
holders Holder[]
sponsors Sponsor[]
comments Comment[]
lists List[]
contributors Contributor[]
messages Message[]
id String @id @default(uuid())
address String @unique
name String?
ensName String?
email String? @unique
emailVerified DateTime?
image String?
bio String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
spaces Space[]
posts Post[]
members Member[]
subscriptionRecords SubscriptionRecord[]
holders Holder[]
sponsors Sponsor[]
comments Comment[]
lists List[]
contributors Contributor[]
messages Message[]
trades Trade[]
@@index([id])
@@index([address])
}
model Space {
id String @id @default(uuid())
name String
description String @default("") @db.Text
logo String? @default("https://public.blob.vercel-storage.com/eEZHAoPTOBSYGBE3/JRajRyC-PhBHEinQkupt02jqfKacBVHLWJq7Iy.png") @db.Text
font String @default("font-cal")
image String? @default("") @db.Text
imageBlurhash String? @default("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAhCAYAAACbffiEAAAACXBIWXMAABYlAAAWJQFJUiTwAAABfUlEQVR4nN3XyZLDIAwE0Pz/v3q3r55JDlSBplsIEI49h76k4opexCK/juP4eXjOT149f2Tf9ySPgcjCc7kdpBTgDPKByKK2bTPFEdMO0RDrusJ0wLRBGCIuelmWJAjkgPGDSIQEMBDCfA2CEPM80+Qwl0JkNxBimiaYGOTUlXYI60YoehzHJDEm7kxjV3whOQTD3AaCuhGKHoYhyb+CBMwjIAFz647kTqyapdV4enGINuDJMSScPmijSwjCaHeLcT77C7EC0C1ugaCTi2HYfAZANgj6Z9A8xY5eiYghDMNQBJNCWhASot0jGsSCUiHWZcSGQjaWWCDaGMOWnsCcn2QhVkRuxqqNxMSdUSElCDbp1hbNOsa6Ugxh7xXauF4DyM1m5BLtCylBXgaxvPXVwEoOBjeIFVODtW74oj1yBQah3E8tyz3SkpolKS9Geo9YMD1QJR1Go4oJkgO1pgbNZq0AOUPChyjvh7vlXaQa+X1UXwKxgHokB2XPxbX+AnijwIU4ahazAAAAAElFTkSuQmCC") @db.Text
subdomain String? @unique
customDomain String? @unique
memberCount Int @default(0)
sponsorCount Int @default(0)
postCount Int @default(0)
message404 String? @default("Blimey! You've found a page that doesn't exist.") @db.Text
symbolName String @unique
spaceAddress String? @unique // space on chain address
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
posts Post[]
channels Channel[]
members Member[]
trades Trade[]
holders Holder[]
sponsors Sponsor[]
contributors Contributor[]
messages Message[]
id String @id @default(uuid())
name String
description String @default("") @db.Text
logo String? @default("https://public.blob.vercel-storage.com/eEZHAoPTOBSYGBE3/JRajRyC-PhBHEinQkupt02jqfKacBVHLWJq7Iy.png") @db.Text
font String @default("font-cal")
image String? @default("") @db.Text
imageBlurhash String? @default("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAhCAYAAACbffiEAAAACXBIWXMAABYlAAAWJQFJUiTwAAABfUlEQVR4nN3XyZLDIAwE0Pz/v3q3r55JDlSBplsIEI49h76k4opexCK/juP4eXjOT149f2Tf9ySPgcjCc7kdpBTgDPKByKK2bTPFEdMO0RDrusJ0wLRBGCIuelmWJAjkgPGDSIQEMBDCfA2CEPM80+Qwl0JkNxBimiaYGOTUlXYI60YoehzHJDEm7kxjV3whOQTD3AaCuhGKHoYhyb+CBMwjIAFz647kTqyapdV4enGINuDJMSScPmijSwjCaHeLcT77C7EC0C1ugaCTi2HYfAZANgj6Z9A8xY5eiYghDMNQBJNCWhASot0jGsSCUiHWZcSGQjaWWCDaGMOWnsCcn2QhVkRuxqqNxMSdUSElCDbp1hbNOsa6Ugxh7xXauF4DyM1m5BLtCylBXgaxvPXVwEoOBjeIFVODtW74oj1yBQah3E8tyz3SkpolKS9Geo9YMD1QJR1Go4oJkgO1pgbNZq0AOUPChyjvh7vlXaQa+X1UXwKxgHokB2XPxbX+AnijwIU4ahazAAAAAElFTkSuQmCC") @db.Text
subdomain String? @unique
customDomain String? @unique
memberCount Int @default(0)
sponsorCount Int @default(0)
postCount Int @default(0)
message404 String? @default("Blimey! You've found a page that doesn't exist.") @db.Text
symbolName String @unique
spaceAddress String? @unique // space on chain address
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
posts Post[]
channels Channel[]
members Member[]
subscriptionRecords SubscriptionRecord[]
holders Holder[]
sponsors Sponsor[]
contributors Contributor[]
messages Message[]
trades Trade[]
@@index([userId])
@@index([name])
@@ -200,6 +202,26 @@ model Sponsor {
@@index([userId])
}
enum SubscriptionType {
SUBSCRIBE
UNSUBSCRIBE
}
model SubscriptionRecord {
id String @id @default(uuid())
duration Int @default(0)
type SubscriptionType
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Space Space @relation(fields: [spaceId], references: [id])
spaceId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
@@index([spaceId])
@@index([type])
}
enum TradeType {
BUY
SELL
@@ -207,7 +229,8 @@ enum TradeType {
model Trade {
id String @id @default(uuid())
duration Int @default(0)
amountIn String @default("0")
amountOut String @default("0")
type TradeType
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -217,6 +240,7 @@ model Trade {
userId String
@@index([spaceId])
@@index([type])
}
enum ChannelType {

View File

@@ -10,6 +10,7 @@ import { messageRouter } from './routers/message'
import { postRouter } from './routers/post'
import { spaceRouter } from './routers/space'
import { sponsorRouter } from './routers/sponsor'
import { subscriptionRecordRouter } from './routers/subscriptionRecord'
import { tradeRouter } from './routers/trade'
import { userRouter } from './routers/user'
import { createCallerFactory, publicProcedure, router } from './trpc'
@@ -26,6 +27,7 @@ export const appRouter = router({
trade: tradeRouter,
sponsor: sponsorRouter,
contributor: contributorRouter,
subscriptionRecord: subscriptionRecordRouter,
})
export const createCaller = createCallerFactory(appRouter)

View File

@@ -0,0 +1,45 @@
import { TradeSource } from '@/lib/constants'
import { precision } from '@/lib/math'
import { prisma } from '@/lib/prisma'
import { SubscriptionRecord } from '@prisma/client'
import { TRPCError } from '@trpc/server'
import { z } from 'zod'
import { protectedProcedure, publicProcedure, router } from '../trpc'
enum SubscriptionType {
SUBSCRIBE = 'SUBSCRIBE',
UNSUBSCRIBE = 'UNSUBSCRIBE',
}
export const subscriptionRecordRouter = router({
listBySpaceId: publicProcedure.input(z.string()).query(async ({ input }) => {
return [] as (SubscriptionRecord & {
user: {
name: string | null
ensName: string | null
email: string | null
address: string
}
})[]
}),
upsertSubscription: protectedProcedure
.input(
z.object({
spaceId: z.string(),
tradeDuration: z.number(),
type: z.enum([
SubscriptionType.SUBSCRIBE,
SubscriptionType.UNSUBSCRIBE,
]),
start: z.number(),
checkpoint: z.number(),
duration: z.number(),
amount: z.string(),
consumed: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
return true
}),
})

View File

@@ -19,37 +19,13 @@ export const tradeRouter = router({
})[]
}),
tradeSpaceKey: protectedProcedure
create: protectedProcedure
.input(
z.object({
spaceId: z.string(),
tradeDuration: z.number(),
type: z.enum([TradeType.BUY, TradeType.SELL]),
start: z.number(),
checkpoint: z.number(),
duration: z.number(),
amount: z.string(),
consumed: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
return true
}),
tradeSponsorKey: protectedProcedure
.input(
z.object({
spaceId: z.string(),
holdAmount: z.string(),
tradeAmount: z.string(),
price: z.string(),
type: z.enum([TradeType.BUY, TradeType.SELL]),
name: z.string().optional(),
logo: z.string().optional(),
homeUrl: z.string().optional(),
cover: z.string().optional(),
description: z.string().optional(),
amountIn: z.string(),
amountOut: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {