Skip to content

Commit

Permalink
feat: V4 project owner mint tokens (#4479)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyd-eth authored Sep 30, 2024
1 parent 999f6e6 commit 56167b6
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { t, Trans } from '@lingui/macro'
import { Modal } from 'antd'
import EthereumAddress from 'components/EthereumAddress'
import useNameOfERC20 from 'hooks/ERC20/useNameOfERC20'
import { useJBContractContext, useReadJbTokensTokenOf } from 'juice-sdk-react'
import { useJBContractContext, useJBTokenContext } from 'juice-sdk-react'
import { OrderDirection, Participant_OrderBy, ParticipantsDocument } from 'packages/v4/graphql/client/graphql'
import { useSubgraphQuery } from 'packages/v4/graphql/useSubgraphQuery'
import { isZeroAddress } from 'utils/address'
Expand All @@ -19,8 +18,10 @@ export const V4TokenHoldersModal = ({
onClose: VoidFunction
}) => {
const { projectId } = useJBContractContext()
const { data: tokenAddress } = useReadJbTokensTokenOf()
const { data: tokenSymbol } = useNameOfERC20(tokenAddress)

const { token } = useJBTokenContext()
const tokenAddress = token?.data?.address
const tokenSymbol = token?.data?.symbol

const { data: totalTokenSupply } = useV4TotalTokenSupply()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Callout } from 'components/Callout/Callout'
import FormattedNumberInput from 'components/inputs/FormattedNumberInput'
import TransactionModal from 'components/modals/TransactionModal'
import { FEES_EXPLANATION } from 'components/strings'
import { useProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext'
import { NATIVE_TOKEN, NATIVE_TOKEN_DECIMALS } from 'juice-sdk-core'
import {
Expand Down Expand Up @@ -35,8 +34,7 @@ export default function V4DistributePayoutsModal({
const { data: payoutSplits } = useV4CurrentPayoutSplits()
const { data: payoutLimit } = usePayoutLimit()
const { distributableAmount: distributable } = useV4DistributableAmount()
const { projectId } = useProjectMetadataContext()
const { contracts } = useJBContractContext()
const { contracts, projectId } = useJBContractContext()
const { addTransaction } = useContext(TxHistoryContext)

const payoutLimitAmountCurrency = payoutLimit?.currency ?? V4_CURRENCY_ETH
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ 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 { Ether } from 'juice-sdk-core'
import { useJBContractContext, useJBTokenContext, useReadJbTokensCreditBalanceOf, useWriteJbControllerClaimTokensFor } from 'juice-sdk-react'
import { useProjectHasErc20Token } from 'packages/v4/hooks/useProjectHasErc20Token'
import { wagmiConfig } from 'packages/v4/wagmiConfig'
import { useContext, useLayoutEffect, useState } from 'react'
Expand All @@ -30,8 +30,9 @@ export function V4ClaimTokensModal({
const { projectId, contracts } = useJBContractContext()
const { addTransaction } = useContext(TxHistoryContext)

const { data: tokenAddress } = useReadJbTokensTokenOf()
const { data: tokenSymbol } = useSymbolOfERC20(tokenAddress)
const { token } = useJBTokenContext()
const tokenAddress = token?.data?.address
const tokenSymbol = token?.data?.symbol

const [loading, setLoading] = useState<boolean>()
const [transactionPending, setTransactionPending] = useState<boolean>()
Expand Down Expand Up @@ -163,7 +164,7 @@ export function V4ClaimTokensModal({
<Descriptions.Item
label={<Trans>Your unclaimed {tokenTextLong}</Trans>}
>
<NativeTokenValue wei={unclaimedBalance ?? 0n} />
{new Ether(unclaimedBalance ?? 0n).format()}{' '}{tokenTextShort}
</Descriptions.Item>

{hasIssuedTokens && tokenSymbol && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ 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, useWriteJbControllerSendReservedTokensToSplitsOf } from 'juice-sdk-react'
import { useJBContractContext, useJBTokenContext, 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'
Expand All @@ -27,8 +26,9 @@ export default function V4DistributeReservedTokensModal({
const { projectId, contracts } = useJBContractContext()
const { splits: reservedTokensSplits } = useV4ReservedSplits()
const { data: projectOwnerAddress } = useProjectOwnerOf()
const { data: tokenAddress } = useReadJbTokensTokenOf()
const { data: tokenSymbol } = useNameOfERC20(tokenAddress)

const { token } = useJBTokenContext()
const tokenSymbol = token?.data?.symbol

const [loading, setLoading] = useState<boolean>()
const [transactionPending, setTransactionPending] = useState<boolean>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { t } from '@lingui/macro'
import { waitForTransactionReceipt } from '@wagmi/core'
import { Form, Input } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { EthAddressInput } from 'components/inputs/EthAddressInput'
import FormattedNumberInput from 'components/inputs/FormattedNumberInput'
import TransactionModal from 'components/modals/TransactionModal'
import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext'
import { utils } from 'ethers'
import useNameOfERC20 from 'hooks/ERC20/useNameOfERC20'
import { useJBContractContext, useReadJbTokensTokenOf, useWriteJbControllerMintTokensOf } from 'juice-sdk-react'
import { wagmiConfig } from 'packages/v4/wagmiConfig'
import { useContext, useState } from 'react'
import { parseWad } from 'utils/format/formatNumber'
import { emitErrorNotification } from 'utils/notifications'
import { tokenSymbolText } from 'utils/tokenSymbolText'
import { Address } from 'viem'

type MintForm = {
beneficary: string
memo: string
amount: string
}

export function V4MintModal({
open,
onCancel,
onConfirmed,
}: {
open?: boolean
onCancel?: VoidFunction
onConfirmed?: VoidFunction
}) {
const { writeContractAsync: writeMintTokens } =
useWriteJbControllerMintTokensOf()
const [form] = useForm<MintForm>()

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

const { projectId, contracts } = useJBContractContext()
const { addTransaction } = useContext(TxHistoryContext)

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

async function executeMintTx() {
const formValues = form.getFieldsValue(true) as MintForm
const amount = parseWad(formValues.amount ?? '0').toBigInt()
const memo = formValues.memo
const beneficiary = formValues.beneficary as Address

if (
!contracts.controller.data ||
!beneficiary ||
!amount ||
!projectId
)
return

setLoading(true)

const args = [
projectId,
amount,
beneficiary,
memo,
false, //useReservedPercent
] as const

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

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

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

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

const tokensTokenLower = tokenSymbolText({
tokenSymbol,
capitalize: false,
plural: true,
})

const tokensTokenUpper = tokenSymbolText({
tokenSymbol,
capitalize: true,
plural: false,
})

return (
<TransactionModal
open={open}
title={t`Mint ${tokensTokenLower}`}
onOk={executeMintTx}
confirmLoading={loading}
transactionPending={transactionPending}
onCancel={onCancel}
okText={t`Mint ${tokensTokenLower}`}
>
<p>Mint new tokens to a specified address.</p>

<Form layout="vertical" form={form}>
<Form.Item
label={t`Token receiver`}
name="beneficary"
rules={[
{
required: true,
validateTrigger: 'onCreate',
validator: (rule, value) => {
if (!value || !utils.isAddress(value))
return Promise.reject('Not a valid ETH address')
else return Promise.resolve()
},
},
]}
>
<EthAddressInput />
</Form.Item>
<Form.Item
name="amount"
label={t`${tokensTokenUpper} amount`}
extra={t`The amount of tokens to mint to the receiver.`}
rules={[
{
required: true,
validateTrigger: 'onCreate',
validator: (rule, value) => {
if (!value || value === '0') {
return Promise.reject('Invalid value')
}
return Promise.resolve()
},
},
]}
required
>
<FormattedNumberInput placeholder="0" />
</Form.Item>
<Form.Item label="Memo" name="memo">
<Input placeholder="Memo included on-chain (optional)" />
</Form.Item>
</Form>
</TransactionModal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import { v4ProjectRoute } from 'packages/v4/utils/routes'
import { useCallback, useState } from 'react'
import { reloadWindow } from 'utils/windowUtils'
import { useChainId } from 'wagmi'
import { useV4BalanceMenuItemsUserFlags } from './hooks/useV4BalanceMenuItemsUserFlags'
import { useV4TokensPanel } from './hooks/useV4TokensPanel'
import { useV4YourBalanceMenuItems } from './hooks/useV4YourBalanceMenuItems'
import { V4ClaimTokensModal } from './V4ClaimTokensModal'
import { V4MintModal } from './V4MintModal'
import { V4ReservedTokensSubPanel } from './V4ReservedTokensSubPanel'

export const V4TokensPanel = () => {
Expand All @@ -33,6 +35,8 @@ export const V4TokensPanel = () => {
totalSupply,
} = useV4TokensPanel()

const { canMintTokens } = useV4BalanceMenuItemsUserFlags()

const [tokenHolderModalOpen, setTokenHolderModalOpen] = useState(false)
const openTokenHolderModal = useCallback(
() => setTokenHolderModalOpen(true),
Expand All @@ -49,8 +53,8 @@ export const V4TokensPanel = () => {
// setRedeemModalVisible,
claimTokensModalVisible,
setClaimTokensModalVisible,
// mintModalVisible,
// setMintModalVisible,
mintModalVisible,
setMintModalVisible,
// transferUnclaimedTokensModalVisible,
// setTransferUnclaimedTokensModalVisible,
} = useV4YourBalanceMenuItems()
Expand Down Expand Up @@ -92,7 +96,7 @@ export const V4TokensPanel = () => {
</span>
}
kebabMenu={
userTokenBalance.value > 0n
userTokenBalance.value > 0n || canMintTokens
? {
items,
}
Expand Down Expand Up @@ -157,12 +161,12 @@ export const V4TokensPanel = () => {
onCancel={() => setClaimTokensModalVisible(false)}
onConfirmed={reloadWindow}
/>
{/*<V2V3MintModal
<V4MintModal
open={mintModalVisible}
onCancel={() => setMintModalVisible(false)}
onConfirmed={reloadWindow}
/>
<TransferUnclaimedTokensModalWrapper
{/*<TransferUnclaimedTokensModalWrapper
open={transferUnclaimedTokensModalVisible}
onCancel={() => setTransferUnclaimedTokensModalVisible(false)}
onConfirmed={reloadWindow}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ReactNode, useMemo, useState } from 'react'
import { useV4BalanceMenuItemsUserFlags } from './useV4BalanceMenuItemsUserFlags'

export const useV4YourBalanceMenuItems = () => {
const { canBurnTokens, canClaimErcTokens, canMintTokens } =
const { canClaimErcTokens, canMintTokens } =
useV4BalanceMenuItemsUserFlags()

const [redeemModalVisible, setRedeemModalVisible] = useState(false)
Expand Down

0 comments on commit 56167b6

Please sign in to comment.