From 779b765016123ba41546dd8b06a76279b601a9d0 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Tue, 10 Sep 2024 07:49:39 +0800 Subject: [PATCH] feat: v4 deploy erc20 (#4453) --- src/locales/messages.pot | 3 + .../v4/hooks/useProjectHasErc20Token.ts | 8 ++ .../v4/hooks/useV4IssueErc20TokenTx.ts | 67 ++++++++++ src/packages/v4/models/transactions.ts | 5 + .../CreateErc20TokenSettingsPage.tsx | 125 ++++++++++++++++++ .../ProjectSettingsContent.tsx | 3 +- 6 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/packages/v4/hooks/useProjectHasErc20Token.ts create mode 100644 src/packages/v4/hooks/useV4IssueErc20TokenTx.ts create mode 100644 src/packages/v4/models/transactions.ts create mode 100644 src/packages/v4/views/V4ProjectSettings/CreateErc20TokenSettingsPage.tsx diff --git a/src/locales/messages.pot b/src/locales/messages.pot index 5f24b83532..7abc7cc37a 100644 --- a/src/locales/messages.pot +++ b/src/locales/messages.pot @@ -1313,6 +1313,9 @@ msgstr "" msgid "Migrate payment terminal" msgstr "" +msgid "Failed to create ERC20 token: {0}" +msgstr "" + msgid "Payments" msgstr "" diff --git a/src/packages/v4/hooks/useProjectHasErc20Token.ts b/src/packages/v4/hooks/useProjectHasErc20Token.ts new file mode 100644 index 0000000000..a38b254f4a --- /dev/null +++ b/src/packages/v4/hooks/useProjectHasErc20Token.ts @@ -0,0 +1,8 @@ +import { useReadJbTokensTokenOf } from 'juice-sdk-react' +import { isZeroAddress } from 'utils/address' + +export const useProjectHasErc20Token = () => { + const { data: tokenAddress } = useReadJbTokensTokenOf() + + return Boolean(tokenAddress && !isZeroAddress(tokenAddress)) +} diff --git a/src/packages/v4/hooks/useV4IssueErc20TokenTx.ts b/src/packages/v4/hooks/useV4IssueErc20TokenTx.ts new file mode 100644 index 0000000000..237bb92a42 --- /dev/null +++ b/src/packages/v4/hooks/useV4IssueErc20TokenTx.ts @@ -0,0 +1,67 @@ +import { useCallback, useContext } from 'react' + +import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext' +import { useJBContractContext, useWriteJbTokensDeployErc20For } from 'juice-sdk-react' +import { zeroAddress } from 'viem' +import { BaseTxOpts } from '../models/transactions' + +export function useV4IssueErc20TokenTx() { + const { addTransaction } = useContext(TxHistoryContext) + const { projectId, contracts } = useJBContractContext() + + const { writeContractAsync: deployErc20 } = useWriteJbTokensDeployErc20For() + + return useCallback ( + async ({ name, symbol }: { + name: string + symbol: string + }, + { + onTransactionPending: onTransactionPendingCallback, + onTransactionConfirmed: onTransactionConfirmedCallback, + onTransactionError: onTransactionErrorCallback, + }: BaseTxOpts + ) => { + if ( + !projectId || !name || !symbol + ) { + return + } + + const args = [projectId, name, symbol, `${zeroAddress}000000000000000000000000`] as const + + try { + // SIMULATE TX: + // const encodedData = encodeFunctionData({ + // abi: jbTokensAbi, // ABI of the contract + // functionName: 'deployErc20For', + // args, + // }) + + const hash = await deployErc20({ + args, + }) + + onTransactionPendingCallback(hash) + addTransaction?.('Edit Ruleset', { hash }) + // const transactionReceipt: WaitForTransactionReceiptReturnType = await waitForTransactionReceipt( + // wagmiConfig, + // { + // hash, + // }, + // ) + + onTransactionConfirmedCallback() + } catch (e) { + onTransactionErrorCallback( + (e as Error) ?? new Error('Transaction failed'), + ) + } + }, + [ + deployErc20, + projectId, + addTransaction, + ], + ) +} diff --git a/src/packages/v4/models/transactions.ts b/src/packages/v4/models/transactions.ts new file mode 100644 index 0000000000..2aa9d8411e --- /dev/null +++ b/src/packages/v4/models/transactions.ts @@ -0,0 +1,5 @@ +export interface BaseTxOpts { + onTransactionPending: (hash?: `0x${string}`) => void + onTransactionConfirmed: (hash?: `0x${string}`) => void + onTransactionError: (error: Error) => void +} diff --git a/src/packages/v4/views/V4ProjectSettings/CreateErc20TokenSettingsPage.tsx b/src/packages/v4/views/V4ProjectSettings/CreateErc20TokenSettingsPage.tsx new file mode 100644 index 0000000000..d2fded6190 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/CreateErc20TokenSettingsPage.tsx @@ -0,0 +1,125 @@ +import { Trans, t } from '@lingui/macro' +import { Button, Form, Input } from 'antd' +import { IssueErc20TokenTxArgs } from 'components/buttons/IssueErc20TokenButton' +import TransactionModal from 'components/modals/TransactionModal' +import { ISSUE_ERC20_EXPLANATION } from 'components/strings' +import { useProjectHasErc20Token } from 'packages/v4/hooks/useProjectHasErc20Token' +import { useV4IssueErc20TokenTx } from 'packages/v4/hooks/useV4IssueErc20TokenTx' +import { useV4WalletHasPermission } from 'packages/v4/hooks/useV4WalletHasPermission' +import { V4OperatorPermission } from 'packages/v4/models/v4Permissions' +import { useState } from 'react' +import { emitErrorNotification } from 'utils/notifications' + +export function CreateErc20TokenSettingsPage() { + const [form] = Form.useForm() + const [loading, setLoading] = useState() + const [transactionModalOpen, setTransactionModalOpen] = + useState(false) + const [transactionPending, setTransactionPending] = useState(false) +const issueErc20TokenTx = useV4IssueErc20TokenTx() + const projectHasErc20Token = useProjectHasErc20Token() + const hasIssueTicketsPermission = useV4WalletHasPermission( + V4OperatorPermission.DEPLOY_ERC20, + ) + + const canCreateErc20Token = !projectHasErc20Token && hasIssueTicketsPermission + + async function onSetENSNameFormSaved(values: IssueErc20TokenTxArgs) { + await form.validateFields() + + if (!issueErc20TokenTx) { + emitErrorNotification(t`ERC20 transaction not ready. Try again.`) + return + } + + setLoading(true) + + issueErc20TokenTx( + { name: values.name, symbol: values.symbol }, + { + onTransactionPending: () => { + setTransactionPending(true) + setTransactionModalOpen(true) + }, + onTransactionConfirmed: () => { + setTransactionPending(false) + setTransactionModalOpen(false) + setLoading(false) + setTimeout(() => { + window.location.reload() + }, 1000) + }, + onTransactionError: (e: Error) => { + setTransactionPending(false) + setTransactionModalOpen(false) + setLoading(false) + emitErrorNotification(e.message) + emitErrorNotification( + t`Failed to create ERC20 token: ${e.message}`, + ) + }, + }, + ) + } + + if (!canCreateErc20Token) { + return ( +
+

+ Token is already created or you do not have permission to create it. +

+
+ ) + } + + return ( + <> +

{ISSUE_ERC20_EXPLANATION}

+
+ + + + + + form.setFieldsValue({ symbol: e.target.value.toUpperCase() }) + } + /> + + +
+ + setTransactionModalOpen(false)} + onOk={() => setTransactionModalOpen(false)} + confirmLoading={loading} + centered + /> + + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx index b15fb65127..d49acdaf0a 100644 --- a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx +++ b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx @@ -5,6 +5,7 @@ import { Button, Layout } from 'antd' import Link from 'next/link' import { useMemo } from 'react' import { twJoin } from 'tailwind-merge' +import { CreateErc20TokenSettingsPage } from './CreateErc20TokenSettingsPage' import { EditCyclePage } from './EditCyclePage/EditCyclePage' import { useSettingsPagePath } from './hooks/useSettingsPagePath' import { ProjectDetailsSettingsPage } from './ProjectDetailsSettingsPage/ProjectDetailsSettingsPage' @@ -23,7 +24,7 @@ const SettingsPageComponents: { transferownership: () => null, //TransferOwnershipSettingsPage, archiveproject: () => null, //ArchiveProjectSettingsPage, heldfees: () => null, //ProcessHeldFeesPage, - createerc20: () => null, //CreateErc20TokenSettingsPage, + createerc20: CreateErc20TokenSettingsPage, } const V4SettingsPageKeyTitleMap = (