Skip to content

Commit

Permalink
feat: fw-496 a single account can only claim once from a redpacket (#…
Browse files Browse the repository at this point in the history
…11495)

* feat: fw-496 a single account can only claim once from a redpacket

* fixup! feat: fw-496 a single account can only claim once from a redpacket

* fix: claim platform
  • Loading branch information
swkatmask committed Mar 22, 2024
1 parent 840dc81 commit df51410
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 11 deletions.
Expand Up @@ -329,7 +329,12 @@ export function FireflyRedpacketConfirmDialog({
</Box>
<Box className={classes.item}>
<Typography className={classes.title}>{t.amount()}</Typography>
<Typography className={classes.title} display="flex" alignItems="center" columnGap={0.5}>
<Typography
className={classes.title}
display="flex"
alignItems="center"
columnGap={0.5}
component="div">
<FormattedBalance
value={settings.total}
decimals={settings.token?.decimals}
Expand Down
35 changes: 29 additions & 6 deletions packages/plugins/RedPacket/src/SiteAdaptor/RedPacket/index.tsx
@@ -1,12 +1,12 @@
import { usePostInfoDetails, usePostLink } from '@masknet/plugin-infra/content-script'
import { useLastRecognizedIdentity, usePostInfoDetails, usePostLink } from '@masknet/plugin-infra/content-script'
import { share } from '@masknet/plugin-infra/content-script/context'
import { LoadingStatus, TransactionConfirmModal } from '@masknet/shared'
import { EMPTY_LIST, EnhanceableSite, NetworkPluginID, Sniffings } from '@masknet/shared-base'
import { makeStyles, parseColor } from '@masknet/theme'
import type { HappyRedPacketV4 } from '@masknet/web3-contracts/types/HappyRedPacketV4.js'
import { useChainContext, useNetwork, useNetworkContext } from '@masknet/web3-hooks-base'
import { EVMChainResolver } from '@masknet/web3-providers'
import { RedPacketStatus, type RedPacketJSONPayload } from '@masknet/web3-providers/types'
import { EVMChainResolver, FireflyRedPacket } from '@masknet/web3-providers'
import { RedPacketStatus, type RedPacketJSONPayload, type FireflyRedPacketAPI } from '@masknet/web3-providers/types'
import { TokenType, formatBalance, isZero } from '@masknet/web3-shared-base'
import { ChainId } from '@masknet/web3-shared-evm'
import { Card, Grow, Stack, Typography } from '@mui/material'
Expand Down Expand Up @@ -184,6 +184,7 @@ export const RedPacket = memo(function RedPacket({ payload }: RedPacketProps) {
const [{ loading: isClaiming, value: claimTxHash }, claimCallback] = useClaimCallback(account, payload)
const site = usePostInfoDetails.site()
const source = usePostInfoDetails.source()
const platform = source?.toLowerCase() as 'lens' | 'farcaster'
const isOnFirefly = site === EnhanceableSite.Firefly
const postUrl = usePostInfoDetails.url()
const handle = usePostInfoDetails.handle()
Expand All @@ -195,7 +196,6 @@ export const RedPacket = memo(function RedPacket({ payload }: RedPacketProps) {
const getShareText = useCallback(
(hasClaimed: boolean) => {
if (isOnFirefly) {
const platform = source?.toLowerCase() as 'lens' | 'farcaster'
const context = hasClaimed ? (`${platform}_claimed` as 'lens_claimed' | 'farcaster_claimed') : platform
return t.share_on_firefly({
context,
Expand All @@ -222,7 +222,7 @@ export const RedPacket = memo(function RedPacket({ payload }: RedPacketProps) {
t.share_unclaimed_message_official_account(shareTextOption)
: t.share_unclaimed_message_not_twitter(shareTextOption)
},
[payload, link, claimTxHash, t, network?.name, source, isOnFirefly, handle],
[payload, link, claimTxHash, t, network?.name, platform, isOnFirefly, handle],
)
const claimedShareText = useMemo(() => getShareText(true), [getShareText])
const shareText = useMemo(() => {
Expand Down Expand Up @@ -262,6 +262,9 @@ export const RedPacket = memo(function RedPacket({ payload }: RedPacketProps) {
}, [token, redPacketContract, payload.rpid, account, claimedShareText, source])

const [showRequirements, setShowRequirements] = useState(false)
const me = useLastRecognizedIdentity()
const myProfileId = me?.profileId
const myHandle = me?.identifier?.userId
const onClaimOrRefund = useCallback(async () => {
let hash: string | undefined
const result = await recheckClaimStatus()
Expand All @@ -271,14 +274,34 @@ export const RedPacket = memo(function RedPacket({ payload }: RedPacketProps) {
}
if (canClaim) {
hash = await claimCallback()
if (platform && myProfileId && myHandle && hash) {
await FireflyRedPacket.finishClaiming(
payload.rpid,
platform as FireflyRedPacketAPI.PlatformType,
myProfileId,
myHandle,
hash,
)
}
checkResult()
} else if (canRefund) {
hash = await refundCallback()
}
if (typeof hash === 'string') {
checkAvailability()
}
}, [canClaim, canRefund, claimCallback, checkResult, recheckClaimStatus, checkAvailability])
}, [
canClaim,
canRefund,
platform,
claimCallback,
checkResult,
recheckClaimStatus,
checkAvailability,
payload.rpid,
myProfileId,
myHandle,
])

const myStatus = useMemo(() => {
if (!availability) return ''
Expand Down
Expand Up @@ -9,6 +9,7 @@ import { useCallback } from 'react'
import { useAvailability } from './useAvailability.js'
import { useClaimStrategyStatus } from './useClaimStrategyStatus.js'
import { useSignedMessage } from './useSignedMessage.js'
import { useParseRedPacket } from './useParseRedPacket.js'

/**
* Fetch the red packet info from the chain
Expand All @@ -28,6 +29,7 @@ export function useAvailabilityComputed(account: string, payload: RedPacketJSONP
chainId: parsedChainId,
},
)
const parsed = useParseRedPacket(parsedChainId)
const checkAvailability = recheckAvailability as (
options?: RefetchOptions,
) => Promise<QueryObserverResult<typeof availability>>
Expand Down Expand Up @@ -57,7 +59,7 @@ export function useAvailabilityComputed(account: string, payload: RedPacketJSONP
}
const isEmpty = availability.balance === '0'
const isExpired = availability.expired
const isClaimed = availability.claimed_amount !== '0'
const isClaimed = parsed?.redpacket.isClaimed || availability.claimed_amount !== '0'
const isRefunded = isEmpty && availability.claimed < availability.total
const isCreator = isSameAddress(payload?.sender.address ?? '', account)
const isPasswordValid = !!(password && password !== 'PASSWORD INVALID')
Expand Down
@@ -0,0 +1,40 @@
import { useLastRecognizedIdentity, usePostInfoDetails } from '@masknet/plugin-infra/content-script'
import { NetworkPluginID } from '@masknet/shared-base'
import { useChainContext, useNetworkContext } from '@masknet/web3-hooks-base'
import { FireflyRedPacket } from '@masknet/web3-providers'
import type { FireflyRedPacketAPI } from '@masknet/web3-providers/types'
import type { ChainId } from '@masknet/web3-shared-evm'
import { useQuery, type UseQueryResult } from '@tanstack/react-query'

type T = UseQueryResult
/**
* Parse RedPacket with post info
*/
export function useParseRedPacket(chainId: ChainId) {
const images = usePostInfoDetails.postMetadataImages()
const { pluginID } = useNetworkContext()
const { account } = useChainContext<NetworkPluginID.PLUGIN_EVM>({
chainId,
account: pluginID === NetworkPluginID.PLUGIN_EVM ? undefined : '',
})
const source = usePostInfoDetails.source()
const me = useLastRecognizedIdentity()
const myProfileId = me?.profileId

const query = useQuery({
enabled: images.length > 0,
queryKey: ['red-packet', 'parse', source, images[0], account],
queryFn: async () => {
const platform = source?.toLowerCase() as FireflyRedPacketAPI.PlatformType
return FireflyRedPacket.parse({
image: {
imageUrl: images[0],
},
walletAddress: account,
platform,
profileId: myProfileId,
})
},
})
return query.data
}
Expand Up @@ -22,6 +22,7 @@ export function useSignedMessage(
needLensAndFarcasterHandle: true,
platform,
profileId: me?.profileId,
handle: me?.identifier?.userId,
lensToken: me?.lensToken,
farcasterMessage: me?.farcasterMessage as HexString,
farcasterSigner: me?.farcasterSigner as HexString,
Expand Down
4 changes: 3 additions & 1 deletion packages/shared/src/UI/components/Alert/index.tsx
Expand Up @@ -30,7 +30,9 @@ export const Alert = memo(function Alert({ className, children, open, onClose, .
return (
<Box className={cx(classes.alert, className)} {...rest}>
<Icons.Info size={20} />
<Typography fontSize={14}>{children}</Typography>
<Typography fontSize={14} component="div">
{children}
</Typography>
{onClose ?
<Icons.Close size={20} onClick={onClose} />
: null}
Expand Down
27 changes: 27 additions & 0 deletions packages/web3-providers/src/Firefly/RedPacket.ts
Expand Up @@ -26,6 +26,14 @@ function fetchFireflyJSON<T>(url: string, init?: RequestInit): Promise<T> {
}

export class FireflyRedPacket {
static async parse(options: FireflyRedPacketAPI.ParseOptions) {
const url = urlcat(FIREFLY_ROOT_URL, '/v1/misc/redpacket/parse')
const { data } = await fetchFireflyJSON<FireflyRedPacketAPI.ParseResponse>(url, {
method: 'POST',
body: JSON.stringify(options),
})
return data
}
static async getPayloadUrls(from: string, amount?: string, type?: string, symbol?: string, decimals?: number) {
const url = urlcat(FIREFLY_ROOT_URL, '/v1/redpacket/themeList')
const { data } = await fetchJSON<FireflyRedPacketAPI.ThemeListResponse>(url)
Expand Down Expand Up @@ -208,4 +216,23 @@ export class FireflyRedPacket {
body: JSON.stringify(options),
})
}
static async finishClaiming(
rpid: string,
platform: FireflyRedPacketAPI.PlatformType,
profileId: string,
handle: string,
txHash: string,
) {
const url = urlcat(FIREFLY_ROOT_URL, '/v1/redpacket/finishClaiming')
return fetchFireflyJSON<FireflyRedPacketAPI.Response<string>>(url, {
method: 'POST',
body: JSON.stringify({
rpid,
claimPlatform: platform,
claimProfileId: profileId,
claimHandle: handle,
txHash,
}),
})
}
}
3 changes: 2 additions & 1 deletion packages/web3-providers/src/Web3/Base/apis/ChainResolver.ts
Expand Up @@ -16,11 +16,12 @@ export class ChainResolver<ChainId, SchemaType, NetworkType> {
* Guess chain id by name, it's not perfectly accurate
*/
chainId(name: string) {
if (!name) return
return this.descriptors().find((x) =>
[x.name, x.type as string, x.fullName, x.shortName]
.map((x) => x?.toLowerCase())
.filter(Boolean)
.includes(name?.toLowerCase()),
.includes(name.toLowerCase()),
)?.chainId
}
chainName(chainId: ChainId) {
Expand Down
36 changes: 35 additions & 1 deletion packages/web3-providers/src/types/Firefly.ts
Expand Up @@ -287,7 +287,7 @@ export namespace FireflyRedPacketAPI {
is_default?: boolean
}

interface Response<T> {
export interface Response<T> {
code: number
data: T
}
Expand All @@ -308,6 +308,40 @@ export namespace FireflyRedPacketAPI {

export type ClaimHistoryResponse = Response<RedPacketClaimListInfo>

export interface ParseOptions {
text?: string
image?: {
imageUrl: string
}
walletAddress?: string
platform?: PlatformType
profileId?: string
}
export interface ParseResult {
content: string
/** only `text` for now */
type: string
/** only 1 for now */
version: number
serializable: true
meta: object
redpacket: {
/** the same as meta */
payload: object
canClaim: boolean
canRefund: boolean
canSend: boolean
isPasswordValid: boolean
isClaimed: boolean
isEmpty: boolean
isExpired: boolean
isRefunded: boolean
claimedNumber: number
claimedAmount: string
}
}
export type ParseResponse = Response<ParseResult>

export type CheckClaimStrategyStatusOptions = {
rpid: string
profile: {
Expand Down

0 comments on commit df51410

Please sign in to comment.