Skip to content

Commit

Permalink
Add fetchOlderTickets function and OlderTickets component
Browse files Browse the repository at this point in the history
  • Loading branch information
backmeupplz committed Jan 5, 2024
1 parent 185bafc commit d4e0133
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/atoms/lastClaimedTimestamp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { atom } from 'jotai'

export default atom<Promise<bigint> | null>(null)
31 changes: 30 additions & 1 deletion src/components/ClaimDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { Spam__factory } from '@borodutch/spam-contract'
import { ethers } from 'ethers'
import { generateTicket, getOlderTickets } from 'helpers/api'
import { useAccount } from 'wagmi'
import { useEthersSigner } from 'hooks/useEthers'
import { useState } from 'preact/hooks'
import GeneratedTicket from 'models/GeneratedTIcket'
import OlderTicket from 'models/OlderTicket'
import OlderTickets from 'components/OlderTickets'
import env from 'helpers/env'
import generateTicket from 'helpers/api'

export default function ({ signature }: { signature: string }) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
const [ticket, setTicket] = useState<GeneratedTicket | null>(null)
const [olderTickets, setOlderTickets] = useState<OlderTicket[] | null>(null)

const { address } = useAccount()
const signer = useEthersSigner()
Expand All @@ -32,6 +35,22 @@ export default function ({ signature }: { signature: string }) {
}
}

async function fetchOlderTickets() {
setLoading(true)
setError('')
try {
if (!address) throw new Error('No address found')
const tickets = await getOlderTickets(address, signature)
setOlderTickets(tickets)
} catch (error) {
console.error(error)
setOlderTickets(null)
setError(error instanceof Error ? error.message : `${error}`)
} finally {
setLoading(false)
}
}

async function claimSpam() {
setLoading(true)
setSuccess(false)
Expand Down Expand Up @@ -100,11 +119,21 @@ export default function ({ signature }: { signature: string }) {
You successfully claimed $SPAM! Check your wallet for details 🚀
</div>
)}
<button
class="btn btn-primary btn-lg"
onClick={fetchOlderTickets}
disabled={loading}
>
{loading ? ' 🤔' : ''}
{olderTickets?.length ? 'Refresh' : 'Get'} older tickets to claim $SPAM
</button>
{error && (
<div role="alert" class="alert alert-error break-all">
{error}
</div>
)}
{olderTickets?.length && <OlderTickets tickets={olderTickets} />}
</>
)
}
73 changes: 73 additions & 0 deletions src/components/OlderTickets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Spam__factory } from '@borodutch/spam-contract'
import { useAccount } from 'wagmi'
import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect } from 'preact/hooks'
import { useEthersSigner } from 'hooks/useEthers'
import OlderTicket from 'models/OlderTicket'
import SuspenseWithError from 'components/SuspenseWithError'
import TicketClaimButton from 'components/TicketClaimButton'
import env from 'helpers/env'
import getDateString from 'helpers/getDateString'
import lastClaimedTimestampAtom from 'atoms/lastClaimedTimestamp'

