From f70810703a160f1f17897a3335bda06a6e7850e0 Mon Sep 17 00:00:00 2001 From: johnnyd-eth Date: Tue, 19 Nov 2024 18:11:06 +1100 Subject: [PATCH 1/6] feat: NFTs in v4 create (wip) --- src/packages/v4/components/Create/Create.tsx | 6 +- .../components/Wizard/hooks/useSteps.ts | 5 +- .../hooks/NFT/useDeployNftProject.ts | 9 +- .../hooks/DeployProject/useDeployProject.ts | 76 ++++--- .../v4/contexts/V4ProjectMetadataProvider.tsx | 3 +- .../JB721DelegateVersion/index.ts | 1 + .../useJB721DelegateVersion.ts | 22 ++ .../useNftCollectionMetadataUri.ts | 40 ++++ .../useNftCollectionPricingContext.ts | 31 +++ .../useNftDeployerCanReconfigure.ts | 40 ++++ .../contractReader/useNftFlagsOf.ts | 16 ++ .../contractReader/useNftTiers.ts | 89 ++++++++ .../contracts/useJB721DelegateAbi.ts | 57 +++++ .../useJB721DelegateContractAddress.ts | 72 ++++++ .../contracts/useJB721TieredDelegate.ts | 21 ++ .../contracts/useJB721TieredDelegateStore.ts | 21 ++ .../useJBTiered721DelegateProjectDeployer.ts | 26 +++ .../useProjectJB721DelegateVersion.ts | 20 ++ .../useStoreofJB721TieredDelegate.ts | 28 +++ .../transactor/useAdjustTiersTx.ts | 53 +++++ .../transactor/useLaunchProjectWithNftsTx.ts | 172 ++++++++++++++ ...seReconfigureV2V3FundingCycleWithNftsTx.ts | 212 ++++++++++++++++++ .../useSetNftOperatorPermissionsTx.ts | 28 +++ .../hooks/JB721Delegate/useHasNftRewards.ts | 14 ++ .../JB721Delegate/useNftAccountBalance.ts | 25 +++ .../JB721Delegate/useNftCollectionMetadata.ts | 25 +++ .../v4/hooks/JB721Delegate/useNftCredits.ts | 22 ++ .../useNftRewardsEnabledForPay.ts | 14 ++ src/packages/v4/hooks/useLaunchProjectTx.ts | 13 +- src/packages/v4/models/fundAccessLimits.ts | 12 + src/packages/v4/models/nfts.ts | 48 ++++ src/packages/v4/models/splits.ts | 6 + src/packages/v4/models/terminals.ts | 10 + src/packages/v4/utils/launchProject.ts | 133 ----------- .../v4/utils/launchProjectTransformers.ts | 179 +++++++++++++++ .../V4ProjectTabs/V4ProjectTabs.tsx | 9 +- 36 files changed, 1373 insertions(+), 185 deletions(-) create mode 100644 src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/useNftCredits.ts create mode 100644 src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts create mode 100644 src/packages/v4/models/fundAccessLimits.ts create mode 100644 src/packages/v4/models/nfts.ts create mode 100644 src/packages/v4/models/splits.ts create mode 100644 src/packages/v4/models/terminals.ts delete mode 100644 src/packages/v4/utils/launchProject.ts create mode 100644 src/packages/v4/utils/launchProjectTransformers.ts diff --git a/src/packages/v4/components/Create/Create.tsx b/src/packages/v4/components/Create/Create.tsx index e4bd960031..12d0fe009e 100644 --- a/src/packages/v4/components/Create/Create.tsx +++ b/src/packages/v4/components/Create/Create.tsx @@ -10,7 +10,9 @@ import Loading from 'components/Loading' import { readNetwork } from 'constants/networks' import { NetworkName } from 'models/networkName' import { useRouter } from 'next/router' +import { CreateBadge } from './components/CreateBadge' import { FundingCyclesPage } from './components/pages/FundingCycles/FundingCyclesPage' +import { NftRewardsPage } from './components/pages/NftRewards/NftRewardsPage' import { PayoutsPage } from './components/pages/PayoutsPage/PayoutsPage' import { ProjectDetailsPage } from './components/pages/ProjectDetails/ProjectDetailsPage' import { ProjectTokenPage } from './components/pages/ProjectToken/ProjectTokenPage' @@ -86,7 +88,7 @@ export function Create() { > - {/* @@ -99,7 +101,7 @@ export function Create() { } > - */} + Edit Deadline} diff --git a/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts b/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts index 2c58a52f47..89862b1bdc 100644 --- a/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts +++ b/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts @@ -1,6 +1,7 @@ +import { useCallback, useContext, useMemo } from 'react' + import { t } from '@lingui/macro' import { CreatePage } from 'models/createPage' -import { useCallback, useContext, useMemo } from 'react' import { useAppSelector } from 'redux/hooks/useAppSelector' import { useEditingCreateFurthestPageReached } from 'redux/hooks/useEditingCreateFurthestPageReached' import { WizardContext } from '../contexts/WizardContext' @@ -11,7 +12,7 @@ const stepNames = (): Record => { fundingCycles: t`Rulesets`, payouts: t`Payouts`, projectToken: t`Token`, - // nftRewards: t`NFTs`, + nftRewards: t`NFTs`, reconfigurationRules: t`Deadline`, reviewDeploy: t`Deploy`, } diff --git a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts index 75ab2342d8..3159f2057f 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts @@ -1,6 +1,3 @@ -import { TransactionCallbacks } from 'models/transaction' -import { useLaunchProjectWithNftsTx } from 'packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx' -import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' import { useCallback, useMemo } from 'react' import { useAppSelector, @@ -8,6 +5,10 @@ import { useEditingV2V3FundingCycleDataSelector, useEditingV2V3FundingCycleMetadataSelector, } from 'redux/hooks/useAppSelector' + +import { TransactionCallbacks } from 'models/transaction' +import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' +import { useLaunchProjectWithNftsTx } from 'packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx' import { DEFAULT_NFT_FLAGS } from 'redux/slices/editingV2Project' import { NFT_FUNDING_CYCLE_METADATA_OVERRIDES } from 'utils/nftFundingCycleMetadataOverrides' import { buildJB721TierParams } from 'utils/nftRewards' @@ -20,7 +21,7 @@ import { buildJB721TierParams } from 'utils/nftRewards' * @returns A function that deploys a project with NFT rewards. */ export const useDeployNftProject = () => { - const launchProjectWithNftsTx = useLaunchProjectWithNftsTx() + const launchProjectWithNftsTx = useLaunchProjectWithNftsTx() // v4ify this const { projectMetadata, nftRewards, diff --git a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts index a0de85a48a..b97afca36b 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts @@ -1,15 +1,19 @@ -import { uploadProjectMetadata } from 'lib/api/ipfs' -import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx' import { useCallback, useState } from 'react' -import { useAppDispatch } from 'redux/hooks/useAppDispatch' import { useAppSelector, useEditingV2V3FundAccessConstraintsSelector, useEditingV2V3FundingCycleDataSelector, useEditingV2V3FundingCycleMetadataSelector, } from 'redux/hooks/useAppSelector' + +import { uploadProjectMetadata } from 'lib/api/ipfs' +import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx' +import { useAppDispatch } from 'redux/hooks/useAppDispatch' import { editingV2ProjectActions } from 'redux/slices/editingV2Project' import { emitErrorNotification } from 'utils/notifications' +import { useDeployNftProject } from './hooks/NFT/useDeployNftProject' +import { useIsNftProject } from './hooks/NFT/useIsNftProject' +import { useUploadNftRewards } from './hooks/NFT/useUploadNftRewards' import { useDeployStandardProject } from './hooks/useDeployStandardProject' const JUICEBOX_DOMAIN = 'juicebox' @@ -22,9 +26,9 @@ export const useDeployProject = () => { const [isDeploying, setIsDeploying] = useState(false) const [transactionPending, setTransactionPending] = useState() - // const isNftProject = useIsNftProject() - // const uploadNftRewards = useUploadNftRewards() - // const deployNftProject = useDeployNftProject() + const isNftProject = useIsNftProject() + const uploadNftRewards = useUploadNftRewards() + const deployNftProject = useDeployNftProject() const deployStandardProject = useDeployStandardProject() @@ -89,15 +93,15 @@ export const useDeployProject = () => { setIsDeploying(false) throw new Error('Error deploying project.') } - // let nftCids: Awaited> | undefined - // try { - // if (isNftProject) { - // nftCids = await uploadNftRewards() - // } - // } catch (error) { - // handleDeployFailure(error) - // return - // } + let nftCids: Awaited> | undefined + try { + if (isNftProject) { + nftCids = await uploadNftRewards() + } + } catch (error) { + handleDeployFailure(error) + return + } let softTargetAmount: string | undefined let softTargetCurrency: string | undefined @@ -120,42 +124,42 @@ export const useDeployProject = () => { } try { - // let tx - // if (isNftProject) { - // tx = await deployNftProject({ - // metadataCid: projectMetadataCid, - // rewardTierCids: nftCids!.rewardTiers, - // nftCollectionMetadataUri: nftCids!.nfCollectionMetadata, - // ...operationCallbacks(onProjectDeployed), - // }) - // } else { - const tx = await deployStandardProject({ - metadataCid: projectMetadataCid, - ...operationCallbacks(onProjectDeployed), - }) - // } - // if (!tx) { - setIsDeploying(false) - setTransactionPending(false) + let tx + if (isNftProject) { + tx = await deployNftProject({ + metadataCid: projectMetadataCid, + rewardTierCids: nftCids!.rewardTiers, + nftCollectionMetadataUri: nftCids!.nfCollectionMetadata, + ...operationCallbacks(onProjectDeployed), + }) + } else { + tx = await deployStandardProject({ + metadataCid: projectMetadataCid, + ...operationCallbacks(onProjectDeployed), + }) + } + if (!tx) { + setIsDeploying(false) + setTransactionPending(false) return - // } + } } catch (error) { handleDeployFailure(error) return } }, [ - // deployNftProject, + deployNftProject, deployStandardProject, fundAccessConstraints, fundingCycleData, fundingCycleMetadata, handleDeployFailure, - // isNftProject, + isNftProject, operationCallbacks, postPayModal, projectMetadata, - // uploadNftRewards, + uploadNftRewards, ], ) return { diff --git a/src/packages/v4/contexts/V4ProjectMetadataProvider.tsx b/src/packages/v4/contexts/V4ProjectMetadataProvider.tsx index 9841567941..d92dc5bbfd 100644 --- a/src/packages/v4/contexts/V4ProjectMetadataProvider.tsx +++ b/src/packages/v4/contexts/V4ProjectMetadataProvider.tsx @@ -1,6 +1,7 @@ +import { useJBProjectMetadataContext } from 'juice-sdk-react' + import { PV_V4 } from 'constants/pv' import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext' -import { useJBProjectMetadataContext } from 'juice-sdk-react' import { PropsWithChildren } from 'react' export default function V4ProjectMetadataProvider({ diff --git a/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts b/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts new file mode 100644 index 0000000000..64f4509e03 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts @@ -0,0 +1 @@ +export { useJB721DelegateVersion } from './useJB721DelegateVersion' diff --git a/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts b/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts new file mode 100644 index 0000000000..4a3b25ce62 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts @@ -0,0 +1,22 @@ +import { useQuery } from '@tanstack/react-query' +import axios from 'axios' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { isZeroAddress } from 'utils/address' + +export function useJB721DelegateVersion({ + dataSourceAddress, +}: { + dataSourceAddress: string | undefined +}) { + return useQuery({ + queryKey: ['JB721DelegateVersion', dataSourceAddress], + queryFn: async () => { + const res = await axios.get<{ version: JB721DelegateVersion }>( + `/api/juicebox/jb-721-delegate/${dataSourceAddress}`, + ) + + return res.data?.version + }, + enabled: !!dataSourceAddress && !isZeroAddress(dataSourceAddress), + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts new file mode 100644 index 0000000000..75aa64f5d6 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts @@ -0,0 +1,40 @@ +import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' +import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useContext } from 'react' + +export function useNftCollectionMetadataUri( + dataSourceAddress: string | undefined, +) { + const { + contracts: { JB721TieredDelegateStore, JB721TieredDelegate }, + version, + } = useContext(JB721DelegateContractsContext) + + // V3/3.1 + const v3response = useV2ContractReader({ + contract: JB721TieredDelegateStore, + functionName: 'contractUriOf', + args: + version === JB721DelegateVersion.JB721DELEGATE_V3 || + version === JB721DelegateVersion.JB721DELEGATE_V3_1 + ? [dataSourceAddress] + : null, + }) + + const v3_2_response = useV2ContractReader({ + contract: JB721TieredDelegate, + functionName: 'contractURI', + args: + version === JB721DelegateVersion.JB721DELEGATE_V3_2 || + JB721DelegateVersion.JB721DELEGATE_V3_3 || + JB721DelegateVersion.JB721DELEGATE_V3_4 + ? undefined + : null, + }) + + return version === JB721DelegateVersion.JB721DELEGATE_V3 || + version === JB721DelegateVersion.JB721DELEGATE_V3_1 + ? v3response + : v3_2_response +} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts new file mode 100644 index 0000000000..4f1c9fe681 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts @@ -0,0 +1,31 @@ +import { BigNumber } from 'ethers' +import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' +import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' +import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption' +import { useContext } from 'react' + +export type NftPricingContext = { + currency: V2V3CurrencyOption +} + +export function useNftCollectionPricingContext() { + const { + contracts: { JB721TieredDelegate }, + } = useContext(JB721DelegateContractsContext) + + const response = useV2ContractReader<[BigNumber, BigNumber, string]>({ + contract: JB721TieredDelegate, + functionName: 'pricingContext', + }) + + if (!response?.data) return { ...response, data: undefined } + + const [currency] = response.data + + return { + ...response, + data: { + currency: currency?.toNumber(), + } as NftPricingContext, + } +} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts new file mode 100644 index 0000000000..0eb726cff2 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts @@ -0,0 +1,40 @@ +import { getAddress } from 'ethers/lib/utils' +import { useV2V3HasPermissions } from 'packages/v2v3/hooks/contractReader/useV2V3HasPermissions' +import { V2V3OperatorPermission } from 'packages/v2v3/models/v2v3Permissions' +import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' +import { useProjectControllerJB721DelegateVersion } from '../contracts/useProjectJB721DelegateVersion' + +/** + * Checks whether the given [projectOwnerAddress] has given the JBTiered721DelegateProjectDeployer + * permission to reconfigure the given [projectId]s funding cycle. + * + * This must be true for the following circumstances (non-exhaustive): + * - A project is reconfiguring their FC to include NFTs + * - A project is launching their V3 FC with NFTs + */ +export function useNftDeployerCanReconfigure({ + projectOwnerAddress, + projectId, +}: { + projectOwnerAddress: string | undefined + projectId: number | undefined +}) { + const JB721DelegateVersion = useProjectControllerJB721DelegateVersion() + const JBTiered721DelegateProjectDeployer = + useJBTiered721DelegateProjectDeployer({ version: JB721DelegateVersion }) + + const JBTiered721DelegateProjectDeployerAddress = + JBTiered721DelegateProjectDeployer + ? getAddress(JBTiered721DelegateProjectDeployer?.address) + : undefined + + const { data: JBTiered721DelegateProjectDeployerCanReconfigure } = + useV2V3HasPermissions({ + operator: JBTiered721DelegateProjectDeployerAddress, + account: projectOwnerAddress, + domain: projectId, + permissions: [V2V3OperatorPermission.RECONFIGURE], + }) + + return JBTiered721DelegateProjectDeployerCanReconfigure +} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts new file mode 100644 index 0000000000..5a537f7166 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts @@ -0,0 +1,16 @@ +import { JBTiered721Flags } from 'models/nftRewards' +import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' +import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' +import { useContext } from 'react' + +export function useNftFlagsOf(dataSourceAddress: string | undefined) { + const { + contracts: { JB721TieredDelegateStore }, + } = useContext(JB721DelegateContractsContext) + + return useV2ContractReader({ + contract: JB721TieredDelegateStore, + functionName: 'flagsOf', + args: [dataSourceAddress], + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts new file mode 100644 index 0000000000..8698d08f38 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts @@ -0,0 +1,89 @@ +import { JB721TierV3, JB_721_TIER_V3_2 } from 'models/nftRewards' +import { MAX_NFT_REWARD_TIERS } from 'packages/v2v3/constants/nftRewards' +import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' +import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useContext } from 'react' + +function buildArgs( + version: JB721DelegateVersion, + { + dataSourceAddress, + limit, + }: { dataSourceAddress: string | undefined; limit?: number }, +) { + switch (version) { + case JB721DelegateVersion.JB721DELEGATE_V3: + return [ + dataSourceAddress, + 0, // _startingId + limit ?? MAX_NFT_REWARD_TIERS, + ] + case JB721DelegateVersion.JB721DELEGATE_V3_1: + return [ + dataSourceAddress, + 0, // _category + 0, // _startingId + limit ?? MAX_NFT_REWARD_TIERS, + ] + case JB721DelegateVersion.JB721DELEGATE_V3_2: + return [ + dataSourceAddress, + [], // _categories + false, // _includeResolvedUri, return in each tier a result from a tokenUriResolver if one is included in the delegate + 0, // _startingId + limit ?? MAX_NFT_REWARD_TIERS, + ] + + case JB721DelegateVersion.JB721DELEGATE_V3_3: + return [ + dataSourceAddress, + [], // _categories + false, // _includeResolvedUri, return in each tier a result from a tokenUriResolver if one is included in the delegate + 0, // _startingId + limit ?? MAX_NFT_REWARD_TIERS, + ] + + case JB721DelegateVersion.JB721DELEGATE_V3_4: // unchanged + return [ + dataSourceAddress, + [], // _categories + false, // _includeResolvedUri, return in each tier a result from a tokenUriResolver if one is included in the delegate + 0, // _startingId + limit ?? MAX_NFT_REWARD_TIERS, + ] + default: + return null + } +} + +export function useNftTiers({ + dataSourceAddress, + limit, + shouldFetch, +}: { + dataSourceAddress: string | undefined + limit?: number + shouldFetch?: boolean +}) { + const { + contracts: { JB721TieredDelegateStore }, + version, + } = useContext(JB721DelegateContractsContext) + + // send null when project has no dataSource, so the fetch doesn't execute. + const args = + shouldFetch && version + ? buildArgs(version, { dataSourceAddress, limit }) + : null + + return useV2ContractReader({ + contract: JB721TieredDelegateStore, + functionName: + version === JB721DelegateVersion.JB721DELEGATE_V3 || + version === JB721DelegateVersion.JB721DELEGATE_V3_1 + ? 'tiers' + : 'tiersOf', + args, + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts new file mode 100644 index 0000000000..b8fe9e5f3d --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts @@ -0,0 +1,57 @@ +import { ContractInterface } from 'ethers' +import { ContractJson } from 'models/contracts' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useEffect, useState } from 'react' + +type JB721DelegateContractName = + | 'JB721TieredGovernance' + | 'IJBTiered721DelegateStore' + | 'IJBTiered721Delegate' + | 'IJBTiered721DelegateProjectDeployer' + +export async function loadJB721DelegateJson( + contractName: JB721DelegateContractName, + version: JB721DelegateVersion, +): Promise { + console.info('Loading JB721Delegate contract json', version, contractName) + + // NOTE: imports are specified explicitly to avoid Webpack causing V8 to run out of memory and crash during compilation. + if (contractName === 'JB721TieredGovernance') { + return await import( + `@jbx-protocol/juice-721-delegate-v${version}/out/JB721TieredGovernance.sol/JB721TieredGovernance.json` + ) + } + + if (contractName === 'IJBTiered721DelegateStore') { + return await import( + `@jbx-protocol/juice-721-delegate-v${version}/out/IJBTiered721DelegateStore.sol/IJBTiered721DelegateStore.json` + ) + } + + if (contractName === 'IJBTiered721Delegate') { + return await import( + `@jbx-protocol/juice-721-delegate-v${version}/out/IJBTiered721Delegate.sol/IJBTiered721Delegate.json` + ) + } + + if (contractName === 'IJBTiered721DelegateProjectDeployer') { + return await import( + `@jbx-protocol/juice-721-delegate-v${version}/out/IJBTiered721DelegateProjectDeployer.sol/IJBTiered721DelegateProjectDeployer.json` + ) + } +} + +export function useJB721DelegateAbi( + contractName: JB721DelegateContractName, + version: JB721DelegateVersion | undefined, +) { + const [abi, setAbi] = useState(undefined) + + useEffect(() => { + if (!version) return + + loadJB721DelegateJson(contractName, version).then(json => setAbi(json?.abi)) + }, [version, contractName]) + + return abi +} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts new file mode 100644 index 0000000000..d883ebe226 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts @@ -0,0 +1,72 @@ +import { readNetwork } from 'constants/networks' +import { ForgeDeploy, addressFor } from 'forge-run-parser' +import { NetworkName } from 'models/networkName' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useEffect, useState } from 'react' + +/** + * Some addresses aren't in the forge deployment manifests, so we have to hardcode them here. + */ +const ADDRESSES: { + [k in JB721DelegateVersion]?: { + [k in NetworkName]?: { + [k: string]: string + } + } +} = { + [JB721DelegateVersion.JB721DELEGATE_V3_4]: { + [NetworkName.sepolia]: { + JBTiered721DelegateStore: '0xd7F9Ee12b5De2388109C9dD4fAAf39BEfe4C92FB', // the store from 3.3 + JBTiered721DelegateProjectDeployer: + '0x70b59C0ad71b8e7c9B57328bEb7Ad5921b44dB81', + }, + [NetworkName.mainnet]: { + JBTiered721DelegateStore: '0x615B5b50F1Fc591AAAb54e633417640d6F2773Fd', // the store from 3.3 + JBTiered721DelegateProjectDeployer: + '0xFbD1B7dE4082826Bf4BaA68D020eFA5c2707Fb3e', + }, + }, +} + +async function loadJB721DelegateDeployment(version: JB721DelegateVersion) { + return (await import( + `@jbx-protocol/juice-721-delegate-v${version}/broadcast/Deploy.s.sol/${readNetwork.chainId}/run-latest.json` + )) as ForgeDeploy +} + +export async function loadJB721DelegateAddress( + contractName: string, + version: JB721DelegateVersion, +) { + const hardcodedAddress = + ADDRESSES[version]?.[readNetwork.name]?.[contractName] + if (hardcodedAddress) return hardcodedAddress + + const forgeGeployment = await loadJB721DelegateDeployment(version) + const forgeAddress = addressFor(contractName, forgeGeployment) + return forgeAddress! +} + +export function useJB721DelegateContractAddress({ + contractName, + version, +}: { + contractName: string + version: JB721DelegateVersion | undefined +}): string | undefined { + const [address, setAddress] = useState(undefined) + + useEffect(() => { + async function load() { + if (!version) return + + const address = await loadJB721DelegateAddress(contractName, version) + + setAddress(address) + } + + load() + }, [contractName, version]) + + return address +} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts new file mode 100644 index 0000000000..2542213e49 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts @@ -0,0 +1,21 @@ +import { Contract } from 'ethers' +import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useJB721DelegateAbi } from './useJB721DelegateAbi' + +export function useJB721TieredDelegate({ + address, + version, +}: { + address: string | undefined + version: JB721DelegateVersion | undefined +}): Contract | undefined { + const JB721TieredDelegateJson = useJB721DelegateAbi( + 'IJBTiered721Delegate', + version, + ) + return useLoadContractFromAddress({ + address, + abi: JB721TieredDelegateJson, + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts new file mode 100644 index 0000000000..46f2cc9261 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts @@ -0,0 +1,21 @@ +import { Contract } from 'ethers' +import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useJB721DelegateAbi } from './useJB721DelegateAbi' + +export function useJB721TieredDelegateStore({ + address, + version, +}: { + address: string | undefined + version: JB721DelegateVersion | undefined +}): Contract | undefined { + const JB721DelegateStoreJson = useJB721DelegateAbi( + 'IJBTiered721DelegateStore', + version, + ) + return useLoadContractFromAddress({ + address, + abi: JB721DelegateStoreJson, + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts new file mode 100644 index 0000000000..00998b448a --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts @@ -0,0 +1,26 @@ +import { Contract } from 'ethers' +import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress' +import { useJB721DelegateAbi } from 'packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useJB721DelegateContractAddress } from './useJB721DelegateContractAddress' + +export function useJBTiered721DelegateProjectDeployer({ + version, +}: { + version: JB721DelegateVersion | undefined +}): Contract | undefined { + const JBTiered721DelegateProjectDeployerJson = useJB721DelegateAbi( + 'IJBTiered721DelegateProjectDeployer', + version, + ) + + const address = useJB721DelegateContractAddress({ + contractName: 'JBTiered721DelegateProjectDeployer', + version, + }) + + return useLoadContractFromAddress({ + address, + abi: JBTiered721DelegateProjectDeployerJson, + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts new file mode 100644 index 0000000000..28bdf37e79 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts @@ -0,0 +1,20 @@ +import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' +import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' +import { + JB721DelegateVersion, + V2V3ContractName, +} from 'packages/v2v3/models/contracts' +import { useContext } from 'react' + +/** + * Return the JB721Delegate version that should be used with the project's current JBController version. + */ +export function useProjectControllerJB721DelegateVersion() { + const { versions } = useContext(V2V3ProjectContractsContext) + const delegateVersion = + versions.JBControllerVersion === V2V3ContractName.JBController + ? JB721DelegateVersion.JB721DELEGATE_V3 + : DEFAULT_JB_721_DELEGATE_VERSION // the default delegate version for controller >= 3.1 + + return delegateVersion +} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts new file mode 100644 index 0000000000..dc0392b183 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts @@ -0,0 +1,28 @@ +import { Contract } from 'ethers' +import { useContractReadValue } from 'hooks/ContractReader' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { useJB721TieredDelegateStore } from './useJB721TieredDelegateStore' + +export function useStoreOfJB721TieredDelegate({ + JB721TieredDelegate, + version, +}: { + JB721TieredDelegate: Contract | undefined + version: JB721DelegateVersion | undefined +}) { + // get the JBTiered721DelegateStore address on-chain + const { value: storeAddress, loading } = useContractReadValue( + { + contract: JB721TieredDelegate, + functionName: 'store', + args: [], + }, + ) + + const JBTiered721DelegateStore = useJB721TieredDelegateStore({ + address: storeAddress, + version, + }) + + return { value: JBTiered721DelegateStore, loading } +} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts new file mode 100644 index 0000000000..e76e70da49 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts @@ -0,0 +1,53 @@ +import { t } from '@lingui/macro' +import { TransactionContext } from 'contexts/Transaction/TransactionContext' +import { TransactorInstance } from 'hooks/useTransactor' +import { + JB721TierParams, + JB_721_TIER_PARAMS_V3_1, + JB_721_TIER_PARAMS_V3_2, +} from 'models/nftRewards' +import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' +import { useContext } from 'react' + +export function useAdjustTiersTx(): TransactorInstance<{ + newTiers: ( + | JB721TierParams + | JB_721_TIER_PARAMS_V3_1 + | JB_721_TIER_PARAMS_V3_2 + )[] + tierIdsChanged: number[] +}> { + const { transactor } = useContext(TransactionContext) + const { + contracts: { JB721TieredDelegate }, + } = useContext(JB721DelegateContractsContext) + + return async ({ newTiers, tierIdsChanged }, txOpts) => { + if (!transactor || !JB721TieredDelegate) { + const missingParam = !transactor + ? 'transactor' + : !JB721TieredDelegate + ? 'JB721TieredDelegate' + : null + txOpts?.onError?.( + new DOMException( + `Transaction failed, missing argument "${ + missingParam ?? '' + }".`, + ), + ) + + return Promise.resolve(false) + } + + const args = [ + newTiers, // _tiersToAdd + tierIdsChanged, // _tierIdsToRemove + ] + + return transactor(JB721TieredDelegate, 'adjustTiers', args, { + ...txOpts, + title: t`Editing NFTs`, + }) + } +} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts new file mode 100644 index 0000000000..cb251ccb77 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts @@ -0,0 +1,172 @@ +import { + JBDeployTiered721DelegateData, + JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1 +} from 'models/nftRewards' +import { JB721DelegateLaunchProjectData, JBDeploy721TiersHookConfig, LaunchProjectWithNftsTxArgs } from 'packages/v4/models/nfts' + +import { TransactorInstance } from 'hooks/useTransactor' +import { useWallet } from 'hooks/Wallet' +import { useJBContractContext } from 'juice-sdk-react' +import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' +import { useDefaultJBController } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBController' +import { useDefaultJBETHPaymentTerminal } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBETHPaymentTerminal' +import { useV2ProjectTitle } from 'packages/v2v3/hooks/useProjectTitle' +import { + isValidMustStartAtOrAfter +} from 'packages/v2v3/utils/fundingCycle' +import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' +import { useJB721DelegateContractAddress } from '../contracts/useJB721DelegateContractAddress' +import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' + +function buildArgs({ + owner, + deployTiered721DelegateData, + launchProjectData, + JBControllerAddress, +}: { + owner: string + JBControllerAddress: string + deployTiered721DelegateData: + | JBDeployTiered721DelegateData + | JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1 + launchProjectData: JB721DelegateLaunchProjectData +}) { + const baseArgs = [ + owner, + deployTiered721DelegateData, //_deployTiered721DelegateData + launchProjectData, // _launchProjectData + ] + + return [...baseArgs, JBControllerAddress] +} + +export function useLaunchProjectWithNftsTx(): TransactorInstance { + const { contracts } = useJBContractContext() + const defaultJBController = useDefaultJBController() + + const { userAddress } = useWallet() + const projectTitle = useV2ProjectTitle() + const defaultJBETHPaymentTerminal = useDefaultJBETHPaymentTerminal() + const JBTiered721DelegateProjectDeployer = + useJBTiered721DelegateProjectDeployer({ + version: DEFAULT_JB_721_DELEGATE_VERSION, + }) + const JBTiered721DelegateStoreAddress = useJB721DelegateContractAddress({ + contractName: 'JBTiered721DelegateStore', + version: DEFAULT_JB_721_DELEGATE_VERSION, + }) + + return async ( + { + tiered721DelegateData: { + collectionUri, + collectionName, + collectionSymbol, + currency, + tiers, + flags, + governanceType, + }, + projectData: { + projectMetadataCID, + fundingCycleData, + fundingCycleMetadata, + fundAccessConstraints, + groupedSplits = [], + mustStartAtOrAfter = DEFAULT_MUST_START_AT_OR_AFTER, + owner, + }, + }, + txOpts, + ) => { + if ( + !userAddress || + !contracts || + !defaultJBController || + !defaultJBETHPaymentTerminal || + !JBTiered721DelegateProjectDeployer || + !JBTiered721DelegateStoreAddress || + !isValidMustStartAtOrAfter(mustStartAtOrAfter, fundingCycleData.duration) + ) { + const missingParam = !userAddress + ? 'userAddress' + : !contracts + ? 'contracts' + : !defaultJBController + ? 'defaultJBController' + : !JBTiered721DelegateStoreAddress + ? 'JBTiered721DelegateStoreAddress' + : !JBTiered721DelegateProjectDeployer + ? 'JBTiered721DelegateProjectDeployer' + : null + + txOpts?.onError?.( + new DOMException( + `Transaction failed, missing argument "${ + missingParam ?? '' + }".`, + ), + ) + + return Promise.resolve(false) + } + const _owner = owner?.length ? owner : userAddress + + const deployTiered721DelegateData: JBDeploy721TiersHookConfig = { + name: collectionName, + symbol: collectionSymbol, + baseUri: '', // ? + tokenUriResolver: '', // ? + contractUri: '', //collectionUri ; + tiersConfig: tiers, + reserveBeneficiary: '', //?; + flags, + } + + // NFT launch tx does not accept `useDataHookForPay` and `dataHook` (see contracts:`JBPayDataHookRulesetMetadata`) + // const dataHookRulesetMetadata: JBPayDataHookRulesetMetadata = omit( + // rulesetMetadata, + // ['useDataSourceForPay', 'dataSource'], + // ) + + // const launchProjectData: JB721DelegateLaunchProjectData = { + // projectMetadata: { + // domain: JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN, + // content: projectMetadataCID, + // }, + // data: fundingCycleData, + // metadata: dataHookRulesetMetadata, + // mustStartAtOrAfter, + // groupedSplits, + // fundAccessConstraints, + // terminals: [defaultJBETHPaymentTerminal?.address], + // memo: DEFAULT_MEMO, + // } // _launchProjectData + + // const args = buildArgs({ + // owner: _owner, + // deployTiered721DelegateData, + // launchProjectData: transformV2V3CreateArgsToV4(launchProjectData), + // JBControllerAddress: defaultJBController.address, + // }) + + // if (!args) { + // txOpts?.onError?.( + // new DOMException(`Transaction failed, failed to build args`), + // ) + + // return Promise.resolve(false) + // } + + // return transactor( + // JBTiered721DelegateProjectDeployer, + // 'launchProjectFor', + // args, + // { + // ...txOpts, + // title: t`Launch ${projectTitle}`, + // }, + // ) + return Promise.resolve(false) + } +} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts new file mode 100644 index 0000000000..de53a7fbdd --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts @@ -0,0 +1,212 @@ +import { t } from '@lingui/macro' +import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext' +import { TransactionContext } from 'contexts/Transaction/TransactionContext' +import { getAddress } from 'ethers/lib/utils' +import { TransactorInstance } from 'hooks/useTransactor' +import omit from 'lodash/omit' +import { + JBDeployTiered721DelegateData, + JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1, +} from 'models/nftRewards' +import { V2V3ContractsContext } from 'packages/v2v3/contexts/Contracts/V2V3ContractsContext' +import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext' +import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' +import { useJBPrices } from 'packages/v2v3/hooks/JBPrices' +import { ReconfigureFundingCycleTxParams } from 'packages/v2v3/hooks/transactor/useReconfigureV2V3FundingCycleTx' +import { useV2ProjectTitle } from 'packages/v2v3/hooks/useProjectTitle' +import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' +import { + JBPayDataSourceFundingCycleMetadata, + V2V3FundAccessConstraint, + V2V3FundingCycleData, +} from 'packages/v2v3/models/fundingCycle' +import { GroupedSplits, SplitGroup } from 'packages/v2v3/models/splits' +import { V2V3_CURRENCY_ETH } from 'packages/v2v3/utils/currency' +import { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle' +import { useContext } from 'react' +import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' +import { NftRewardsData } from 'redux/slices/editingV2Project/types' +import { + buildDeployTiered721DelegateData, + buildJB721TierParams, + defaultNftCollectionName, +} from 'utils/nftRewards' +import { useJB721DelegateContractAddress } from '../contracts/useJB721DelegateContractAddress' +import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' +import { useProjectControllerJB721DelegateVersion } from '../contracts/useProjectJB721DelegateVersion' + +type ReconfigureWithNftsTxArgs = { + reconfigureData: ReconfigureFundingCycleTxParams + tiered721DelegateData: NftRewardsData +} + +interface JB721DelegateReconfigureFundingCycleData { + data: V2V3FundingCycleData + metadata: JBPayDataSourceFundingCycleMetadata + memo?: string + fundAccessConstraints: V2V3FundAccessConstraint[] + groupedSplits?: GroupedSplits[] + mustStartAtOrAfter?: string // epoch seconds. anything less than "now" will start immediately. +} + +function buildArgs( + version: JB721DelegateVersion, + { + projectId, + deployTiered721DelegateData, + reconfigureFundingCyclesData, + JBControllerAddress, + }: { + projectId: number + JBControllerAddress: string + deployTiered721DelegateData: + | JBDeployTiered721DelegateData + | JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1 + reconfigureFundingCyclesData: JB721DelegateReconfigureFundingCycleData + }, +) { + const baseArgs = [ + projectId, + deployTiered721DelegateData, //_deployTiered721DelegateData + reconfigureFundingCyclesData, // _reconfigureFundingCyclesData + ] + + if (version === JB721DelegateVersion.JB721DELEGATE_V3) { + return baseArgs + } + + return [...baseArgs, JBControllerAddress] // v3.1, 3.2 requires us to pass the controller address in +} + +export function useReconfigureV2V3FundingCycleWithNftsTx(): TransactorInstance { + const { transactor } = useContext(TransactionContext) + const { contracts } = useContext(V2V3ContractsContext) + const { + contracts: { JBController: projectJBController }, + } = useContext(V2V3ProjectContractsContext) + const { projectId } = useContext(ProjectMetadataContext) + const { projectOwnerAddress } = useContext(V2V3ProjectContext) + + const JB721DelegateVersion = useProjectControllerJB721DelegateVersion() + const JBTiered721DelegateStoreAddress = useJB721DelegateContractAddress({ + contractName: 'JBTiered721DelegateStore', + version: JB721DelegateVersion, + }) + const JBTiered721DelegateProjectDeployer = + useJBTiered721DelegateProjectDeployer({ version: JB721DelegateVersion }) + const JBPrices = useJBPrices() + + const projectTitle = useV2ProjectTitle() + + return async ( + { + reconfigureData: { + fundingCycleData, + fundingCycleMetadata, + fundAccessConstraints, + groupedSplits = [], + mustStartAtOrAfter = DEFAULT_MUST_START_AT_OR_AFTER, + memo, + }, + tiered721DelegateData: { + governanceType, + rewardTiers, + CIDs, + collectionMetadata, + flags, + }, + }, + txOpts, + ) => { + const collectionName = + collectionMetadata.name ?? defaultNftCollectionName(projectTitle) + + if ( + !transactor || + !projectId || + !CIDs || + !rewardTiers || + !JBTiered721DelegateStoreAddress || + !projectOwnerAddress || + !contracts || + !projectJBController || + !JBPrices || + !isValidMustStartAtOrAfter( + mustStartAtOrAfter, + fundingCycleData.duration, + ) || + !collectionName || + !JBTiered721DelegateProjectDeployer + ) { + txOpts?.onDone?.() + return Promise.resolve(false) + } + + // build `delegateData` + const tiers = buildJB721TierParams({ + cids: CIDs, + rewardTiers, + version: JB721DelegateVersion, + }) + + const deployTiered721DelegateData = buildDeployTiered721DelegateData( + { + collectionUri: collectionMetadata.uri ?? '', + collectionName, + collectionSymbol: collectionMetadata.symbol ?? '', + currency: V2V3_CURRENCY_ETH, // TODO use the user-set currency form the form + governanceType, + tiers, + ownerAddress: projectOwnerAddress, + contractAddresses: { + JBDirectoryAddress: getAddress(contracts.JBDirectory.address), + JBFundingCycleStoreAddress: getAddress( + contracts.JBFundingCycleStore.address, + ), + JBPricesAddress: getAddress(JBPrices.address), + JBTiered721DelegateStoreAddress, + }, + flags, + }, + JB721DelegateVersion, + ) + + // NFT launch tx does not accept `useDataSourceForPay` and `dataSource` (see contracts:`JBPayDataSourceFundingCycleMetadata`) + const dataSourceFCMetadata: JBPayDataSourceFundingCycleMetadata = omit( + fundingCycleMetadata, + ['useDataSourceForPay', 'dataSource'], + ) + + const reconfigureFundingCyclesData: JB721DelegateReconfigureFundingCycleData = + { + data: fundingCycleData, + metadata: dataSourceFCMetadata, + mustStartAtOrAfter, + groupedSplits, + fundAccessConstraints, + memo, + } + + const args = buildArgs(JB721DelegateVersion, { + projectId, + deployTiered721DelegateData, + reconfigureFundingCyclesData, + JBControllerAddress: projectJBController.address, + }) + + if (!args) { + txOpts?.onDone?.() + return Promise.resolve(false) + } + + return transactor( + JBTiered721DelegateProjectDeployer, + 'reconfigureFundingCyclesOf', + args, + { + ...txOpts, + title: t`Setup new NFTs for ${projectTitle}`, + }, + ) + } +} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts new file mode 100644 index 0000000000..670d9e3c68 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts @@ -0,0 +1,28 @@ +import { getAddress } from 'ethers/lib/utils' +import { TransactorInstance } from 'hooks/useTransactor' +import { useSetOperatorTx } from 'packages/v2v3/hooks/transactor/useSetOperatorTx' +import { V2V3OperatorPermission } from 'packages/v2v3/models/v2v3Permissions' +import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' +import { useProjectControllerJB721DelegateVersion } from '../contracts/useProjectJB721DelegateVersion' + +export function useSetNftOperatorPermissionsTx(): TransactorInstance { + const setOperatorTx = useSetOperatorTx() + + const JB721DelegateVersion = useProjectControllerJB721DelegateVersion() + const JBTiered721DelegateProjectDeployer = + useJBTiered721DelegateProjectDeployer({ version: JB721DelegateVersion }) + + return (_, txOpts) => { + const operator = getAddress( + JBTiered721DelegateProjectDeployer?.address ?? '', + ) + + return setOperatorTx( + { + operatorAddress: operator, + permissionIndexes: [V2V3OperatorPermission.RECONFIGURE], + }, + txOpts, + ) + } +} diff --git a/src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts b/src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts new file mode 100644 index 0000000000..4cb5e87c64 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts @@ -0,0 +1,14 @@ +import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' +import { useContext } from 'react' + +export function useHasNftRewards(): { value: boolean; loading: boolean } { + const { + version: JB721DelegateVersion, + loading: { JB721TieredDelegateStoreLoading }, + } = useContext(JB721DelegateContractsContext) + + return { + value: Boolean(JB721DelegateVersion), + loading: JB721TieredDelegateStoreLoading, + } +} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts b/src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts new file mode 100644 index 0000000000..7dce0ce200 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts @@ -0,0 +1,25 @@ +import { useNfTsQuery } from 'generated/graphql' +import { client } from 'lib/apollo/client' + +/** + * Return all the project's NFTs that are owned by the given account. + */ +export function useNftAccountBalance({ + dataSourceAddress, + accountAddress, +}: { + dataSourceAddress: string | undefined + accountAddress: string | undefined +}) { + return useNfTsQuery({ + client, + variables: { + where: { + ...(dataSourceAddress + ? { collection_: { address: dataSourceAddress } } + : {}), + ...(accountAddress ? { owner_: { wallet: accountAddress } } : {}), + }, + }, + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts b/src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts new file mode 100644 index 0000000000..0a4e836f38 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts @@ -0,0 +1,25 @@ +import { useQuery } from '@tanstack/react-query' +import { ipfsFetch } from 'lib/api/ipfs' +import { NftCollectionMetadata } from 'models/nftRewards' +import { cidFromUrl } from 'utils/ipfs' + +// gets Nft Collection metadata from IPFS +export function useNftCollectionMetadata(uri: string | undefined) { + return useQuery({ + queryKey: ['nft-collection-metadata', uri], + queryFn: async () => { + if (!uri) { + throw new Error('NFT Contract URI not specified.') + } + + const cid = cidFromUrl(uri) + if (!cid) { + throw new Error('NFT Contract URI invalid.') + } + + const response = await ipfsFetch(cid) + return response.data + }, + enabled: !!uri, + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftCredits.ts b/src/packages/v4/hooks/JB721Delegate/useNftCredits.ts new file mode 100644 index 0000000000..c1ec55d554 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/useNftCredits.ts @@ -0,0 +1,22 @@ +import { BigNumber } from 'ethers' +import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' +import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' +import { useContext } from 'react' + +/** + * Return the wei amount of unused credits for a given address. + * @example If the user paid 1 ETH and didnt mint a 1 ETH NFT, then they have 1 ETH of unused credits. + * @param address + * @returns + */ +export function useNftCredits(address: string | undefined) { + const { + contracts: { JB721TieredDelegate }, + } = useContext(JB721DelegateContractsContext) + + return useV2ContractReader({ + contract: JB721TieredDelegate, + functionName: 'creditsOf', + args: address ? [address] : null, + }) +} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts b/src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts new file mode 100644 index 0000000000..1336200070 --- /dev/null +++ b/src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts @@ -0,0 +1,14 @@ +import { NftRewardsContext } from 'packages/v2v3/contexts/NftRewards/NftRewardsContext' +import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext' +import { hasDataSourceForPay } from 'packages/v2v3/utils/fundingCycle' +import { useContext, useMemo } from 'react' + +export function useNftRewardsEnabledForPay() { + const { fundingCycleMetadata } = useContext(V2V3ProjectContext) + const { + nftRewards: { rewardTiers }, + } = useContext(NftRewardsContext) + const hasNftRewards = useMemo(() => rewardTiers?.length !== 0, [rewardTiers]) + + return hasNftRewards && hasDataSourceForPay(fundingCycleMetadata) +} diff --git a/src/packages/v4/hooks/useLaunchProjectTx.ts b/src/packages/v4/hooks/useLaunchProjectTx.ts index a337c808aa..5b9872dc10 100644 --- a/src/packages/v4/hooks/useLaunchProjectTx.ts +++ b/src/packages/v4/hooks/useLaunchProjectTx.ts @@ -1,3 +1,10 @@ +import { useCallback, useContext } from 'react' +import { Address, WaitForTransactionReceiptReturnType } from 'viem' +import { + LaunchV2V3ProjectArgs, + transformV2V3CreateArgsToV4, +} from '../utils/launchProjectTransformers' + import { waitForTransactionReceipt } from '@wagmi/core' import { JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN } from 'constants/metadataDomain' import { DEFAULT_MEMO } from 'constants/transactionDefaults' @@ -6,14 +13,8 @@ import { useWallet } from 'hooks/Wallet' import { NATIVE_TOKEN } from 'juice-sdk-core' import { useWriteJbControllerLaunchProjectFor } from 'juice-sdk-react' import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' -import { useCallback, useContext } from 'react' import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' -import { Address, WaitForTransactionReceiptReturnType } from 'viem' import { sepolia } from 'viem/chains' -import { - LaunchV2V3ProjectArgs, - transformV2V3CreateArgsToV4, -} from '../utils/launchProject' import { wagmiConfig } from '../wagmiConfig' import { useCurrentRouteChainId } from './useCurrentRouteChainId' diff --git a/src/packages/v4/models/fundAccessLimits.ts b/src/packages/v4/models/fundAccessLimits.ts new file mode 100644 index 0000000000..d5e1f0b919 --- /dev/null +++ b/src/packages/v4/models/fundAccessLimits.ts @@ -0,0 +1,12 @@ +export type FundAccessLimitGroup = { + terminal: `0x${string}`; + token: `0x${string}`; + payoutLimits: { + amount: bigint; + currency: number; + }[]; + surplusAllowances: { + amount: bigint; + currency: number; + }[]; +} diff --git a/src/packages/v4/models/nfts.ts b/src/packages/v4/models/nfts.ts new file mode 100644 index 0000000000..be5c2fe7e9 --- /dev/null +++ b/src/packages/v4/models/nfts.ts @@ -0,0 +1,48 @@ +import { JBRulesetData, JBRulesetMetadata } from "juice-sdk-core"; + +import { LaunchV2V3ProjectData } from "packages/v2v3/hooks/transactor/useLaunchProjectTx"; +import { Address } from "viem"; +import { LaunchV4ProjectGroupedSplit } from "../utils/launchProjectTransformers"; +import { FundAccessLimitGroup } from "./fundAccessLimits"; +import { LaunchProjectJBTerminal } from "./terminals"; + +export type JBDeploy721TiersHookConfig = { + name: string; + symbol: string; + baseUri: string; + tokenUriResolver: string;//IJB721TokenUriResolver; + contractUri: string; + // tiersConfig: //JB721InitTiersConfig; + reserveBeneficiary: Address; + // flags// JB721TiersHookFlags; +} + +export type JBPayDataHookRulesetConfig = JBRulesetData & { + metadata: JBPayDataHookRulesetMetadata; + memo?: string; + fundAccessLimitGroups: FundAccessLimitGroup[]; + mustStartAtOrAfter?: string; // epoch seconds. anything less than "now" will start immediately. + terminals: string[]; + duration: bigint; + weight: bigint; + decayPercent: bigint; + approvalHook: Address; + splitGroups: LaunchV4ProjectGroupedSplit[]; +} + +export interface LaunchProjectWithNftsTxArgs { + tiered721DelegateData: JBDeploy721TiersHookConfig; + projectData: LaunchV2V3ProjectData; +} + +export type JB721DelegateLaunchProjectData = { + rulesetConfigurations: JBPayDataHookRulesetConfig[]; + terminalConfigurations: LaunchProjectJBTerminal[]; + projectMetadataUri: string; + memo?: string; +} + +export type JBPayDataHookRulesetMetadata = Omit< + JBRulesetMetadata, + 'useDataSourceForPay' | 'dataSource' +> diff --git a/src/packages/v4/models/splits.ts b/src/packages/v4/models/splits.ts new file mode 100644 index 0000000000..7e5fb0e075 --- /dev/null +++ b/src/packages/v4/models/splits.ts @@ -0,0 +1,6 @@ +import { JBSplit } from "juice-sdk-core" + +export interface GroupedSplits { + groupId: G + splits: JBSplit[] +} diff --git a/src/packages/v4/models/terminals.ts b/src/packages/v4/models/terminals.ts new file mode 100644 index 0000000000..c5f474928f --- /dev/null +++ b/src/packages/v4/models/terminals.ts @@ -0,0 +1,10 @@ +import { Address } from 'viem'; + +export type LaunchProjectJBTerminal = { + terminal: Address; + accountingContextsToAccept: { + token: `0x${string}`; + decimals: 18; + currency: number; + }[]; +} diff --git a/src/packages/v4/utils/launchProject.ts b/src/packages/v4/utils/launchProject.ts deleted file mode 100644 index 21292b1834..0000000000 --- a/src/packages/v4/utils/launchProject.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { NATIVE_TOKEN, NATIVE_TOKEN_DECIMALS, SplitGroup } from 'juice-sdk-core' -import round from 'lodash/round' -import { V2FundingCycleMetadata } from 'packages/v2/models/fundingCycle' -import { - V2V3FundAccessConstraint, - V2V3FundingCycleData, -} from 'packages/v2v3/models/fundingCycle' -import { GroupedSplits } from 'packages/v2v3/models/splits' -import { V3FundingCycleMetadata } from 'packages/v3/models/fundingCycle' -import { Address } from 'viem' - -export type LaunchV2V3ProjectArgs = [ - string, // _owner - [string, number], // _projectMetadata [projectMetadataCID, JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN] - V2V3FundingCycleData, // _data - V2FundingCycleMetadata | V3FundingCycleMetadata, // _metadata - string, // _mustStartAtOrAfter - GroupedSplits[], // _groupedSplits - V2V3FundAccessConstraint[], // _fundAccessConstraints - string[], // _terminals - string, // _memo -] - -export function transformV2V3CreateArgsToV4({ - v2v3Args, - primaryNativeTerminal, - currencyTokenAddress, -}: { - v2v3Args: LaunchV2V3ProjectArgs - primaryNativeTerminal: Address - currencyTokenAddress: Address -}) { - const [ - _owner, - _projectMetadata, - _data, - _metadata, - _mustStartAtOrAfter, - _groupedSplits, - _fundAccessConstraints, - _terminals, - _memo, - ] = v2v3Args - - const mustStartAtOrAfterNum = parseInt(_mustStartAtOrAfter) - const now = round(new Date().getTime() / 1000) - - const ruleset = { - mustStartAtOrAfter: mustStartAtOrAfterNum > now ? mustStartAtOrAfterNum : now, - duration: _data.duration.toNumber(), - weight: _data.weight.toBigInt(), - decayPercent: _data.discountRate.toNumber(), - - approvalHook: _data.ballot as Address, - - metadata: { - reservedPercent: _metadata.reservedRate.toNumber(), - redemptionRate: _metadata.redemptionRate.toNumber(), - baseCurrency: 1, // Not present in v2v3, passing 1 by default - pausePay: _metadata.pausePay, - pauseRedeem: _metadata.pauseRedeem, - pauseCreditTransfers: Boolean(_metadata.global.pauseTransfers), - allowOwnerMinting: _metadata.allowMinting, - allowSetCustomToken: false, // Assuming false by default - allowTerminalMigration: _metadata.allowTerminalMigration, - allowSetTerminals: _metadata.global.allowSetTerminals, - allowSetController: _metadata.global.allowSetController, - allowAddAccountingContext: false, // Not present in v2v3, passing false by default - allowAddPriceFeed: false, // Not present in v2v3, passing false by default - ownerMustSendPayouts: false, // Not present in v2v3, passing false by default - holdFees: _metadata.holdFees, - useTotalSurplusForRedemptions: _metadata.useTotalOverflowForRedemptions, - useDataHookForPay: _metadata.useDataSourceForPay, - useDataHookForRedeem: _metadata.useDataSourceForRedeem, - dataHook: _metadata.dataSource as Address, - metadata: 0, - allowCrosschainSuckerExtension: false, - }, - - splitGroups: _groupedSplits.map(group => ({ - groupId: - group.group === SplitGroup.ETHPayout - ? BigInt(NATIVE_TOKEN) - : 1n, // TODO dont hardcode reserved token group as 1n - splits: group.splits.map(split => ({ - preferAddToBalance: Boolean(split.preferClaimed), - percent: split.percent, - projectId: BigInt(parseInt(split.projectId ?? '0x00', 16)), - beneficiary: split.beneficiary as Address, - lockedUntil: split.lockedUntil ?? 0, - hook: split.allocator as Address, - })), - })), - - fundAccessLimitGroups: _fundAccessConstraints.map(constraint => ({ - terminal: primaryNativeTerminal, - token: currencyTokenAddress, - payoutLimits: [ - { - amount: constraint.distributionLimit.toBigInt(), - currency: Number(BigInt(NATIVE_TOKEN)), // TODO support USD somehow - }, - ], - surplusAllowances: [ - { - amount: constraint.overflowAllowance.toBigInt(), - currency: Number(BigInt(NATIVE_TOKEN)), - }, - ], - })), - } - - const rulesetConfigurations = [ruleset] - - const terminalConfigurations = _terminals.map(terminal => ({ - terminal: terminal as Address, - accountingContextsToAccept: [ - { - token: currencyTokenAddress, - decimals: NATIVE_TOKEN_DECIMALS, - currency: Number(BigInt(currencyTokenAddress)), - }, - ], - })) - - return [ - _owner as Address, - _projectMetadata[0], - rulesetConfigurations, - terminalConfigurations, - _memo, - ] as const -} diff --git a/src/packages/v4/utils/launchProjectTransformers.ts b/src/packages/v4/utils/launchProjectTransformers.ts new file mode 100644 index 0000000000..e5ae49b08a --- /dev/null +++ b/src/packages/v4/utils/launchProjectTransformers.ts @@ -0,0 +1,179 @@ +import { JBSplit, NATIVE_TOKEN, NATIVE_TOKEN_DECIMALS, SplitGroup } from 'juice-sdk-core' +import { + V2V3FundAccessConstraint, + V2V3FundingCycleData, +} from 'packages/v2v3/models/fundingCycle' + +import round from 'lodash/round' +import { V2FundingCycleMetadata } from 'packages/v2/models/fundingCycle' +import { GroupedSplits as V2V3GroupedSplits } from 'packages/v2v3/models/splits' +import { V3FundingCycleMetadata } from 'packages/v3/models/fundingCycle' +import { Address } from 'viem' +import { FundAccessLimitGroup } from '../models/fundAccessLimits' +import { GroupedSplits as V4GroupedSplits } from '../models/splits' +import { LaunchProjectJBTerminal } from '../models/terminals' + +export type LaunchV2V3ProjectArgs = [ + string, // _owner + [string, number], // _projectMetadata [projectMetadataCID, JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN] + V2V3FundingCycleData, // _fundingCycleData + V2FundingCycleMetadata | V3FundingCycleMetadata, // _fundingCycleMetadata + string, // _mustStartAtOrAfter + V2V3GroupedSplits[], // _groupedSplits + V2V3FundAccessConstraint[], // _fundAccessConstraints + string[], // _terminals + string, // _memo +] + +export function transformV2V3CreateArgsToV4({ + v2v3Args, + primaryNativeTerminal, + currencyTokenAddress, +}: { + v2v3Args: LaunchV2V3ProjectArgs + primaryNativeTerminal: Address + currencyTokenAddress: Address +}) { + const [ + _owner, + _projectMetadata, + _fundingCycleData, + _fundingCycleMetadata, + _mustStartAtOrAfter, + _groupedSplits, + _fundAccessConstraints, + _terminals, + _memo, + ] = v2v3Args + + const mustStartAtOrAfterNum = parseInt(_mustStartAtOrAfter) + const now = round(new Date().getTime() / 1000) + + const ruleset = { + mustStartAtOrAfter: mustStartAtOrAfterNum > now ? mustStartAtOrAfterNum : now, + duration: _fundingCycleData.duration.toNumber(), + weight: _fundingCycleData.weight.toBigInt(), + decayPercent: _fundingCycleData.discountRate.toNumber(), + + approvalHook: _fundingCycleData.ballot as Address, + + metadata: transformFCMetadataToRulesetMetadata({ fundingCycleMetadata: _fundingCycleMetadata}), + + splitGroups: transformV2V3SplitsToV4({ v2v3Splits: _groupedSplits }), + + fundAccessLimitGroups: transformV2V3FundAccessConstraintsToV4({ v2V3FundAccessConstraints: _fundAccessConstraints, primaryNativeTerminal, currencyTokenAddress }) + } + + const rulesetConfigurations = [ruleset] + + const terminalConfigurations = generateV4LaunchTerminalConfigurationsArg({ terminals: _terminals, currencyTokenAddress }) + + return [ + _owner as Address, + _projectMetadata[0], + rulesetConfigurations, + terminalConfigurations, + _memo, + ] as const +} + + +export function transformFCMetadataToRulesetMetadata({ + fundingCycleMetadata +}: { + fundingCycleMetadata: V2FundingCycleMetadata | V3FundingCycleMetadata +}) { + return { + reservedPercent: fundingCycleMetadata.reservedRate.toNumber(), + redemptionRate: fundingCycleMetadata.redemptionRate.toNumber(), + baseCurrency: 1, // Not present in v2v3, passing 1 by default + pausePay: fundingCycleMetadata.pausePay, + pauseRedeem: fundingCycleMetadata.pauseRedeem, + pauseCreditTransfers: Boolean(fundingCycleMetadata.global.pauseTransfers), + allowOwnerMinting: fundingCycleMetadata.allowMinting, + allowSetCustomToken: false, // Assuming false by default + allowTerminalMigration: fundingCycleMetadata.allowTerminalMigration, + allowSetTerminals: fundingCycleMetadata.global.allowSetTerminals, + allowSetController: fundingCycleMetadata.global.allowSetController, + allowAddAccountingContext: false, // Not present in v2v3, passing false by default + allowAddPriceFeed: false, // Not present in v2v3, passing false by default + ownerMustSendPayouts: false, // Not present in v2v3, passing false by default + holdFees: fundingCycleMetadata.holdFees, + useTotalSurplusForRedemptions: fundingCycleMetadata.useTotalOverflowForRedemptions, + useDataHookForPay: fundingCycleMetadata.useDataSourceForPay, + useDataHookForRedeem: fundingCycleMetadata.useDataSourceForRedeem, + dataHook: fundingCycleMetadata.dataSource as Address, + metadata: 0, + allowCrosschainSuckerExtension: false, + } +} + +type LaunchProjectJBSplit = Omit & { percent: number } +export type LaunchV4ProjectGroupedSplit = Omit, 'splits'> & { splits: LaunchProjectJBSplit[] } + +export function transformV2V3SplitsToV4({ + v2v3Splits +}: { + v2v3Splits: V2V3GroupedSplits[] +}): LaunchV4ProjectGroupedSplit[] { + return v2v3Splits.map(group => ({ + groupId: + group.group === SplitGroup.ETHPayout + ? Number(BigInt(NATIVE_TOKEN)) + : 1, // TODO dont hardcode reserved token group as 1n + splits: group.splits.map(split => ({ + preferAddToBalance: Boolean(split.preferClaimed), + percent: split.percent, + projectId: BigInt(parseInt(split.projectId ?? '0x00', 16)), + beneficiary: split.beneficiary as Address, + lockedUntil: split.lockedUntil ?? 0, + hook: split.allocator as Address, + })), + })) +} + +export function transformV2V3FundAccessConstraintsToV4({ + v2V3FundAccessConstraints, + primaryNativeTerminal, + currencyTokenAddress +}: { + v2V3FundAccessConstraints: V2V3FundAccessConstraint[] + primaryNativeTerminal: Address, + currencyTokenAddress: Address +}): FundAccessLimitGroup[] { + return v2V3FundAccessConstraints.map(constraint => ({ + terminal: primaryNativeTerminal, + token: currencyTokenAddress, + payoutLimits: [ + { + amount: constraint.distributionLimit.toBigInt(), + currency: Number(BigInt(NATIVE_TOKEN)), // TODO support USD somehow + }, + ], + surplusAllowances: [ + { + amount: constraint.overflowAllowance.toBigInt(), + currency: Number(BigInt(NATIVE_TOKEN)), + }, + ], + })) +} + +export function generateV4LaunchTerminalConfigurationsArg({ + terminals, + currencyTokenAddress, +}: { + terminals: string[] + currencyTokenAddress: Address +}): LaunchProjectJBTerminal[] { + return terminals.map(terminal => ({ + terminal: terminal as Address, + accountingContextsToAccept: [ + { + token: currencyTokenAddress, + decimals: NATIVE_TOKEN_DECIMALS, + currency: Number(BigInt(currencyTokenAddress)), + }, + ], + })) +} diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx index f88a7c5575..cc99f1436c 100644 --- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx +++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx @@ -1,8 +1,9 @@ +import { Fragment, useEffect, useMemo, useRef, useState } from 'react' + import { Tab } from '@headlessui/react' import { t } from '@lingui/macro' import { ProjectTab } from 'components/Project/ProjectTabs/ProjectTab' import { useOnScreen } from 'hooks/useOnScreen' -import { Fragment, useEffect, useMemo, useRef, useState } from 'react' import { twMerge } from 'tailwind-merge' import { useProjectPageQueries } from '../hooks/useProjectPageQueries' import V4AboutPanel from './V4AboutPanel' @@ -47,6 +48,12 @@ export const V4ProjectTabs = ({ className }: { className?: string }) => { () => [ { id: 'activity', name: t`Activity`, panel: }, { id: 'about', name: t`About`, panel: }, + // { + // id: 'nft_rewards', + // name: t`NFTs`, + // panel: , + // hideTab: !showNftRewards, + // }, { id: 'cycle_payouts', name: t`Cycles & Payouts`, From af3c18731c17cb816db39507abf170393a53ba16 Mon Sep 17 00:00:00 2001 From: aeolian <94939382+aeolianeth@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:39:30 +1000 Subject: [PATCH 2/6] wip nft launch --- package.json | 4 +- .../hooks/NFT/useDeployNftProject.ts | 9 +- .../JB721DelegateVersion/index.ts | 1 - .../useJB721DelegateVersion.ts | 22 -- .../useNftCollectionMetadataUri.ts | 40 ---- .../useNftCollectionPricingContext.ts | 31 --- .../useNftDeployerCanReconfigure.ts | 40 ---- .../contractReader/useNftFlagsOf.ts | 16 -- .../contractReader/useNftTiers.ts | 89 -------- .../contracts/useJB721DelegateAbi.ts | 57 ----- .../useJB721DelegateContractAddress.ts | 72 ------ .../contracts/useJB721TieredDelegate.ts | 21 -- .../contracts/useJB721TieredDelegateStore.ts | 21 -- .../useJBTiered721DelegateProjectDeployer.ts | 26 --- .../useProjectJB721DelegateVersion.ts | 20 -- .../useStoreofJB721TieredDelegate.ts | 28 --- .../transactor/useAdjustTiersTx.ts | 53 ----- .../transactor/useLaunchProjectWithNftsTx.ts | 172 -------------- ...seReconfigureV2V3FundingCycleWithNftsTx.ts | 212 ------------------ .../useSetNftOperatorPermissionsTx.ts | 28 --- .../hooks/JB721Delegate/useHasNftRewards.ts | 14 -- .../JB721Delegate/useNftAccountBalance.ts | 25 --- .../JB721Delegate/useNftCollectionMetadata.ts | 25 --- .../v4/hooks/JB721Delegate/useNftCredits.ts | 22 -- .../useNftRewardsEnabledForPay.ts | 14 -- .../v4/hooks/useCurrentRouteChainId.ts | 2 +- src/packages/v4/hooks/useLaunchProjectTx.ts | 6 +- .../v4/hooks/useLaunchProjectWithNftsTx.ts | 207 +++++++++++++++++ src/packages/v4/models/nfts.ts | 104 ++++++--- .../v4/utils/launchProjectTransformers.ts | 53 +++-- yarn.lock | 16 +- 31 files changed, 333 insertions(+), 1117 deletions(-) delete mode 100644 src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/useNftCredits.ts delete mode 100644 src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts create mode 100644 src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts diff --git a/package.json b/package.json index a3b93e6e65..c9b9f8708e 100644 --- a/package.json +++ b/package.json @@ -107,8 +107,8 @@ "graphql": "^16.8.1", "he": "^1.2.0", "jsonwebtoken": "^9.0.0", - "juice-sdk-core": "^11.0.0-alpha", - "juice-sdk-react": "^11.0.0-alpha", + "juice-sdk-core": "^11.5.0-alpha", + "juice-sdk-react": "^11.6.0-alpha", "juicebox-metadata-helper": "0.1.7", "less": "4.1.2", "lodash": "^4.17.21", diff --git a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts index 3159f2057f..bebbdb3a5f 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts @@ -1,3 +1,6 @@ +import { TransactionCallbacks } from 'models/transaction' +import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' +import { useLaunchProjectWithNftsTx } from 'packages/v4/hooks/useLaunchProjectWithNftsTx' import { useCallback, useMemo } from 'react' import { useAppSelector, @@ -5,10 +8,6 @@ import { useEditingV2V3FundingCycleDataSelector, useEditingV2V3FundingCycleMetadataSelector, } from 'redux/hooks/useAppSelector' - -import { TransactionCallbacks } from 'models/transaction' -import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' -import { useLaunchProjectWithNftsTx } from 'packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx' import { DEFAULT_NFT_FLAGS } from 'redux/slices/editingV2Project' import { NFT_FUNDING_CYCLE_METADATA_OVERRIDES } from 'utils/nftFundingCycleMetadataOverrides' import { buildJB721TierParams } from 'utils/nftRewards' @@ -21,7 +20,7 @@ import { buildJB721TierParams } from 'utils/nftRewards' * @returns A function that deploys a project with NFT rewards. */ export const useDeployNftProject = () => { - const launchProjectWithNftsTx = useLaunchProjectWithNftsTx() // v4ify this + const launchProjectWithNftsTx = useLaunchProjectWithNftsTx() const { projectMetadata, nftRewards, diff --git a/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts b/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts deleted file mode 100644 index 64f4509e03..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useJB721DelegateVersion } from './useJB721DelegateVersion' diff --git a/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts b/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts deleted file mode 100644 index 4a3b25ce62..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import axios from 'axios' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { isZeroAddress } from 'utils/address' - -export function useJB721DelegateVersion({ - dataSourceAddress, -}: { - dataSourceAddress: string | undefined -}) { - return useQuery({ - queryKey: ['JB721DelegateVersion', dataSourceAddress], - queryFn: async () => { - const res = await axios.get<{ version: JB721DelegateVersion }>( - `/api/juicebox/jb-721-delegate/${dataSourceAddress}`, - ) - - return res.data?.version - }, - enabled: !!dataSourceAddress && !isZeroAddress(dataSourceAddress), - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts deleted file mode 100644 index 75aa64f5d6..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' -import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useContext } from 'react' - -export function useNftCollectionMetadataUri( - dataSourceAddress: string | undefined, -) { - const { - contracts: { JB721TieredDelegateStore, JB721TieredDelegate }, - version, - } = useContext(JB721DelegateContractsContext) - - // V3/3.1 - const v3response = useV2ContractReader({ - contract: JB721TieredDelegateStore, - functionName: 'contractUriOf', - args: - version === JB721DelegateVersion.JB721DELEGATE_V3 || - version === JB721DelegateVersion.JB721DELEGATE_V3_1 - ? [dataSourceAddress] - : null, - }) - - const v3_2_response = useV2ContractReader({ - contract: JB721TieredDelegate, - functionName: 'contractURI', - args: - version === JB721DelegateVersion.JB721DELEGATE_V3_2 || - JB721DelegateVersion.JB721DELEGATE_V3_3 || - JB721DelegateVersion.JB721DELEGATE_V3_4 - ? undefined - : null, - }) - - return version === JB721DelegateVersion.JB721DELEGATE_V3 || - version === JB721DelegateVersion.JB721DELEGATE_V3_1 - ? v3response - : v3_2_response -} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts deleted file mode 100644 index 4f1c9fe681..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftCollectionPricingContext.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BigNumber } from 'ethers' -import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' -import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' -import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption' -import { useContext } from 'react' - -export type NftPricingContext = { - currency: V2V3CurrencyOption -} - -export function useNftCollectionPricingContext() { - const { - contracts: { JB721TieredDelegate }, - } = useContext(JB721DelegateContractsContext) - - const response = useV2ContractReader<[BigNumber, BigNumber, string]>({ - contract: JB721TieredDelegate, - functionName: 'pricingContext', - }) - - if (!response?.data) return { ...response, data: undefined } - - const [currency] = response.data - - return { - ...response, - data: { - currency: currency?.toNumber(), - } as NftPricingContext, - } -} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts deleted file mode 100644 index 0eb726cff2..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getAddress } from 'ethers/lib/utils' -import { useV2V3HasPermissions } from 'packages/v2v3/hooks/contractReader/useV2V3HasPermissions' -import { V2V3OperatorPermission } from 'packages/v2v3/models/v2v3Permissions' -import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' -import { useProjectControllerJB721DelegateVersion } from '../contracts/useProjectJB721DelegateVersion' - -/** - * Checks whether the given [projectOwnerAddress] has given the JBTiered721DelegateProjectDeployer - * permission to reconfigure the given [projectId]s funding cycle. - * - * This must be true for the following circumstances (non-exhaustive): - * - A project is reconfiguring their FC to include NFTs - * - A project is launching their V3 FC with NFTs - */ -export function useNftDeployerCanReconfigure({ - projectOwnerAddress, - projectId, -}: { - projectOwnerAddress: string | undefined - projectId: number | undefined -}) { - const JB721DelegateVersion = useProjectControllerJB721DelegateVersion() - const JBTiered721DelegateProjectDeployer = - useJBTiered721DelegateProjectDeployer({ version: JB721DelegateVersion }) - - const JBTiered721DelegateProjectDeployerAddress = - JBTiered721DelegateProjectDeployer - ? getAddress(JBTiered721DelegateProjectDeployer?.address) - : undefined - - const { data: JBTiered721DelegateProjectDeployerCanReconfigure } = - useV2V3HasPermissions({ - operator: JBTiered721DelegateProjectDeployerAddress, - account: projectOwnerAddress, - domain: projectId, - permissions: [V2V3OperatorPermission.RECONFIGURE], - }) - - return JBTiered721DelegateProjectDeployerCanReconfigure -} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts deleted file mode 100644 index 5a537f7166..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftFlagsOf.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { JBTiered721Flags } from 'models/nftRewards' -import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' -import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' -import { useContext } from 'react' - -export function useNftFlagsOf(dataSourceAddress: string | undefined) { - const { - contracts: { JB721TieredDelegateStore }, - } = useContext(JB721DelegateContractsContext) - - return useV2ContractReader({ - contract: JB721TieredDelegateStore, - functionName: 'flagsOf', - args: [dataSourceAddress], - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts deleted file mode 100644 index 8698d08f38..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contractReader/useNftTiers.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { JB721TierV3, JB_721_TIER_V3_2 } from 'models/nftRewards' -import { MAX_NFT_REWARD_TIERS } from 'packages/v2v3/constants/nftRewards' -import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' -import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useContext } from 'react' - -function buildArgs( - version: JB721DelegateVersion, - { - dataSourceAddress, - limit, - }: { dataSourceAddress: string | undefined; limit?: number }, -) { - switch (version) { - case JB721DelegateVersion.JB721DELEGATE_V3: - return [ - dataSourceAddress, - 0, // _startingId - limit ?? MAX_NFT_REWARD_TIERS, - ] - case JB721DelegateVersion.JB721DELEGATE_V3_1: - return [ - dataSourceAddress, - 0, // _category - 0, // _startingId - limit ?? MAX_NFT_REWARD_TIERS, - ] - case JB721DelegateVersion.JB721DELEGATE_V3_2: - return [ - dataSourceAddress, - [], // _categories - false, // _includeResolvedUri, return in each tier a result from a tokenUriResolver if one is included in the delegate - 0, // _startingId - limit ?? MAX_NFT_REWARD_TIERS, - ] - - case JB721DelegateVersion.JB721DELEGATE_V3_3: - return [ - dataSourceAddress, - [], // _categories - false, // _includeResolvedUri, return in each tier a result from a tokenUriResolver if one is included in the delegate - 0, // _startingId - limit ?? MAX_NFT_REWARD_TIERS, - ] - - case JB721DelegateVersion.JB721DELEGATE_V3_4: // unchanged - return [ - dataSourceAddress, - [], // _categories - false, // _includeResolvedUri, return in each tier a result from a tokenUriResolver if one is included in the delegate - 0, // _startingId - limit ?? MAX_NFT_REWARD_TIERS, - ] - default: - return null - } -} - -export function useNftTiers({ - dataSourceAddress, - limit, - shouldFetch, -}: { - dataSourceAddress: string | undefined - limit?: number - shouldFetch?: boolean -}) { - const { - contracts: { JB721TieredDelegateStore }, - version, - } = useContext(JB721DelegateContractsContext) - - // send null when project has no dataSource, so the fetch doesn't execute. - const args = - shouldFetch && version - ? buildArgs(version, { dataSourceAddress, limit }) - : null - - return useV2ContractReader({ - contract: JB721TieredDelegateStore, - functionName: - version === JB721DelegateVersion.JB721DELEGATE_V3 || - version === JB721DelegateVersion.JB721DELEGATE_V3_1 - ? 'tiers' - : 'tiersOf', - args, - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts deleted file mode 100644 index b8fe9e5f3d..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ContractInterface } from 'ethers' -import { ContractJson } from 'models/contracts' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useEffect, useState } from 'react' - -type JB721DelegateContractName = - | 'JB721TieredGovernance' - | 'IJBTiered721DelegateStore' - | 'IJBTiered721Delegate' - | 'IJBTiered721DelegateProjectDeployer' - -export async function loadJB721DelegateJson( - contractName: JB721DelegateContractName, - version: JB721DelegateVersion, -): Promise { - console.info('Loading JB721Delegate contract json', version, contractName) - - // NOTE: imports are specified explicitly to avoid Webpack causing V8 to run out of memory and crash during compilation. - if (contractName === 'JB721TieredGovernance') { - return await import( - `@jbx-protocol/juice-721-delegate-v${version}/out/JB721TieredGovernance.sol/JB721TieredGovernance.json` - ) - } - - if (contractName === 'IJBTiered721DelegateStore') { - return await import( - `@jbx-protocol/juice-721-delegate-v${version}/out/IJBTiered721DelegateStore.sol/IJBTiered721DelegateStore.json` - ) - } - - if (contractName === 'IJBTiered721Delegate') { - return await import( - `@jbx-protocol/juice-721-delegate-v${version}/out/IJBTiered721Delegate.sol/IJBTiered721Delegate.json` - ) - } - - if (contractName === 'IJBTiered721DelegateProjectDeployer') { - return await import( - `@jbx-protocol/juice-721-delegate-v${version}/out/IJBTiered721DelegateProjectDeployer.sol/IJBTiered721DelegateProjectDeployer.json` - ) - } -} - -export function useJB721DelegateAbi( - contractName: JB721DelegateContractName, - version: JB721DelegateVersion | undefined, -) { - const [abi, setAbi] = useState(undefined) - - useEffect(() => { - if (!version) return - - loadJB721DelegateJson(contractName, version).then(json => setAbi(json?.abi)) - }, [version, contractName]) - - return abi -} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts deleted file mode 100644 index d883ebe226..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { readNetwork } from 'constants/networks' -import { ForgeDeploy, addressFor } from 'forge-run-parser' -import { NetworkName } from 'models/networkName' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useEffect, useState } from 'react' - -/** - * Some addresses aren't in the forge deployment manifests, so we have to hardcode them here. - */ -const ADDRESSES: { - [k in JB721DelegateVersion]?: { - [k in NetworkName]?: { - [k: string]: string - } - } -} = { - [JB721DelegateVersion.JB721DELEGATE_V3_4]: { - [NetworkName.sepolia]: { - JBTiered721DelegateStore: '0xd7F9Ee12b5De2388109C9dD4fAAf39BEfe4C92FB', // the store from 3.3 - JBTiered721DelegateProjectDeployer: - '0x70b59C0ad71b8e7c9B57328bEb7Ad5921b44dB81', - }, - [NetworkName.mainnet]: { - JBTiered721DelegateStore: '0x615B5b50F1Fc591AAAb54e633417640d6F2773Fd', // the store from 3.3 - JBTiered721DelegateProjectDeployer: - '0xFbD1B7dE4082826Bf4BaA68D020eFA5c2707Fb3e', - }, - }, -} - -async function loadJB721DelegateDeployment(version: JB721DelegateVersion) { - return (await import( - `@jbx-protocol/juice-721-delegate-v${version}/broadcast/Deploy.s.sol/${readNetwork.chainId}/run-latest.json` - )) as ForgeDeploy -} - -export async function loadJB721DelegateAddress( - contractName: string, - version: JB721DelegateVersion, -) { - const hardcodedAddress = - ADDRESSES[version]?.[readNetwork.name]?.[contractName] - if (hardcodedAddress) return hardcodedAddress - - const forgeGeployment = await loadJB721DelegateDeployment(version) - const forgeAddress = addressFor(contractName, forgeGeployment) - return forgeAddress! -} - -export function useJB721DelegateContractAddress({ - contractName, - version, -}: { - contractName: string - version: JB721DelegateVersion | undefined -}): string | undefined { - const [address, setAddress] = useState(undefined) - - useEffect(() => { - async function load() { - if (!version) return - - const address = await loadJB721DelegateAddress(contractName, version) - - setAddress(address) - } - - load() - }, [contractName, version]) - - return address -} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts deleted file mode 100644 index 2542213e49..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Contract } from 'ethers' -import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useJB721DelegateAbi } from './useJB721DelegateAbi' - -export function useJB721TieredDelegate({ - address, - version, -}: { - address: string | undefined - version: JB721DelegateVersion | undefined -}): Contract | undefined { - const JB721TieredDelegateJson = useJB721DelegateAbi( - 'IJBTiered721Delegate', - version, - ) - return useLoadContractFromAddress({ - address, - abi: JB721TieredDelegateJson, - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts deleted file mode 100644 index 46f2cc9261..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Contract } from 'ethers' -import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useJB721DelegateAbi } from './useJB721DelegateAbi' - -export function useJB721TieredDelegateStore({ - address, - version, -}: { - address: string | undefined - version: JB721DelegateVersion | undefined -}): Contract | undefined { - const JB721DelegateStoreJson = useJB721DelegateAbi( - 'IJBTiered721DelegateStore', - version, - ) - return useLoadContractFromAddress({ - address, - abi: JB721DelegateStoreJson, - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts deleted file mode 100644 index 00998b448a..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Contract } from 'ethers' -import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress' -import { useJB721DelegateAbi } from 'packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useJB721DelegateContractAddress } from './useJB721DelegateContractAddress' - -export function useJBTiered721DelegateProjectDeployer({ - version, -}: { - version: JB721DelegateVersion | undefined -}): Contract | undefined { - const JBTiered721DelegateProjectDeployerJson = useJB721DelegateAbi( - 'IJBTiered721DelegateProjectDeployer', - version, - ) - - const address = useJB721DelegateContractAddress({ - contractName: 'JBTiered721DelegateProjectDeployer', - version, - }) - - return useLoadContractFromAddress({ - address, - abi: JBTiered721DelegateProjectDeployerJson, - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts deleted file mode 100644 index 28bdf37e79..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' -import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' -import { - JB721DelegateVersion, - V2V3ContractName, -} from 'packages/v2v3/models/contracts' -import { useContext } from 'react' - -/** - * Return the JB721Delegate version that should be used with the project's current JBController version. - */ -export function useProjectControllerJB721DelegateVersion() { - const { versions } = useContext(V2V3ProjectContractsContext) - const delegateVersion = - versions.JBControllerVersion === V2V3ContractName.JBController - ? JB721DelegateVersion.JB721DELEGATE_V3 - : DEFAULT_JB_721_DELEGATE_VERSION // the default delegate version for controller >= 3.1 - - return delegateVersion -} diff --git a/src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts b/src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts deleted file mode 100644 index dc0392b183..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Contract } from 'ethers' -import { useContractReadValue } from 'hooks/ContractReader' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { useJB721TieredDelegateStore } from './useJB721TieredDelegateStore' - -export function useStoreOfJB721TieredDelegate({ - JB721TieredDelegate, - version, -}: { - JB721TieredDelegate: Contract | undefined - version: JB721DelegateVersion | undefined -}) { - // get the JBTiered721DelegateStore address on-chain - const { value: storeAddress, loading } = useContractReadValue( - { - contract: JB721TieredDelegate, - functionName: 'store', - args: [], - }, - ) - - const JBTiered721DelegateStore = useJB721TieredDelegateStore({ - address: storeAddress, - version, - }) - - return { value: JBTiered721DelegateStore, loading } -} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts deleted file mode 100644 index e76e70da49..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/transactor/useAdjustTiersTx.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { t } from '@lingui/macro' -import { TransactionContext } from 'contexts/Transaction/TransactionContext' -import { TransactorInstance } from 'hooks/useTransactor' -import { - JB721TierParams, - JB_721_TIER_PARAMS_V3_1, - JB_721_TIER_PARAMS_V3_2, -} from 'models/nftRewards' -import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' -import { useContext } from 'react' - -export function useAdjustTiersTx(): TransactorInstance<{ - newTiers: ( - | JB721TierParams - | JB_721_TIER_PARAMS_V3_1 - | JB_721_TIER_PARAMS_V3_2 - )[] - tierIdsChanged: number[] -}> { - const { transactor } = useContext(TransactionContext) - const { - contracts: { JB721TieredDelegate }, - } = useContext(JB721DelegateContractsContext) - - return async ({ newTiers, tierIdsChanged }, txOpts) => { - if (!transactor || !JB721TieredDelegate) { - const missingParam = !transactor - ? 'transactor' - : !JB721TieredDelegate - ? 'JB721TieredDelegate' - : null - txOpts?.onError?.( - new DOMException( - `Transaction failed, missing argument "${ - missingParam ?? '' - }".`, - ), - ) - - return Promise.resolve(false) - } - - const args = [ - newTiers, // _tiersToAdd - tierIdsChanged, // _tierIdsToRemove - ] - - return transactor(JB721TieredDelegate, 'adjustTiers', args, { - ...txOpts, - title: t`Editing NFTs`, - }) - } -} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts deleted file mode 100644 index cb251ccb77..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { - JBDeployTiered721DelegateData, - JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1 -} from 'models/nftRewards' -import { JB721DelegateLaunchProjectData, JBDeploy721TiersHookConfig, LaunchProjectWithNftsTxArgs } from 'packages/v4/models/nfts' - -import { TransactorInstance } from 'hooks/useTransactor' -import { useWallet } from 'hooks/Wallet' -import { useJBContractContext } from 'juice-sdk-react' -import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' -import { useDefaultJBController } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBController' -import { useDefaultJBETHPaymentTerminal } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBETHPaymentTerminal' -import { useV2ProjectTitle } from 'packages/v2v3/hooks/useProjectTitle' -import { - isValidMustStartAtOrAfter -} from 'packages/v2v3/utils/fundingCycle' -import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' -import { useJB721DelegateContractAddress } from '../contracts/useJB721DelegateContractAddress' -import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' - -function buildArgs({ - owner, - deployTiered721DelegateData, - launchProjectData, - JBControllerAddress, -}: { - owner: string - JBControllerAddress: string - deployTiered721DelegateData: - | JBDeployTiered721DelegateData - | JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1 - launchProjectData: JB721DelegateLaunchProjectData -}) { - const baseArgs = [ - owner, - deployTiered721DelegateData, //_deployTiered721DelegateData - launchProjectData, // _launchProjectData - ] - - return [...baseArgs, JBControllerAddress] -} - -export function useLaunchProjectWithNftsTx(): TransactorInstance { - const { contracts } = useJBContractContext() - const defaultJBController = useDefaultJBController() - - const { userAddress } = useWallet() - const projectTitle = useV2ProjectTitle() - const defaultJBETHPaymentTerminal = useDefaultJBETHPaymentTerminal() - const JBTiered721DelegateProjectDeployer = - useJBTiered721DelegateProjectDeployer({ - version: DEFAULT_JB_721_DELEGATE_VERSION, - }) - const JBTiered721DelegateStoreAddress = useJB721DelegateContractAddress({ - contractName: 'JBTiered721DelegateStore', - version: DEFAULT_JB_721_DELEGATE_VERSION, - }) - - return async ( - { - tiered721DelegateData: { - collectionUri, - collectionName, - collectionSymbol, - currency, - tiers, - flags, - governanceType, - }, - projectData: { - projectMetadataCID, - fundingCycleData, - fundingCycleMetadata, - fundAccessConstraints, - groupedSplits = [], - mustStartAtOrAfter = DEFAULT_MUST_START_AT_OR_AFTER, - owner, - }, - }, - txOpts, - ) => { - if ( - !userAddress || - !contracts || - !defaultJBController || - !defaultJBETHPaymentTerminal || - !JBTiered721DelegateProjectDeployer || - !JBTiered721DelegateStoreAddress || - !isValidMustStartAtOrAfter(mustStartAtOrAfter, fundingCycleData.duration) - ) { - const missingParam = !userAddress - ? 'userAddress' - : !contracts - ? 'contracts' - : !defaultJBController - ? 'defaultJBController' - : !JBTiered721DelegateStoreAddress - ? 'JBTiered721DelegateStoreAddress' - : !JBTiered721DelegateProjectDeployer - ? 'JBTiered721DelegateProjectDeployer' - : null - - txOpts?.onError?.( - new DOMException( - `Transaction failed, missing argument "${ - missingParam ?? '' - }".`, - ), - ) - - return Promise.resolve(false) - } - const _owner = owner?.length ? owner : userAddress - - const deployTiered721DelegateData: JBDeploy721TiersHookConfig = { - name: collectionName, - symbol: collectionSymbol, - baseUri: '', // ? - tokenUriResolver: '', // ? - contractUri: '', //collectionUri ; - tiersConfig: tiers, - reserveBeneficiary: '', //?; - flags, - } - - // NFT launch tx does not accept `useDataHookForPay` and `dataHook` (see contracts:`JBPayDataHookRulesetMetadata`) - // const dataHookRulesetMetadata: JBPayDataHookRulesetMetadata = omit( - // rulesetMetadata, - // ['useDataSourceForPay', 'dataSource'], - // ) - - // const launchProjectData: JB721DelegateLaunchProjectData = { - // projectMetadata: { - // domain: JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN, - // content: projectMetadataCID, - // }, - // data: fundingCycleData, - // metadata: dataHookRulesetMetadata, - // mustStartAtOrAfter, - // groupedSplits, - // fundAccessConstraints, - // terminals: [defaultJBETHPaymentTerminal?.address], - // memo: DEFAULT_MEMO, - // } // _launchProjectData - - // const args = buildArgs({ - // owner: _owner, - // deployTiered721DelegateData, - // launchProjectData: transformV2V3CreateArgsToV4(launchProjectData), - // JBControllerAddress: defaultJBController.address, - // }) - - // if (!args) { - // txOpts?.onError?.( - // new DOMException(`Transaction failed, failed to build args`), - // ) - - // return Promise.resolve(false) - // } - - // return transactor( - // JBTiered721DelegateProjectDeployer, - // 'launchProjectFor', - // args, - // { - // ...txOpts, - // title: t`Launch ${projectTitle}`, - // }, - // ) - return Promise.resolve(false) - } -} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts deleted file mode 100644 index de53a7fbdd..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { t } from '@lingui/macro' -import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext' -import { TransactionContext } from 'contexts/Transaction/TransactionContext' -import { getAddress } from 'ethers/lib/utils' -import { TransactorInstance } from 'hooks/useTransactor' -import omit from 'lodash/omit' -import { - JBDeployTiered721DelegateData, - JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1, -} from 'models/nftRewards' -import { V2V3ContractsContext } from 'packages/v2v3/contexts/Contracts/V2V3ContractsContext' -import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext' -import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' -import { useJBPrices } from 'packages/v2v3/hooks/JBPrices' -import { ReconfigureFundingCycleTxParams } from 'packages/v2v3/hooks/transactor/useReconfigureV2V3FundingCycleTx' -import { useV2ProjectTitle } from 'packages/v2v3/hooks/useProjectTitle' -import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' -import { - JBPayDataSourceFundingCycleMetadata, - V2V3FundAccessConstraint, - V2V3FundingCycleData, -} from 'packages/v2v3/models/fundingCycle' -import { GroupedSplits, SplitGroup } from 'packages/v2v3/models/splits' -import { V2V3_CURRENCY_ETH } from 'packages/v2v3/utils/currency' -import { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle' -import { useContext } from 'react' -import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' -import { NftRewardsData } from 'redux/slices/editingV2Project/types' -import { - buildDeployTiered721DelegateData, - buildJB721TierParams, - defaultNftCollectionName, -} from 'utils/nftRewards' -import { useJB721DelegateContractAddress } from '../contracts/useJB721DelegateContractAddress' -import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' -import { useProjectControllerJB721DelegateVersion } from '../contracts/useProjectJB721DelegateVersion' - -type ReconfigureWithNftsTxArgs = { - reconfigureData: ReconfigureFundingCycleTxParams - tiered721DelegateData: NftRewardsData -} - -interface JB721DelegateReconfigureFundingCycleData { - data: V2V3FundingCycleData - metadata: JBPayDataSourceFundingCycleMetadata - memo?: string - fundAccessConstraints: V2V3FundAccessConstraint[] - groupedSplits?: GroupedSplits[] - mustStartAtOrAfter?: string // epoch seconds. anything less than "now" will start immediately. -} - -function buildArgs( - version: JB721DelegateVersion, - { - projectId, - deployTiered721DelegateData, - reconfigureFundingCyclesData, - JBControllerAddress, - }: { - projectId: number - JBControllerAddress: string - deployTiered721DelegateData: - | JBDeployTiered721DelegateData - | JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1 - reconfigureFundingCyclesData: JB721DelegateReconfigureFundingCycleData - }, -) { - const baseArgs = [ - projectId, - deployTiered721DelegateData, //_deployTiered721DelegateData - reconfigureFundingCyclesData, // _reconfigureFundingCyclesData - ] - - if (version === JB721DelegateVersion.JB721DELEGATE_V3) { - return baseArgs - } - - return [...baseArgs, JBControllerAddress] // v3.1, 3.2 requires us to pass the controller address in -} - -export function useReconfigureV2V3FundingCycleWithNftsTx(): TransactorInstance { - const { transactor } = useContext(TransactionContext) - const { contracts } = useContext(V2V3ContractsContext) - const { - contracts: { JBController: projectJBController }, - } = useContext(V2V3ProjectContractsContext) - const { projectId } = useContext(ProjectMetadataContext) - const { projectOwnerAddress } = useContext(V2V3ProjectContext) - - const JB721DelegateVersion = useProjectControllerJB721DelegateVersion() - const JBTiered721DelegateStoreAddress = useJB721DelegateContractAddress({ - contractName: 'JBTiered721DelegateStore', - version: JB721DelegateVersion, - }) - const JBTiered721DelegateProjectDeployer = - useJBTiered721DelegateProjectDeployer({ version: JB721DelegateVersion }) - const JBPrices = useJBPrices() - - const projectTitle = useV2ProjectTitle() - - return async ( - { - reconfigureData: { - fundingCycleData, - fundingCycleMetadata, - fundAccessConstraints, - groupedSplits = [], - mustStartAtOrAfter = DEFAULT_MUST_START_AT_OR_AFTER, - memo, - }, - tiered721DelegateData: { - governanceType, - rewardTiers, - CIDs, - collectionMetadata, - flags, - }, - }, - txOpts, - ) => { - const collectionName = - collectionMetadata.name ?? defaultNftCollectionName(projectTitle) - - if ( - !transactor || - !projectId || - !CIDs || - !rewardTiers || - !JBTiered721DelegateStoreAddress || - !projectOwnerAddress || - !contracts || - !projectJBController || - !JBPrices || - !isValidMustStartAtOrAfter( - mustStartAtOrAfter, - fundingCycleData.duration, - ) || - !collectionName || - !JBTiered721DelegateProjectDeployer - ) { - txOpts?.onDone?.() - return Promise.resolve(false) - } - - // build `delegateData` - const tiers = buildJB721TierParams({ - cids: CIDs, - rewardTiers, - version: JB721DelegateVersion, - }) - - const deployTiered721DelegateData = buildDeployTiered721DelegateData( - { - collectionUri: collectionMetadata.uri ?? '', - collectionName, - collectionSymbol: collectionMetadata.symbol ?? '', - currency: V2V3_CURRENCY_ETH, // TODO use the user-set currency form the form - governanceType, - tiers, - ownerAddress: projectOwnerAddress, - contractAddresses: { - JBDirectoryAddress: getAddress(contracts.JBDirectory.address), - JBFundingCycleStoreAddress: getAddress( - contracts.JBFundingCycleStore.address, - ), - JBPricesAddress: getAddress(JBPrices.address), - JBTiered721DelegateStoreAddress, - }, - flags, - }, - JB721DelegateVersion, - ) - - // NFT launch tx does not accept `useDataSourceForPay` and `dataSource` (see contracts:`JBPayDataSourceFundingCycleMetadata`) - const dataSourceFCMetadata: JBPayDataSourceFundingCycleMetadata = omit( - fundingCycleMetadata, - ['useDataSourceForPay', 'dataSource'], - ) - - const reconfigureFundingCyclesData: JB721DelegateReconfigureFundingCycleData = - { - data: fundingCycleData, - metadata: dataSourceFCMetadata, - mustStartAtOrAfter, - groupedSplits, - fundAccessConstraints, - memo, - } - - const args = buildArgs(JB721DelegateVersion, { - projectId, - deployTiered721DelegateData, - reconfigureFundingCyclesData, - JBControllerAddress: projectJBController.address, - }) - - if (!args) { - txOpts?.onDone?.() - return Promise.resolve(false) - } - - return transactor( - JBTiered721DelegateProjectDeployer, - 'reconfigureFundingCyclesOf', - args, - { - ...txOpts, - title: t`Setup new NFTs for ${projectTitle}`, - }, - ) - } -} diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts deleted file mode 100644 index 670d9e3c68..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { getAddress } from 'ethers/lib/utils' -import { TransactorInstance } from 'hooks/useTransactor' -import { useSetOperatorTx } from 'packages/v2v3/hooks/transactor/useSetOperatorTx' -import { V2V3OperatorPermission } from 'packages/v2v3/models/v2v3Permissions' -import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer' -import { useProjectControllerJB721DelegateVersion } from '../contracts/useProjectJB721DelegateVersion' - -export function useSetNftOperatorPermissionsTx(): TransactorInstance { - const setOperatorTx = useSetOperatorTx() - - const JB721DelegateVersion = useProjectControllerJB721DelegateVersion() - const JBTiered721DelegateProjectDeployer = - useJBTiered721DelegateProjectDeployer({ version: JB721DelegateVersion }) - - return (_, txOpts) => { - const operator = getAddress( - JBTiered721DelegateProjectDeployer?.address ?? '', - ) - - return setOperatorTx( - { - operatorAddress: operator, - permissionIndexes: [V2V3OperatorPermission.RECONFIGURE], - }, - txOpts, - ) - } -} diff --git a/src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts b/src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts deleted file mode 100644 index 4cb5e87c64..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/useHasNftRewards.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' -import { useContext } from 'react' - -export function useHasNftRewards(): { value: boolean; loading: boolean } { - const { - version: JB721DelegateVersion, - loading: { JB721TieredDelegateStoreLoading }, - } = useContext(JB721DelegateContractsContext) - - return { - value: Boolean(JB721DelegateVersion), - loading: JB721TieredDelegateStoreLoading, - } -} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts b/src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts deleted file mode 100644 index 7dce0ce200..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/useNftAccountBalance.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useNfTsQuery } from 'generated/graphql' -import { client } from 'lib/apollo/client' - -/** - * Return all the project's NFTs that are owned by the given account. - */ -export function useNftAccountBalance({ - dataSourceAddress, - accountAddress, -}: { - dataSourceAddress: string | undefined - accountAddress: string | undefined -}) { - return useNfTsQuery({ - client, - variables: { - where: { - ...(dataSourceAddress - ? { collection_: { address: dataSourceAddress } } - : {}), - ...(accountAddress ? { owner_: { wallet: accountAddress } } : {}), - }, - }, - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts b/src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts deleted file mode 100644 index 0a4e836f38..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/useNftCollectionMetadata.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { ipfsFetch } from 'lib/api/ipfs' -import { NftCollectionMetadata } from 'models/nftRewards' -import { cidFromUrl } from 'utils/ipfs' - -// gets Nft Collection metadata from IPFS -export function useNftCollectionMetadata(uri: string | undefined) { - return useQuery({ - queryKey: ['nft-collection-metadata', uri], - queryFn: async () => { - if (!uri) { - throw new Error('NFT Contract URI not specified.') - } - - const cid = cidFromUrl(uri) - if (!cid) { - throw new Error('NFT Contract URI invalid.') - } - - const response = await ipfsFetch(cid) - return response.data - }, - enabled: !!uri, - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftCredits.ts b/src/packages/v4/hooks/JB721Delegate/useNftCredits.ts deleted file mode 100644 index c1ec55d554..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/useNftCredits.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { BigNumber } from 'ethers' -import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext' -import useV2ContractReader from 'packages/v2v3/hooks/contractReader/useV2ContractReader' -import { useContext } from 'react' - -/** - * Return the wei amount of unused credits for a given address. - * @example If the user paid 1 ETH and didnt mint a 1 ETH NFT, then they have 1 ETH of unused credits. - * @param address - * @returns - */ -export function useNftCredits(address: string | undefined) { - const { - contracts: { JB721TieredDelegate }, - } = useContext(JB721DelegateContractsContext) - - return useV2ContractReader({ - contract: JB721TieredDelegate, - functionName: 'creditsOf', - args: address ? [address] : null, - }) -} diff --git a/src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts b/src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts deleted file mode 100644 index 1336200070..0000000000 --- a/src/packages/v4/hooks/JB721Delegate/useNftRewardsEnabledForPay.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NftRewardsContext } from 'packages/v2v3/contexts/NftRewards/NftRewardsContext' -import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext' -import { hasDataSourceForPay } from 'packages/v2v3/utils/fundingCycle' -import { useContext, useMemo } from 'react' - -export function useNftRewardsEnabledForPay() { - const { fundingCycleMetadata } = useContext(V2V3ProjectContext) - const { - nftRewards: { rewardTiers }, - } = useContext(NftRewardsContext) - const hasNftRewards = useMemo(() => rewardTiers?.length !== 0, [rewardTiers]) - - return hasNftRewards && hasDataSourceForPay(fundingCycleMetadata) -} diff --git a/src/packages/v4/hooks/useCurrentRouteChainId.ts b/src/packages/v4/hooks/useCurrentRouteChainId.ts index 58db1b737a..d5766f5bf1 100644 --- a/src/packages/v4/hooks/useCurrentRouteChainId.ts +++ b/src/packages/v4/hooks/useCurrentRouteChainId.ts @@ -5,7 +5,7 @@ export function useCurrentRouteChainId() { const router = useRouter() const { chainName } = router.query if (!chainName) { - return null + return undefined } return chainNameMap[chainName as string] diff --git a/src/packages/v4/hooks/useLaunchProjectTx.ts b/src/packages/v4/hooks/useLaunchProjectTx.ts index 5b9872dc10..cf1d758f64 100644 --- a/src/packages/v4/hooks/useLaunchProjectTx.ts +++ b/src/packages/v4/hooks/useLaunchProjectTx.ts @@ -32,7 +32,7 @@ export interface LaunchTxOpts { * Return the project ID created from a `launchProjectFor` transaction. * @param txReceipt receipt of `launchProjectFor` transaction */ -const getProjectIdFromLaunchReceipt = ( +export const getProjectIdFromLaunchReceipt = ( txReceipt: WaitForTransactionReceiptReturnType, ): number => { const projectIdHex: string | undefined = @@ -47,14 +47,14 @@ const getProjectIdFromLaunchReceipt = ( * The contract addresses to use for deployment * @todo not ideal to hardcode these addresses */ -const SUPPORTED_JB_MULTITERMINAL_ADDRESS = { +export const SUPPORTED_JB_MULTITERMINAL_ADDRESS = { '84532': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, '421614': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, '11155111': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, '11155420': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, } -const SUPPORTED_JB_CONTROLLER_ADDRESS = { +export const SUPPORTED_JB_CONTROLLER_ADDRESS = { '84532': '0x219A5cE6d1c512D5b050ad2E3d380b8746BE0Cb8' as Address, '421614': '0x219A5cE6d1c512D5b050ad2E3d380b8746BE0Cb8' as Address, '11155111': '0x219A5cE6d1c512D5b050ad2E3d380b8746BE0Cb8' as Address, diff --git a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts new file mode 100644 index 0000000000..0bf128306a --- /dev/null +++ b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts @@ -0,0 +1,207 @@ +import { waitForTransactionReceipt } from '@wagmi/core' +import { JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN } from 'constants/metadataDomain' +import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext' +import { useWallet } from 'hooks/Wallet' +import { DEFAULT_MEMO, NATIVE_TOKEN } from 'juice-sdk-core' +import { + useJBContractContext, + useReadJb721TiersHookStoreTiersOf, + useWriteJb721TiersHookProjectDeployerLaunchProjectFor, +} from 'juice-sdk-react' +import { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle' +import { + JBDeploy721TiersHookConfig, + LaunchProjectWithNftsTxArgs, +} from 'packages/v4/models/nfts' +import { useContext } from 'react' +import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' +import { + Address, + toBytes, + toHex, + WaitForTransactionReceiptReturnType, + zeroAddress, +} from 'viem' +import { + LaunchV2V3ProjectArgs, + transformV2V3CreateArgsToV4, +} from '../utils/launchProjectTransformers' +import { wagmiConfig } from '../wagmiConfig' +import { useCurrentRouteChainId } from './useCurrentRouteChainId' +import { + getProjectIdFromLaunchReceipt, + LaunchTxOpts, + SUPPORTED_JB_CONTROLLER_ADDRESS, + SUPPORTED_JB_MULTITERMINAL_ADDRESS, +} from './useLaunchProjectTx' +function createSalt() { + const base: string = '0x' + Math.random().toString(16).slice(2) // idk lol + const salt = toHex(toBytes(base, { size: 32 })) + + return salt +} + +/** + * The contract addresses to use for deployment + * @todo not ideal to hardcode these addresses + */ +export const SUPPORTED_JB_721_TIER_STORE = { + '84532': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, + '421614': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, + '11155111': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, + '11155420': '0x4DeF0AA5B9CA095d11705284221b2878731ab4EF' as Address, +} + +/** + * + * TODO still wip + */ +export function useLaunchProjectWithNftsTx() { + const { contracts } = useJBContractContext() + const { addTransaction } = useContext(TxHistoryContext) + + const { userAddress } = useWallet() + const chainId = useCurrentRouteChainId() + const defaultJBController = chainId + ? SUPPORTED_JB_CONTROLLER_ADDRESS[chainId] + : undefined + + const defaultJBETHPaymentTerminal = chainId + ? SUPPORTED_JB_MULTITERMINAL_ADDRESS[chainId] + : undefined + const JBTiered721DelegateStoreAddress = chainId + ? SUPPORTED_JB_721_TIER_STORE[chainId] + : undefined + + const { writeContractAsync: writeLaunchProject } = + useWriteJb721TiersHookProjectDeployerLaunchProjectFor() + + return async ( + { + tiered721DelegateData: { + collectionUri, + collectionName, + collectionSymbol, + currency, + tiers, + flags, + }, + projectData: { + projectMetadataCID, + fundingCycleData, + fundingCycleMetadata, + fundAccessConstraints, + groupedSplits = [], + mustStartAtOrAfter = DEFAULT_MUST_START_AT_OR_AFTER, + owner, + }, + }: LaunchProjectWithNftsTxArgs, + { + onTransactionPending: onTransactionPendingCallback, + onTransactionConfirmed: onTransactionConfirmedCallback, + onTransactionError: onTransactionErrorCallback, + }: LaunchTxOpts, + ) => { + if ( + !userAddress || + !contracts || + !defaultJBController || + !defaultJBETHPaymentTerminal || + !JBTiered721DelegateStoreAddress || + !isValidMustStartAtOrAfter(mustStartAtOrAfter, fundingCycleData.duration) + ) { + const missingParam = !userAddress + ? 'userAddress' + : !contracts + ? 'contracts' + : !defaultJBController + ? 'defaultJBController' + : !JBTiered721DelegateStoreAddress + ? 'JBTiered721DelegateProjectDeployer' + : null + + onTransactionErrorCallback?.( + new DOMException( + `Transaction failed, missing argument "${ + missingParam ?? '' + }".`, + ), + ) + + return Promise.resolve(false) + } + const _owner = owner?.length ? owner : userAddress + + const deployTiered721DelegateData: JBDeploy721TiersHookConfig = { + name: collectionName, + symbol: collectionSymbol, + baseUri: '', // ? + tokenUriResolver: zeroAddress, // ? + contractUri: '', //collectionUri ; ? + tiersConfig: tiers, + reserveBeneficiary: zeroAddress, //?; + flags, + } + + const v2v3LaunchProjectArgs = [ + _owner, + [projectMetadataCID, JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN], + fundingCycleData, + fundingCycleMetadata, + mustStartAtOrAfter, + groupedSplits, + fundAccessConstraints, + [defaultJBETHPaymentTerminal], // _terminals, just supporting single for now + // Eventually should be something like: + // getTerminalsFromFundAccessConstraints( + // fundAccessConstraints, + // contracts.primaryNativeTerminal.data, + // ), + DEFAULT_MEMO, + ] as LaunchV2V3ProjectArgs + const launchProjectData = transformV2V3CreateArgsToV4({ + v2v3Args: v2v3LaunchProjectArgs, + primaryNativeTerminal: defaultJBETHPaymentTerminal, + currencyTokenAddress: NATIVE_TOKEN, + }) + + const args = [ + owner, + deployTiered721DelegateData, //_deployTiered721DelegateData + launchProjectData, // _launchProjectData, + defaultJBController, + createSalt(), + ] + + try { + // SIMULATE TX: TODO update for nfts + // const encodedData = encodeFunctionData({ + // abi: jbControllerAbi, // ABI of the contract + // functionName: 'launchProjectFor', + // args, + // }) + + const hash = await writeLaunchProject({ + chainId, + args, + }) + + type x = typeof useReadJb721TiersHookStoreTiersOf + + onTransactionPendingCallback(hash) + addTransaction?.('Launch Project', { hash }) + const transactionReceipt: WaitForTransactionReceiptReturnType = + await waitForTransactionReceipt(wagmiConfig, { + hash, + }) + + const newProjectId = getProjectIdFromLaunchReceipt(transactionReceipt) + + onTransactionConfirmedCallback(hash, newProjectId) + } catch (e) { + onTransactionErrorCallback( + (e as Error) ?? new Error('Transaction failed'), + ) + } + } +} diff --git a/src/packages/v4/models/nfts.ts b/src/packages/v4/models/nfts.ts index be5c2fe7e9..8849b97e3d 100644 --- a/src/packages/v4/models/nfts.ts +++ b/src/packages/v4/models/nfts.ts @@ -1,45 +1,89 @@ -import { JBRulesetData, JBRulesetMetadata } from "juice-sdk-core"; +import { JBRulesetData, JBRulesetMetadata } from 'juice-sdk-core' -import { LaunchV2V3ProjectData } from "packages/v2v3/hooks/transactor/useLaunchProjectTx"; -import { Address } from "viem"; -import { LaunchV4ProjectGroupedSplit } from "../utils/launchProjectTransformers"; -import { FundAccessLimitGroup } from "./fundAccessLimits"; -import { LaunchProjectJBTerminal } from "./terminals"; +import { jb721TiersHookStoreAbi } from 'juice-sdk-react' +import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' +import { Address, ContractFunctionReturnType } from 'viem' +import { LaunchV4ProjectGroupedSplit } from '../utils/launchProjectTransformers' +import { FundAccessLimitGroup } from './fundAccessLimits' +import { LaunchProjectJBTerminal } from './terminals' +import { V4CurrencyOption } from './v4CurrencyOption' +type JB721TierParams = Omit< + ContractFunctionReturnType< + typeof jb721TiersHookStoreAbi, + 'view', + 'tiersOf' + >[0], + 'id' +> + +type JB721InitTiersConfig = { + tiers: JB721TierParams[] + currency: number + decimals: number + prices: Address +} + +type JB721TiersHookFlags = { + noNewTiersWithReserves: boolean + noNewTiersWithVotes: boolean + noNewTiersWithOwnerMinting: boolean + preventOverspending: boolean +} + +/** + * string name; + string symbol; + string baseUri; + IJB721TokenUriResolver tokenUriResolver; + string contractUri; + JB721InitTiersConfig tiersConfig; + address reserveBeneficiary; + JB721TiersHookFlags flags; + */ export type JBDeploy721TiersHookConfig = { - name: string; - symbol: string; - baseUri: string; - tokenUriResolver: string;//IJB721TokenUriResolver; - contractUri: string; - // tiersConfig: //JB721InitTiersConfig; - reserveBeneficiary: Address; - // flags// JB721TiersHookFlags; + name: string + symbol: string + baseUri: string + tokenUriResolver: Address //IJB721TokenUriResolver; + contractUri: string + tiersConfig: JB721InitTiersConfig + reserveBeneficiary: Address + flags: JB721TiersHookFlags } export type JBPayDataHookRulesetConfig = JBRulesetData & { - metadata: JBPayDataHookRulesetMetadata; - memo?: string; - fundAccessLimitGroups: FundAccessLimitGroup[]; - mustStartAtOrAfter?: string; // epoch seconds. anything less than "now" will start immediately. - terminals: string[]; - duration: bigint; - weight: bigint; - decayPercent: bigint; - approvalHook: Address; - splitGroups: LaunchV4ProjectGroupedSplit[]; + metadata: JBPayDataHookRulesetMetadata + memo?: string + fundAccessLimitGroups: FundAccessLimitGroup[] + mustStartAtOrAfter?: string // epoch seconds. anything less than "now" will start immediately. + terminals: string[] + duration: bigint + weight: bigint + decayPercent: bigint + approvalHook: Address + splitGroups: LaunchV4ProjectGroupedSplit[] +} + +interface DeployTiered721DelegateData { + collectionUri: string + collectionName: string + collectionSymbol: string + currency: V4CurrencyOption + tiers: JB721TierParams[] + flags: JB721TiersHookFlags } export interface LaunchProjectWithNftsTxArgs { - tiered721DelegateData: JBDeploy721TiersHookConfig; - projectData: LaunchV2V3ProjectData; + tiered721DelegateData: DeployTiered721DelegateData + projectData: LaunchV2V3ProjectData } export type JB721DelegateLaunchProjectData = { - rulesetConfigurations: JBPayDataHookRulesetConfig[]; - terminalConfigurations: LaunchProjectJBTerminal[]; - projectMetadataUri: string; - memo?: string; + rulesetConfigurations: JBPayDataHookRulesetConfig[] + terminalConfigurations: LaunchProjectJBTerminal[] + projectMetadataUri: string + memo?: string } export type JBPayDataHookRulesetMetadata = Omit< diff --git a/src/packages/v4/utils/launchProjectTransformers.ts b/src/packages/v4/utils/launchProjectTransformers.ts index e5ae49b08a..f8a41d2b55 100644 --- a/src/packages/v4/utils/launchProjectTransformers.ts +++ b/src/packages/v4/utils/launchProjectTransformers.ts @@ -1,11 +1,15 @@ -import { JBSplit, NATIVE_TOKEN, NATIVE_TOKEN_DECIMALS, SplitGroup } from 'juice-sdk-core' +import { + JBSplit, + NATIVE_TOKEN, + NATIVE_TOKEN_DECIMALS, + SplitGroup, +} from 'juice-sdk-core' +import round from 'lodash/round' +import { V2FundingCycleMetadata } from 'packages/v2/models/fundingCycle' import { V2V3FundAccessConstraint, V2V3FundingCycleData, } from 'packages/v2v3/models/fundingCycle' - -import round from 'lodash/round' -import { V2FundingCycleMetadata } from 'packages/v2/models/fundingCycle' import { GroupedSplits as V2V3GroupedSplits } from 'packages/v2v3/models/splits' import { V3FundingCycleMetadata } from 'packages/v3/models/fundingCycle' import { Address } from 'viem' @@ -50,23 +54,33 @@ export function transformV2V3CreateArgsToV4({ const now = round(new Date().getTime() / 1000) const ruleset = { - mustStartAtOrAfter: mustStartAtOrAfterNum > now ? mustStartAtOrAfterNum : now, + mustStartAtOrAfter: + mustStartAtOrAfterNum > now ? mustStartAtOrAfterNum : now, duration: _fundingCycleData.duration.toNumber(), weight: _fundingCycleData.weight.toBigInt(), decayPercent: _fundingCycleData.discountRate.toNumber(), approvalHook: _fundingCycleData.ballot as Address, - metadata: transformFCMetadataToRulesetMetadata({ fundingCycleMetadata: _fundingCycleMetadata}), + metadata: transformFCMetadataToRulesetMetadata({ + fundingCycleMetadata: _fundingCycleMetadata, + }), splitGroups: transformV2V3SplitsToV4({ v2v3Splits: _groupedSplits }), - fundAccessLimitGroups: transformV2V3FundAccessConstraintsToV4({ v2V3FundAccessConstraints: _fundAccessConstraints, primaryNativeTerminal, currencyTokenAddress }) + fundAccessLimitGroups: transformV2V3FundAccessConstraintsToV4({ + v2V3FundAccessConstraints: _fundAccessConstraints, + primaryNativeTerminal, + currencyTokenAddress, + }), } const rulesetConfigurations = [ruleset] - const terminalConfigurations = generateV4LaunchTerminalConfigurationsArg({ terminals: _terminals, currencyTokenAddress }) + const terminalConfigurations = generateV4LaunchTerminalConfigurationsArg({ + terminals: _terminals, + currencyTokenAddress, + }) return [ _owner as Address, @@ -77,9 +91,8 @@ export function transformV2V3CreateArgsToV4({ ] as const } - export function transformFCMetadataToRulesetMetadata({ - fundingCycleMetadata + fundingCycleMetadata, }: { fundingCycleMetadata: V2FundingCycleMetadata | V3FundingCycleMetadata }) { @@ -99,7 +112,8 @@ export function transformFCMetadataToRulesetMetadata({ allowAddPriceFeed: false, // Not present in v2v3, passing false by default ownerMustSendPayouts: false, // Not present in v2v3, passing false by default holdFees: fundingCycleMetadata.holdFees, - useTotalSurplusForRedemptions: fundingCycleMetadata.useTotalOverflowForRedemptions, + useTotalSurplusForRedemptions: + fundingCycleMetadata.useTotalOverflowForRedemptions, useDataHookForPay: fundingCycleMetadata.useDataSourceForPay, useDataHookForRedeem: fundingCycleMetadata.useDataSourceForRedeem, dataHook: fundingCycleMetadata.dataSource as Address, @@ -109,18 +123,19 @@ export function transformFCMetadataToRulesetMetadata({ } type LaunchProjectJBSplit = Omit & { percent: number } -export type LaunchV4ProjectGroupedSplit = Omit, 'splits'> & { splits: LaunchProjectJBSplit[] } +export type LaunchV4ProjectGroupedSplit = Omit< + V4GroupedSplits, + 'splits' +> & { splits: LaunchProjectJBSplit[] } export function transformV2V3SplitsToV4({ - v2v3Splits + v2v3Splits, }: { v2v3Splits: V2V3GroupedSplits[] }): LaunchV4ProjectGroupedSplit[] { return v2v3Splits.map(group => ({ groupId: - group.group === SplitGroup.ETHPayout - ? Number(BigInt(NATIVE_TOKEN)) - : 1, // TODO dont hardcode reserved token group as 1n + group.group === SplitGroup.ETHPayout ? Number(BigInt(NATIVE_TOKEN)) : 1, // TODO dont hardcode reserved token group as 1n splits: group.splits.map(split => ({ preferAddToBalance: Boolean(split.preferClaimed), percent: split.percent, @@ -135,10 +150,10 @@ export function transformV2V3SplitsToV4({ export function transformV2V3FundAccessConstraintsToV4({ v2V3FundAccessConstraints, primaryNativeTerminal, - currencyTokenAddress + currencyTokenAddress, }: { v2V3FundAccessConstraints: V2V3FundAccessConstraint[] - primaryNativeTerminal: Address, + primaryNativeTerminal: Address currencyTokenAddress: Address }): FundAccessLimitGroup[] { return v2V3FundAccessConstraints.map(constraint => ({ @@ -159,7 +174,7 @@ export function transformV2V3FundAccessConstraintsToV4({ })) } -export function generateV4LaunchTerminalConfigurationsArg({ +function generateV4LaunchTerminalConfigurationsArg({ terminals, currencyTokenAddress, }: { diff --git a/yarn.lock b/yarn.lock index 9aae589e59..44e92fbd19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12837,18 +12837,18 @@ jsx-ast-utils@^3.3.5: object.assign "^4.1.4" object.values "^1.1.6" -juice-sdk-core@^11.0.0-alpha: - version "11.0.0-alpha" - resolved "https://registry.yarnpkg.com/juice-sdk-core/-/juice-sdk-core-11.0.0-alpha.tgz#dd0228158de3c3a2799ea6ba3b1f22c8c0635d60" - integrity sha512-wqKAb9f88579CiTZP6MNb08TOStQ4OqgLQYh7cwmONcpon09+A9tyHPCxhgKjGHGevzF80Wv2Z0dPjgujMRI2Q== +juice-sdk-core@^11.5.0-alpha: + version "11.5.0-alpha" + resolved "https://registry.yarnpkg.com/juice-sdk-core/-/juice-sdk-core-11.5.0-alpha.tgz#40ae9e4a44a86dc74777842bb18b30ae36373344" + integrity sha512-0T/v1OHsG99tO/Zo5pY1tbJzKjh0KB4bEMLAnJSRNOeb6hAKiS77xgcth22uYRRpZc4OLZWFjszR+FC2dAKQUg== dependencies: bs58 "^5.0.0" fpnum "^1.0.0" -juice-sdk-react@^11.0.0-alpha: - version "11.0.0-alpha" - resolved "https://registry.yarnpkg.com/juice-sdk-react/-/juice-sdk-react-11.0.0-alpha.tgz#15b93be75d80e9e83a0f45b4cc8c81d3f1828e49" - integrity sha512-G0zCPMCozfAK0Y9mxGMtMOYW8Bm3ml5bEEr/pKFbrrxIs+sEUnK4f5CZWiaA6fZlN8QnUbXbIRteFjv+RB2Yzg== +juice-sdk-react@^11.6.0-alpha: + version "11.6.0-alpha" + resolved "https://registry.yarnpkg.com/juice-sdk-react/-/juice-sdk-react-11.6.0-alpha.tgz#5ddc6a42f2586ed3b694bde5b8ad76b9ad788b9c" + integrity sha512-9ji5COnA0aaqXQOnqbqpohaOXPE7tTNz9J9FlA2d4jG9wlXjYDJrpCAB42g1p1Ojvel6zsLLzuY7aT25UrYFjg== juice@^10.0.0: version "10.0.0" From 4b0d9af5174f082a3b51bbb970314246c911962c Mon Sep 17 00:00:00 2001 From: aeolian <94939382+aeolianeth@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:50:20 +1000 Subject: [PATCH 3/6] wip more --- src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts | 8 ++++---- src/packages/v4/models/nfts.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts index 0bf128306a..455dd5553f 100644 --- a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts +++ b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts @@ -53,7 +53,7 @@ export const SUPPORTED_JB_721_TIER_STORE = { } /** - * + * * TODO still wip */ export function useLaunchProjectWithNftsTx() { @@ -132,12 +132,12 @@ export function useLaunchProjectWithNftsTx() { } const _owner = owner?.length ? owner : userAddress - const deployTiered721DelegateData: JBDeploy721TiersHookConfig = { + const deployTiered721HookData: JBDeploy721TiersHookConfig = { name: collectionName, symbol: collectionSymbol, baseUri: '', // ? tokenUriResolver: zeroAddress, // ? - contractUri: '', //collectionUri ; ? + contractUri: collectionUri, // ? tiersConfig: tiers, reserveBeneficiary: zeroAddress, //?; flags, @@ -167,7 +167,7 @@ export function useLaunchProjectWithNftsTx() { const args = [ owner, - deployTiered721DelegateData, //_deployTiered721DelegateData + deployTiered721HookData, //_deployTiered721HookData launchProjectData, // _launchProjectData, defaultJBController, createSalt(), diff --git a/src/packages/v4/models/nfts.ts b/src/packages/v4/models/nfts.ts index 8849b97e3d..cc0d30c1ce 100644 --- a/src/packages/v4/models/nfts.ts +++ b/src/packages/v4/models/nfts.ts @@ -21,7 +21,7 @@ type JB721InitTiersConfig = { tiers: JB721TierParams[] currency: number decimals: number - prices: Address + prices: Address // JBPrices address } type JB721TiersHookFlags = { From 9d019e4266c694408deabefef1fea31ec429263b Mon Sep 17 00:00:00 2001 From: aeolian <94939382+aeolianeth@users.noreply.github.com> Date: Tue, 19 Nov 2024 22:51:06 +1000 Subject: [PATCH 4/6] compiles --- .../hooks/NFT/useDeployNftProject.ts | 119 +++++++++++++----- .../hooks/DeployProject/useDeployProject.ts | 1 - .../v4/hooks/useLaunchProjectWithNftsTx.ts | 35 ++++-- src/packages/v4/models/nfts.ts | 20 +-- .../v4/utils/launchProjectTransformers.ts | 7 +- .../slices/editingV2Project/defaultState.ts | 9 ++ src/utils/ipfs.ts | 4 +- 7 files changed, 137 insertions(+), 58 deletions(-) diff --git a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts index bebbdb3a5f..da471c5c53 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts @@ -1,7 +1,10 @@ -import { TransactionCallbacks } from 'models/transaction' -import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' +import { ONE_BILLION } from 'constants/numbers' +import { DEFAULT_JB_721_TIER_CATEGORY } from 'constants/transactionDefaults' +import { JBTiered721Flags, NftRewardTier } from 'models/nftRewards' +import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx' import { useLaunchProjectWithNftsTx } from 'packages/v4/hooks/useLaunchProjectWithNftsTx' -import { useCallback, useMemo } from 'react' +import { JB721TierConfig, JB721TiersHookFlags } from 'packages/v4/models/nfts' +import { useCallback } from 'react' import { useAppSelector, useEditingV2V3FundAccessConstraintsSelector, @@ -9,8 +12,72 @@ import { useEditingV2V3FundingCycleMetadataSelector, } from 'redux/hooks/useAppSelector' import { DEFAULT_NFT_FLAGS } from 'redux/slices/editingV2Project' +import { encodeIpfsUri } from 'utils/ipfs' import { NFT_FUNDING_CYCLE_METADATA_OVERRIDES } from 'utils/nftFundingCycleMetadataOverrides' -import { buildJB721TierParams } from 'utils/nftRewards' +import { sortNftsByContributionFloor } from 'utils/nftRewards' +import { Address, parseEther, zeroAddress } from 'viem' +export const DEFAULT_NFT_MAX_SUPPLY = ONE_BILLION - 1 + +function nftRewardTierToJB721TierConfig( + rewardTier: NftRewardTier, + cid: string, +): JB721TierConfig { + const price = parseEther(rewardTier.contributionFloor.toString()) + const initialSupply = rewardTier.maxSupply ?? DEFAULT_NFT_MAX_SUPPLY + const encodedIPFSUri = encodeIpfsUri(cid) + + const reserveFrequency = rewardTier.reservedRate + ? rewardTier.reservedRate - 1 + : 0 + const reserveBeneficiary = + (rewardTier.beneficiary as Address | undefined) ?? zeroAddress + const votingUnits = parseInt(rewardTier.votingWeight ?? '0') + // should default to 0, with useVotingUnits `true`, to save gas + + return { + price, + initialSupply, + votingUnits, + reserveFrequency, + reserveBeneficiary, + encodedIPFSUri, + allowOwnerMint: false, + useReserveBeneficiaryAsDefault: false, + transfersPausable: false, + useVotingUnits: true, + cannotBeRemoved: false, + cannotIncreaseDiscountPercent: false, + discountPercent: 0, + remainingSupply: initialSupply, + category: DEFAULT_JB_721_TIER_CATEGORY, + resolvedUri: '', + } +} + +function buildJB721TierParams({ + cids, // MUST BE SORTED BY CONTRIBUTION FLOOR (TODO: not ideal) + rewardTiers, +}: { + cids: string[] + rewardTiers: NftRewardTier[] +}): JB721TierConfig[] { + const sortedRewardTiers = sortNftsByContributionFloor(rewardTiers) + + return cids.map((cid, index) => { + const rewardTier = sortedRewardTiers[index] + + return nftRewardTierToJB721TierConfig(rewardTier, cid) + }) +} + +function toV4Flags(v2v3Flags: JBTiered721Flags): JB721TiersHookFlags { + return { + noNewTiersWithOwnerMinting: v2v3Flags.lockManualMintingChanges, + noNewTiersWithReserves: v2v3Flags.lockReservedTokenChanges, + noNewTiersWithVotes: v2v3Flags.lockVotingUnitChanges, + preventOverspending: v2v3Flags.preventOverspending, + } +} /** * Hook that returns a function that deploys a project with NFT rewards. @@ -33,22 +100,12 @@ export const useDeployNftProject = () => { const fundingCycleData = useEditingV2V3FundingCycleDataSelector() const fundAccessConstraints = useEditingV2V3FundAccessConstraintsSelector() - const collectionName = useMemo( - () => - nftRewards.collectionMetadata.name - ? nftRewards.collectionMetadata.name - : projectMetadata.name, - [nftRewards.collectionMetadata.name, projectMetadata.name], - ) - const collectionSymbol = useMemo( - () => nftRewards.collectionMetadata.symbol ?? '', - [nftRewards.collectionMetadata.symbol], - ) - const nftFlags = useMemo( - () => nftRewards.flags ?? DEFAULT_NFT_FLAGS, - [nftRewards.flags], - ) - const governanceType = nftRewards.governanceType + const collectionName = nftRewards.collectionMetadata.name + ? nftRewards.collectionMetadata.name + : projectMetadata.name + const collectionSymbol = nftRewards.collectionMetadata.symbol ?? '' + const nftFlags = nftRewards.flags ?? DEFAULT_NFT_FLAGS + // const governanceType = nftRewards.governanceType const currency = nftRewards.pricing.currency /** @@ -63,15 +120,14 @@ export const useDeployNftProject = () => { rewardTierCids, nftCollectionMetadataUri, - onDone, - onConfirmed, - onCancelled, - onError, + onTransactionPending, + onTransactionConfirmed, + onTransactionError, }: { metadataCid: string rewardTierCids: string[] nftCollectionMetadataUri: string - } & TransactionCallbacks) => { + } & LaunchTxOpts) => { if (!collectionName) throw new Error('No collection name or project name') if (!(rewardTierCids.length && nftRewards.rewardTiers)) throw new Error('No NFTs') @@ -80,8 +136,8 @@ export const useDeployNftProject = () => { const tiers = buildJB721TierParams({ cids: rewardTierCids, rewardTiers: nftRewards.rewardTiers, - version: DEFAULT_JB_721_DELEGATE_VERSION, }) + const flags = toV4Flags(nftFlags) return await launchProjectWithNftsTx( { @@ -90,9 +146,8 @@ export const useDeployNftProject = () => { collectionName, collectionSymbol, currency, - governanceType, tiers, - flags: nftFlags, + flags, }, projectData: { owner: inputProjectOwner?.length ? inputProjectOwner : undefined, @@ -108,10 +163,9 @@ export const useDeployNftProject = () => { }, }, { - onDone, - onConfirmed, - onCancelled, - onError, + onTransactionPending, + onTransactionConfirmed, + onTransactionError, }, ) }, @@ -123,7 +177,6 @@ export const useDeployNftProject = () => { reservedTokensGroupedSplits, launchProjectWithNftsTx, collectionSymbol, - governanceType, inputProjectOwner, fundingCycleData, mustStartAtOrAfter, diff --git a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts index b97afca36b..843ea6aeb2 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts @@ -29,7 +29,6 @@ export const useDeployProject = () => { const isNftProject = useIsNftProject() const uploadNftRewards = useUploadNftRewards() const deployNftProject = useDeployNftProject() - const deployStandardProject = useDeployStandardProject() const { diff --git a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts index 455dd5553f..ab20f33604 100644 --- a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts +++ b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts @@ -15,6 +15,7 @@ import { } from 'packages/v4/models/nfts' import { useContext } from 'react' import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' +import { ipfsUri } from 'utils/ipfs' import { Address, toBytes, @@ -34,6 +35,7 @@ import { SUPPORTED_JB_CONTROLLER_ADDRESS, SUPPORTED_JB_MULTITERMINAL_ADDRESS, } from './useLaunchProjectTx' + function createSalt() { const base: string = '0x' + Math.random().toString(16).slice(2) // idk lol const salt = toHex(toBytes(base, { size: 32 })) @@ -65,7 +67,6 @@ export function useLaunchProjectWithNftsTx() { const defaultJBController = chainId ? SUPPORTED_JB_CONTROLLER_ADDRESS[chainId] : undefined - const defaultJBETHPaymentTerminal = chainId ? SUPPORTED_JB_MULTITERMINAL_ADDRESS[chainId] : undefined @@ -74,7 +75,7 @@ export function useLaunchProjectWithNftsTx() { : undefined const { writeContractAsync: writeLaunchProject } = - useWriteJb721TiersHookProjectDeployerLaunchProjectFor() + useWriteJb721TiersHookProjectDeployerLaunchProjectFor() return async ( { @@ -130,16 +131,21 @@ export function useLaunchProjectWithNftsTx() { return Promise.resolve(false) } - const _owner = owner?.length ? owner : userAddress + const _owner = (owner?.length ? owner : userAddress) as Address const deployTiered721HookData: JBDeploy721TiersHookConfig = { name: collectionName, symbol: collectionSymbol, - baseUri: '', // ? - tokenUriResolver: zeroAddress, // ? - contractUri: collectionUri, // ? - tiersConfig: tiers, - reserveBeneficiary: zeroAddress, //?; + baseUri: ipfsUri(''), // ? + tokenUriResolver: zeroAddress, + contractUri: ipfsUri(collectionUri), + tiersConfig: { + currency, + decimals: 18, + prices: zeroAddress, + tiers, + }, + reserveBeneficiary: zeroAddress, flags, } @@ -166,12 +172,17 @@ export function useLaunchProjectWithNftsTx() { }) const args = [ - owner, + _owner, deployTiered721HookData, //_deployTiered721HookData - launchProjectData, // _launchProjectData, + { + projectUri: launchProjectData[1], + rulesetConfigurations: launchProjectData[2], + terminalConfigurations: launchProjectData[3], + memo: launchProjectData[4], + }, // _launchProjectData, defaultJBController, - createSalt(), - ] + // createSalt(), + ] as const try { // SIMULATE TX: TODO update for nfts diff --git a/src/packages/v4/models/nfts.ts b/src/packages/v4/models/nfts.ts index cc0d30c1ce..f0fcdd7ec1 100644 --- a/src/packages/v4/models/nfts.ts +++ b/src/packages/v4/models/nfts.ts @@ -1,5 +1,4 @@ import { JBRulesetData, JBRulesetMetadata } from 'juice-sdk-core' - import { jb721TiersHookStoreAbi } from 'juice-sdk-react' import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' import { Address, ContractFunctionReturnType } from 'viem' @@ -8,23 +7,30 @@ import { FundAccessLimitGroup } from './fundAccessLimits' import { LaunchProjectJBTerminal } from './terminals' import { V4CurrencyOption } from './v4CurrencyOption' -type JB721TierParams = Omit< +/** + * @see https://github.com/Bananapus/nana-721-hook/blob/main/src/structs/JB721TierConfig.sol + */ +export type JB721TierConfig = Omit< ContractFunctionReturnType< typeof jb721TiersHookStoreAbi, 'view', 'tiersOf' >[0], - 'id' -> + 'id' | 'votingUnits' +> & { + useReserveBeneficiaryAsDefault: boolean + useVotingUnits: boolean + votingUnits: number +} type JB721InitTiersConfig = { - tiers: JB721TierParams[] + tiers: JB721TierConfig[] currency: number decimals: number prices: Address // JBPrices address } -type JB721TiersHookFlags = { +export type JB721TiersHookFlags = { noNewTiersWithReserves: boolean noNewTiersWithVotes: boolean noNewTiersWithOwnerMinting: boolean @@ -70,7 +76,7 @@ interface DeployTiered721DelegateData { collectionName: string collectionSymbol: string currency: V4CurrencyOption - tiers: JB721TierParams[] + tiers: JB721TierConfig[] flags: JB721TiersHookFlags } diff --git a/src/packages/v4/utils/launchProjectTransformers.ts b/src/packages/v4/utils/launchProjectTransformers.ts index f8a41d2b55..d5cfb39edb 100644 --- a/src/packages/v4/utils/launchProjectTransformers.ts +++ b/src/packages/v4/utils/launchProjectTransformers.ts @@ -123,10 +123,11 @@ export function transformFCMetadataToRulesetMetadata({ } type LaunchProjectJBSplit = Omit & { percent: number } + export type LaunchV4ProjectGroupedSplit = Omit< V4GroupedSplits, - 'splits' -> & { splits: LaunchProjectJBSplit[] } + 'splits' | 'groupId' +> & { splits: LaunchProjectJBSplit[], groupId: bigint } export function transformV2V3SplitsToV4({ v2v3Splits, @@ -135,7 +136,7 @@ export function transformV2V3SplitsToV4({ }): LaunchV4ProjectGroupedSplit[] { return v2v3Splits.map(group => ({ groupId: - group.group === SplitGroup.ETHPayout ? Number(BigInt(NATIVE_TOKEN)) : 1, // TODO dont hardcode reserved token group as 1n + group.group === SplitGroup.ETHPayout ? BigInt(NATIVE_TOKEN) : 1n, // TODO dont hardcode reserved token group as 1n splits: group.splits.map(split => ({ preferAddToBalance: Boolean(split.preferClaimed), percent: split.percent, diff --git a/src/redux/slices/editingV2Project/defaultState.ts b/src/redux/slices/editingV2Project/defaultState.ts index 3467dcf5ac..dacd6c6e16 100644 --- a/src/redux/slices/editingV2Project/defaultState.ts +++ b/src/redux/slices/editingV2Project/defaultState.ts @@ -22,6 +22,7 @@ import { serializeV2V3FundingCycleData, serializeV2V3FundingCycleMetadata, } from 'packages/v2v3/utils/serializers' +import { JB721TiersHookFlags } from 'packages/v4/models/nfts' import { projectDescriptionTemplate } from 'templates/create/projectDescriptionTemplate' import { CreateState, ProjectState } from './types' @@ -92,6 +93,14 @@ export const DEFAULT_NFT_FLAGS: JBTiered721Flags = { preventOverspending: false, } + +export const DEFAULT_NFT_FLAGS_V4: JB721TiersHookFlags = { + noNewTiersWithReserves: false, + noNewTiersWithVotes: false, + noNewTiersWithOwnerMinting: false, + preventOverspending: false, +} + const DEFAULT_PROJECT_METADATA_STATE: ProjectMetadata = { name: '', infoUri: '', diff --git a/src/utils/ipfs.ts b/src/utils/ipfs.ts index c8ea023760..f13a560715 100644 --- a/src/utils/ipfs.ts +++ b/src/utils/ipfs.ts @@ -62,8 +62,8 @@ export function pinataToGatewayUrl(url: string) { * * Hex-encoded CIDs are used to store some CIDs on-chain because they are more gas-efficient. */ -export function encodeIpfsUri(cid: string) { - return '0x' + Buffer.from(base58.decode(cid).slice(2)).toString('hex') +export function encodeIpfsUri(cid: string): `0x${string}` { + return `0x${Buffer.from(base58.decode(cid).slice(2)).toString('hex')}` } /** From a7955e40e434814bb232b317d8438e71d6d06979 Mon Sep 17 00:00:00 2001 From: aeolian <94939382+aeolianeth@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:07:54 +1000 Subject: [PATCH 5/6] done --- .../pages/ReviewDeploy/ReviewDeployPage.tsx | 5 ++-- .../v4/hooks/useLaunchProjectWithNftsTx.ts | 23 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx b/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx index 17d5d5554e..7302f3591b 100644 --- a/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx +++ b/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx @@ -24,6 +24,7 @@ import { WizardContext } from '../../Wizard/contexts/WizardContext' import { FundingConfigurationReview } from './components/FundingConfigurationReview/FundingConfigurationReview' import { ProjectDetailsReview } from './components/ProjectDetailsReview/ProjectDetailsReview' import { ProjectTokenReview } from './components/ProjectTokenReview/ProjectTokenReview' +import { RewardsReview } from './components/RewardsReview/RewardsReview' import { RulesReview } from './components/RulesReview/RulesReview' enum ReviewDeployKey { @@ -164,7 +165,7 @@ export const ReviewDeployPage = () => { > - {/* { } > - */} + { + const projectIdHex: string | undefined = + txReceipt?.logs[0]?.topics?.[1] + if (!projectIdHex) return 0 + + const projectId = parseInt(projectIdHex, 16) + return projectId +} + /** * The contract addresses to use for deployment * @todo not ideal to hardcode these addresses @@ -63,7 +78,7 @@ export function useLaunchProjectWithNftsTx() { const { addTransaction } = useContext(TxHistoryContext) const { userAddress } = useWallet() - const chainId = useCurrentRouteChainId() + const chainId = useCurrentRouteChainId() ?? sepolia.id // default to sepolia const defaultJBController = chainId ? SUPPORTED_JB_CONTROLLER_ADDRESS[chainId] : undefined @@ -206,7 +221,7 @@ export function useLaunchProjectWithNftsTx() { hash, }) - const newProjectId = getProjectIdFromLaunchReceipt(transactionReceipt) + const newProjectId = getProjectIdFromNftLaunchReceipt(transactionReceipt) onTransactionConfirmedCallback(hash, newProjectId) } catch (e) { From 48c3ab6cb60ea5472d81be2dcdfc879042d3e1f0 Mon Sep 17 00:00:00 2001 From: aeolian <94939382+aeolianeth@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:13:02 +1000 Subject: [PATCH 6/6] ya --- src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts index 15abe54901..ae2f584691 100644 --- a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts +++ b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts @@ -2,8 +2,9 @@ import { waitForTransactionReceipt } from '@wagmi/core' import { JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN } from 'constants/metadataDomain' import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext' import { useWallet } from 'hooks/Wallet' -import { DEFAULT_MEMO, NATIVE_TOKEN } from 'juice-sdk-core' +import { DEFAULT_MEMO, NATIVE_TOKEN, NATIVE_TOKEN_DECIMALS } from 'juice-sdk-core' import { + jbPricesAddress, useJBContractContext, useReadJb721TiersHookStoreTiersOf, useWriteJb721TiersHookProjectDeployerLaunchProjectFor, @@ -23,6 +24,7 @@ import { WaitForTransactionReceiptReturnType, zeroAddress, } from 'viem' +import { sepolia } from 'viem/chains' import { LaunchV2V3ProjectArgs, transformV2V3CreateArgsToV4, @@ -34,7 +36,6 @@ import { SUPPORTED_JB_CONTROLLER_ADDRESS, SUPPORTED_JB_MULTITERMINAL_ADDRESS } from './useLaunchProjectTx' -import { sepolia } from 'viem/chains' function createSalt() { const base: string = '0x' + Math.random().toString(16).slice(2) // idk lol @@ -151,13 +152,13 @@ export function useLaunchProjectWithNftsTx() { const deployTiered721HookData: JBDeploy721TiersHookConfig = { name: collectionName, symbol: collectionSymbol, - baseUri: ipfsUri(''), // ? + baseUri: ipfsUri(''), tokenUriResolver: zeroAddress, contractUri: ipfsUri(collectionUri), tiersConfig: { currency, - decimals: 18, - prices: zeroAddress, + decimals: NATIVE_TOKEN_DECIMALS, + prices: jbPricesAddress[chainId], tiers, }, reserveBeneficiary: zeroAddress,