Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: v4 claim tokens as erc20 #4478

Merged
merged 2 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
@@ -1,11 +1,15 @@
import { t, Trans } from '@lingui/macro'
import { waitForTransactionReceipt } from '@wagmi/core'
import TransactionModal from 'components/modals/TransactionModal'
import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext'
import useNameOfERC20 from 'hooks/ERC20/useNameOfERC20'
import { useJBContractContext, useReadJbTokensTokenOf } from 'juice-sdk-react'
import { useJBContractContext, useReadJbTokensTokenOf, useWriteJbControllerSendReservedTokensToSplitsOf } from 'juice-sdk-react'
import SplitList from 'packages/v4/components/SplitList/SplitList'
import useProjectOwnerOf from 'packages/v4/hooks/useV4ProjectOwnerOf'
import { useV4ReservedSplits } from 'packages/v4/hooks/useV4ReservedSplits'
import { useState } from 'react'
import { wagmiConfig } from 'packages/v4/wagmiConfig'
import { useContext, useState } from 'react'
import { emitErrorNotification } from 'utils/notifications'
import { tokenSymbolText } from 'utils/tokenSymbolText'
import { useV4ReservedTokensSubPanel } from './hooks/useV4ReservedTokensSubPanel'

Expand All @@ -18,7 +22,9 @@ export default function V4DistributeReservedTokensModal({
onCancel?: VoidFunction
onConfirmed?: VoidFunction
}) {
const { projectId } = useJBContractContext()
const { addTransaction } = useContext(TxHistoryContext)

const { projectId, contracts } = useJBContractContext()
const { splits: reservedTokensSplits } = useV4ReservedSplits()
const { data: projectOwnerAddress } = useProjectOwnerOf()
const { data: tokenAddress } = useReadJbTokensTokenOf()
Expand All @@ -28,30 +34,47 @@ export default function V4DistributeReservedTokensModal({
const [transactionPending, setTransactionPending] = useState<boolean>()

// const distributeReservedTokensTx = useDistributeReservedTokens()
const { writeContractAsync: writeSendReservedTokens, data } =
useWriteJbControllerSendReservedTokensToSplitsOf()

const { pendingReservedTokens, pendingReservedTokensFormatted } =
useV4ReservedTokensSubPanel()

async function distributeReservedTokens() {
async function sendReservedTokens() {
if (
// !payoutLimitAmountCurrency ||
// !distributionAmount ||
!contracts.controller.data ||
!projectId
)
return

setLoading(true)

// const txSuccessful = await distributeReservedTokensTx(
// {},
// {
// onDone: () => {
// setTransactionPending(true)
// },
// onConfirmed: () => {
// setLoading(false)
// setTransactionPending(false)
// onConfirmed?.()
// },
// },
// )

// if (!txSuccessful) {
setLoading(false)
setTransactionPending(false)
// }
const args = [
BigInt(projectId)
] as const

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

addTransaction?.('Send reserved tokens', { hash })
await waitForTransactionReceipt(wagmiConfig, {
hash,
})

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

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

const tokenTextPlural = tokenSymbolText({
Expand All @@ -70,7 +93,7 @@ export default function V4DistributeReservedTokensModal({
<TransactionModal
title={<Trans>Send reserved {tokenTextPlural}</Trans>}
open={open}
onOk={() => distributeReservedTokens()}
onOk={() => sendReservedTokens()}
okText={t`Send ${tokenTextPlural}`}
connectWalletText={t`Connect wallet to send reserved ${tokenTextPlural}`}
confirmLoading={loading}
Expand Down
Loading
Loading