function OlderTicketsSuspended({ tickets }: { tickets: OlderTicket[] }) {
const lastClaimedTimestamp = useAtomValue(lastClaimedTimestampAtom)
return (
<>
<p>
{lastClaimedTimestamp
? `The last time you've claimed spam was at ${getDateString(
lastClaimedTimestamp
)}.`
: "You haven't claimed $SPAM yet!"}
</p>
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>From</th>
<th>To</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{tickets.map((ticket) => (
<tr id={ticket.signature}>
<td>{getDateString(ticket.fromDate)}</td>
<td>{getDateString(ticket.toDate)}</td>
<td>{ticket.total}</td>
<td>
<TicketClaimButton ticket={ticket} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)
}

export default function ({ tickets }: { tickets: OlderTicket[] }) {
const { address } = useAccount()
const signer = useEthersSigner()

if (!address || !signer) {
return <p>Doesn't look like there is an address connected!</p>
}
const setLastClaimedTimestamp = useSetAtom(lastClaimedTimestampAtom)
useEffect(() => {
if (!address) {
setLastClaimedTimestamp(null)
return
}
const bigintAddress = BigInt(address)
const contract = Spam__factory.connect(env.VITE_CONTRACT, signer)
setLastClaimedTimestamp(contract.lastClaimTimestamps(bigintAddress, 0n))
}, [address, setLastClaimedTimestamp, signer])
return (
<SuspenseWithError errorText="Failed to load last claim date">
<OlderTicketsSuspended tickets={tickets} />
</SuspenseWithError>
)
}
90 changes: 90 additions & 0 deletions src/components/TicketClaimButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Spam__factory } from '@borodutch/spam-contract'
import { ethers } from 'ethers'
import { useAccount } from 'wagmi'
import { useAtomValue, useSetAtom } from 'jotai'
import { useEthersSigner } from 'hooks/useEthers'
import { useState } from 'preact/hooks'
import OlderTicket from 'models/OlderTicket'
import env from 'helpers/env'
import lastClaimedTimestampAtom from 'atoms/lastClaimedTimestamp'

function evenPad(value: string) {
return value.length % 2 === 0 ? value : `0${value}`
}

function turnIntoBytes(value: bigint) {
return ethers.getBytes(
ethers.zeroPadValue(`0x${evenPad(value.toString(16))}`, 32)
)
}

export default function ({
ticket,
}: {
ticket: OlderTicket
refreshClaimTimestamp: () => void
}) {
const [loading, setLoading] = useState(false)
const { address } = useAccount()
const signer = useEthersSigner()
const setLastClaimedTimestamp = useSetAtom(lastClaimedTimestampAtom)

async function claimSpam() {
setLoading(true)
try {
if (!ticket) {
throw new Error('No ticket found')
}
if (!address) {
throw new Error('No address found')
}
const contract = Spam__factory.connect(env.VITE_CONTRACT, signer)
const { r, yParityAndS } = ethers.Signature.from(ticket.signature)
const spammerBytes = turnIntoBytes(BigInt(address))
const ticketTypeBytes = turnIntoBytes(0n)
const spamAmountBytes = turnIntoBytes(
ethers.parseEther(`${ticket.total}`)
)
const fromTimestampBytes = turnIntoBytes(
BigInt(new Date(ticket.fromDate).getTime())
)
const toTimestampBytes = turnIntoBytes(
BigInt(new Date(ticket.toDate).getTime())
)
const message = [
...spammerBytes,
...ticketTypeBytes,
...spamAmountBytes,
...fromTimestampBytes,
...toTimestampBytes,
]
const tx = await contract.claimSpam(
new Uint8Array(message),
r,
yParityAndS
)
await tx.wait()
const bigintAddress = BigInt(address)
setLastClaimedTimestamp(contract.lastClaimTimestamps(bigintAddress, 0n))
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}

const lastClaimedTimestamp = useAtomValue(lastClaimedTimestampAtom)
const disabled =
!!lastClaimedTimestamp &&
new Date(Number(lastClaimedTimestamp)).getTime() >
new Date(ticket.fromDate).getTime()
return (
<button
class="btn btn-primary btn-xs"
disabled={disabled || loading}
onClick={claimSpam}
>
{loading && '🤔 '}Claim!
</button>
)
}
26 changes: 25 additions & 1 deletion src/helpers/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import GeneratedTicket from 'models/GeneratedTIcket'
import OlderTicket from 'models/OlderTicket'
import env from 'helpers/env'

export default async function generateTicket(
export async function generateTicket(
address: `0x${string}`,
signature: string
) {
Expand All @@ -23,3 +24,26 @@ export default async function generateTicket(
return res
})) as GeneratedTicket
}

export async function getOlderTickets(
address: `0x${string}`,
signature: string
) {
return (await (
await fetch(`${env.VITE_BACKEND}/tickets`, {
headers: {
address,
signature,
'Content-Type': 'application/json',
},
})
)
.json()
.then((res) => {
if (res.statusCode >= 400) {
console.log(res)
throw new Error(res.message)
}
return res
})) as OlderTicket[]
}
9 changes: 9 additions & 0 deletions src/helpers/getDateString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function (date: Date | string | bigint) {
if (typeof date === 'bigint') {
date = new Date(Number(date))
}
if (typeof date === 'string') {
date = new Date(date)
}
return date.toLocaleString()
}
13 changes: 13 additions & 0 deletions src/models/OlderTicket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface OlderTicket {
address: string
signature: string
ticketType: number
fromDate: string
toDate: string
baseAmount: number
additionalForLikes: number
additionalForRecasts: number
total: number
createdAt: string
}
export default OlderTicket

0 comments on commit d4e0133

Please sign in to comment.