Skip to content

Commit

Permalink
feat: v4 claim tokens as erc20 (#4478)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyd-eth authored Sep 30, 2024
1 parent 2fdbedf commit 999f6e6
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 46 deletions.
5 changes: 3 additions & 2 deletions src/packages/v4/hooks/useProjectHasErc20Token.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useReadJbTokensTokenOf } from 'juice-sdk-react'
import { useJBTokenContext } from 'juice-sdk-react'
import { isZeroAddress } from 'utils/address'

export const useProjectHasErc20Token = () => {
const { data: tokenAddress } = useReadJbTokensTokenOf()
const { token } = useJBTokenContext()
const tokenAddress = token?.data?.address

return Boolean(tokenAddress && !isZeroAddress(tokenAddress))
}
14 changes: 8 additions & 6 deletions src/packages/v4/hooks/useV4IssueErc20TokenTx.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useCallback, useContext } from 'react'

import { waitForTransactionReceipt } from '@wagmi/core'
import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext'
import { useJBContractContext, useWriteJbControllerDeployErc20For } from 'juice-sdk-react'
import { Address, zeroAddress } from 'viem'
import { BaseTxOpts } from '../models/transactions'
import { wagmiConfig } from '../wagmiConfig'

export function useV4IssueErc20TokenTx() {
const { addTransaction } = useContext(TxHistoryContext)
Expand Down Expand Up @@ -47,12 +49,12 @@ export function useV4IssueErc20TokenTx() {

onTransactionPendingCallback(hash)
addTransaction?.('Launch ERC20 Token', { hash })
// const transactionReceipt: WaitForTransactionReceiptReturnType = await waitForTransactionReceipt(
// wagmiConfig,
// {
// hash,
// },
// )
await waitForTransactionReceipt(
wagmiConfig,
{
hash,
},
)

onTransactionConfirmedCallback()
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function V4DistributePayoutsModal({
const [loading, setLoading] = useState<boolean>()
const [distributionAmount, setDistributionAmount] = useState<string>()

const { writeContractAsync: writeSendPayouts, data } =
const { writeContractAsync: writeSendPayouts } =
useWriteJbMultiTerminalSendPayoutsOf()

async function executeDistributePayoutsTx() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { WarningOutlined } from '@ant-design/icons'
import { t, Trans } from '@lingui/macro'
import { waitForTransactionReceipt } from '@wagmi/core'
import { Descriptions, Form } from 'antd'
import InputAccessoryButton from 'components/buttons/InputAccessoryButton'
import EthereumAddress from 'components/EthereumAddress'
import FormattedNumberInput from 'components/inputs/FormattedNumberInput'
import TransactionModal from 'components/modals/TransactionModal'
import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext'
import useSymbolOfERC20 from 'hooks/ERC20/useSymbolOfERC20'
import { useWallet } from 'hooks/Wallet'
import { NativeTokenValue, useJBContractContext, useReadJbTokensCreditBalanceOf, useReadJbTokensTokenOf, useWriteJbControllerClaimTokensFor } from 'juice-sdk-react'
import { useProjectHasErc20Token } from 'packages/v4/hooks/useProjectHasErc20Token'
import { wagmiConfig } from 'packages/v4/wagmiConfig'
import { useContext, useLayoutEffect, useState } from 'react'
import { fromWad, parseWad } from 'utils/format/formatNumber'
import { emitErrorNotification } from 'utils/notifications'
import { tokenSymbolText } from 'utils/tokenSymbolText'
import { zeroAddress } from 'viem'

export function V4ClaimTokensModal({
open,
onCancel,
onConfirmed,
}: {
open?: boolean
onCancel?: VoidFunction
onConfirmed?: VoidFunction
}) {
const { projectId, contracts } = useJBContractContext()
const { addTransaction } = useContext(TxHistoryContext)

const { data: tokenAddress } = useReadJbTokensTokenOf()
const { data: tokenSymbol } = useSymbolOfERC20(tokenAddress)

const [loading, setLoading] = useState<boolean>()
const [transactionPending, setTransactionPending] = useState<boolean>()
const [claimAmount, setClaimAmount] = useState<string>()

const { userAddress } = useWallet()

const { writeContractAsync: writeClaimTokens } =
useWriteJbControllerClaimTokensFor()

const hasIssuedTokens = useProjectHasErc20Token()

const { data: unclaimedBalance } = useReadJbTokensCreditBalanceOf({
args: [userAddress ?? zeroAddress, projectId],
})

useLayoutEffect(() => {
setClaimAmount(fromWad(unclaimedBalance))
}, [unclaimedBalance])

async function executeClaimTokensTx() {
if (
!contracts.controller.data ||
!claimAmount ||
!userAddress ||
!projectId
)
return

setLoading(true)

const args = [
userAddress,
projectId,
parseWad(claimAmount).toBigInt(),
userAddress
] as const

try {
// SIMULATE TX:
// const encodedData = encodeFunctionData({
// abi: jbControllerAbi,
// functionName: 'claimTokensFor',
// args,
// })
// console.log('encodedData:', encodedData)
// console.log('contract:', contracts.controller.data)

const hash = await writeClaimTokens({
address: contracts.controller.data,
args,
})
setTransactionPending(true)

addTransaction?.('Claim tokens as ERC20', { hash })
await waitForTransactionReceipt(wagmiConfig, {
hash,
})

setLoading(false)
setTransactionPending(false)
onConfirmed?.()
} catch (e) {
setLoading(false)

emitErrorNotification((e as unknown as Error).message)
}
}

const tokenTextLong = tokenSymbolText({
tokenSymbol,
plural: true,
includeTokenWord: true,
})

const tokenTextShort = tokenSymbolText({
tokenSymbol,
plural: true,
})

return (
<TransactionModal
title={t`Claim ${tokenTextShort} as ERC-20 tokens`}
connectWalletText={t`Connect wallet to claim`}
open={open}
onOk={executeClaimTokensTx}
okText={t`Claim ${tokenTextShort}`}
confirmLoading={loading}
transactionPending={transactionPending}
okButtonProps={{ disabled: parseWad(claimAmount).eq(0) }}
onCancel={onCancel}
width={600}
centered
>
<div className="flex flex-col gap-6">
{!hasIssuedTokens && (
<div className="bg-smoke-100 p-2 dark:bg-slate-600">
<WarningOutlined />{' '}
<Trans>
Tokens cannot be claimed because the project owner has not issued
an ERC-20 for this project.
</Trans>
</div>
)}

<div>
<p>
<Trans>
Claiming {tokenTextLong} will convert your {tokenTextShort}{' '}
balance to ERC-20 tokens and mint them to your wallet.
</Trans>
</p>
<p className="font-medium">
<Trans>
If you're not sure if you need to claim, you probably don't.
</Trans>
</p>
<p>
<Trans>
You can redeem your {tokenTextLong} for ETH without claiming them.
You can transfer your unclaimed {tokenTextLong} to another address
from the Tools menu, which can be accessed from the wrench icon in
the upper right-hand corner of this project.
</Trans>
</p>
</div>

<Descriptions size="small" layout="horizontal" column={1}>
<Descriptions.Item
label={<Trans>Your unclaimed {tokenTextLong}</Trans>}
>
<NativeTokenValue wei={unclaimedBalance ?? 0n} />
</Descriptions.Item>

{hasIssuedTokens && tokenSymbol && (
<Descriptions.Item
label={<Trans>{tokenSymbol} ERC-20 address</Trans>}
>
<EthereumAddress address={tokenAddress} />
</Descriptions.Item>
)}
</Descriptions>

<Form layout="vertical">
<Form.Item label={t`Amount of ERC-20 tokens to claim`}>
<FormattedNumberInput
min={0}
max={parseFloat(fromWad(unclaimedBalance))}
disabled={!hasIssuedTokens}
placeholder="0"
value={claimAmount}
accessory={
<InputAccessoryButton
content={t`MAX`}
onClick={() => setClaimAmount(fromWad(unclaimedBalance))}
/>
}
onChange={val => setClaimAmount(val)}
/>
</Form.Item>
</Form>
</div>
</TransactionModal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import { useJBContractContext } from 'juice-sdk-react'
import { V4TokenHoldersModal } from 'packages/v4/components/modals/V4TokenHoldersModal/V4TokenHoldersModal'
import { v4ProjectRoute } from 'packages/v4/utils/routes'
import { useCallback, useState } from 'react'
import { reloadWindow } from 'utils/windowUtils'
import { useChainId } from 'wagmi'
import { useV4TokensPanel } from './hooks/useV4TokensPanel'
import { useV4YourBalanceMenuItems } from './hooks/useV4YourBalanceMenuItems'
import { V4ClaimTokensModal } from './V4ClaimTokensModal'
import { V4ReservedTokensSubPanel } from './V4ReservedTokensSubPanel'

export const V4TokensPanel = () => {
Expand Down Expand Up @@ -45,7 +47,7 @@ export const V4TokensPanel = () => {
items,
// redeemModalVisible,
// setRedeemModalVisible,
// claimTokensModalVisible,
claimTokensModalVisible,
setClaimTokensModalVisible,
// mintModalVisible,
// setMintModalVisible,
Expand All @@ -68,7 +70,7 @@ export const V4TokensPanel = () => {
title={t`Your balance`}
description={
<span className="flex flex-col justify-between gap-5 md:flex-row md:items-center">
<Trans>{userTokenBalance.format()} tokens</Trans>
<Trans>{userTokenBalance.format(8)} tokens</Trans>
<div className="flex flex-col justify-between gap-5 md:flex-row md:items-center md:gap-4">
{/* {projectHasErc20Token && (
<Button
Expand Down Expand Up @@ -124,7 +126,7 @@ export const V4TokensPanel = () => {
title={t`Total supply`}
description={
<span>
{totalSupply.format()} {projectToken}
{totalSupply.format(8)} {projectToken}
</span>
}
/>
Expand All @@ -149,13 +151,13 @@ export const V4TokensPanel = () => {
open={redeemModalVisible}
onCancel={() => setRedeemModalVisible(false)}
onConfirmed={reloadWindow}
/>
<V2V3ClaimTokensModal
/> */}
<V4ClaimTokensModal
open={claimTokensModalVisible}
onCancel={() => setClaimTokensModalVisible(false)}
onConfirmed={reloadWindow}
/>
<V2V3MintModal
{/*<V2V3MintModal
open={mintModalVisible}
onCancel={() => setMintModalVisible(false)}
onConfirmed={reloadWindow}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useWallet } from 'hooks/Wallet'
import { JBProjectToken } from 'juice-sdk-core'
import { useJBContractContext, useJBTokenContext, useReadJbTokensTotalBalanceOf } from 'juice-sdk-react'
import { useProjectHasErc20Token } from 'packages/v4/hooks/useProjectHasErc20Token'
import { useV4TotalTokenSupply } from 'packages/v4/hooks/useV4TotalTokenSupply'
import { useV4WalletHasPermission } from 'packages/v4/hooks/useV4WalletHasPermission'
import { V4OperatorPermission } from 'packages/v4/models/v4Permissions'
import { useMemo } from 'react'
import { isZeroAddress } from 'utils/address'
import { tokenSymbolText } from 'utils/tokenSymbolText'
import { zeroAddress } from 'viem'

Expand All @@ -26,7 +26,7 @@ export const useV4TokensPanel = () => {
const hasDeployErc20Permission = useV4WalletHasPermission(
V4OperatorPermission.DEPLOY_ERC20,
)
const projectHasErc20Token = Boolean(tokenAddress && !isZeroAddress(tokenAddress))
const projectHasErc20Token = useProjectHasErc20Token()

const { data: _userTokenBalance, isLoading: userTokenBalanceLoading } =
useReadJbTokensTotalBalanceOf({
Expand Down
Loading

0 comments on commit 999f6e6

Please sign in to comment.