diff --git a/src/components/Project/ProjectTabs/EmptyScreen.tsx b/src/components/Project/ProjectTabs/EmptyScreen.tsx
index 66eeb8c31b..8424bc58dd 100644
--- a/src/components/Project/ProjectTabs/EmptyScreen.tsx
+++ b/src/components/Project/ProjectTabs/EmptyScreen.tsx
@@ -33,7 +33,7 @@ export const EmptyScreen = ({
{title}
- {subtitle && (
+ {subtitle || (
This project has no description.
diff --git a/src/models/JB721Delegate.ts b/src/models/JB721Delegate.ts
new file mode 100644
index 0000000000..4174ab8ff9
--- /dev/null
+++ b/src/models/JB721Delegate.ts
@@ -0,0 +1,8 @@
+export enum JB721DelegateVersion {
+ JB721DELEGATE_V3 = '3',
+ JB721DELEGATE_V3_1 = '3-1',
+ JB721DELEGATE_V3_2 = '3-2',
+ JB721DELEGATE_V3_3 = '3-3',
+ JB721DELEGATE_V3_4 = '3-4',
+ JB721DELEGATE_V4 = '4',
+}
diff --git a/src/models/nftRewards.ts b/src/models/nftRewards.ts
index 912a815be3..87fc83b87b 100644
--- a/src/models/nftRewards.ts
+++ b/src/models/nftRewards.ts
@@ -60,6 +60,24 @@ export type JB_721_TIER_PARAMS_V3_2 = Omit<
useVotingUnits: boolean
}
+// v4TODO: add to SDK
+export type JB_721_TIER_PARAMS_V4 = {
+ price: bigint
+ initialSupply: number
+ votingUnits: number
+ reserveFrequency: number
+ reserveBeneficiary: `0x${string}`
+ encodedIPFSUri: `0x${string}`
+ category: number
+ discountPercent: number
+ allowOwnerMint: boolean
+ useReserveBeneficiaryAsDefault: boolean
+ transfersPausable: boolean
+ useVotingUnits: boolean
+ cannotBeRemoved: boolean
+ cannotIncreaseDiscountPercent: boolean
+}
+
// Tiers as they are stored on-chain.
export type JB721TierV3 = JB721TierParams & {
id: BigNumber
@@ -126,7 +144,7 @@ export enum JB721GovernanceType {
}
export interface JB721PricingParams {
- tiers: (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2)[]
+ tiers: (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2 | JB_721_TIER_PARAMS_V4)[]
currency: CurrencyOption
decimals: number
prices: string // address
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useUpdateCurrentCollection.ts b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useUpdateCurrentCollection.ts
index 28a9513496..177e3abd8f 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useUpdateCurrentCollection.ts
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useUpdateCurrentCollection.ts
@@ -1,11 +1,12 @@
-import { t } from '@lingui/macro'
-import { NEW_NFT_ID_LOWER_LIMIT } from 'components/NftRewards/RewardsList/AddEditRewardModal'
-import { NftRewardTier } from 'models/nftRewards'
-import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext'
-import { useAdjustTiersTx } from 'packages/v2v3/hooks/JB721Delegate/transactor/useAdjustTiersTx'
-import { useCallback, useContext, useState } from 'react'
+import { JB721TierParams, JB_721_TIER_PARAMS_V3_1, JB_721_TIER_PARAMS_V3_2, NftRewardTier } from 'models/nftRewards'
import { buildJB721TierParams, pinNftRewards } from 'utils/nftRewards'
+import { useCallback, useContext, useState } from 'react'
+
+import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext'
+import { NEW_NFT_ID_LOWER_LIMIT } from 'components/NftRewards/RewardsList/AddEditRewardModal'
import { emitErrorNotification } from 'utils/notifications'
+import { t } from '@lingui/macro'
+import { useAdjustTiersTx } from 'packages/v2v3/hooks/JB721Delegate/transactor/useAdjustTiersTx'
export function useUpdateCurrentCollection({
rewardTiers,
@@ -40,7 +41,7 @@ export function useUpdateCurrentCollection({
cids: rewardTiersCIDs,
rewardTiers: newRewardTiers,
version,
- })
+ }) as (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2)[]
if (!newTiers) {
emitErrorNotification(
diff --git a/src/packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext.ts b/src/packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext.ts
index 5af4ca0bba..60784a8216 100644
--- a/src/packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext.ts
+++ b/src/packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext.ts
@@ -1,5 +1,5 @@
import { Contract } from 'ethers'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { createContext } from 'react'
interface JB721DelegateContracts {
diff --git a/src/packages/v2v3/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts b/src/packages/v2v3/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts
index 4a3b25ce62..19be67c98f 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/JB721DelegateVersion/useJB721DelegateVersion.ts
@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query'
import axios from 'axios'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { isZeroAddress } from 'utils/address'
export function useJB721DelegateVersion({
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts b/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts
index 75aa64f5d6..1fc3766540 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftCollectionMetadataUri.ts
@@ -1,6 +1,6 @@
+import { JB721DelegateVersion } from 'models/JB721Delegate'
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(
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftTiers.ts b/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftTiers.ts
index 8698d08f38..649e608bb3 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftTiers.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contractReader/useNftTiers.ts
@@ -1,8 +1,9 @@
import { JB721TierV3, JB_721_TIER_V3_2 } from 'models/nftRewards'
+
+import { JB721DelegateVersion } from 'models/JB721Delegate'
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(
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts
index b8fe9e5f3d..e067be553d 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi.ts
@@ -1,7 +1,8 @@
+import { useEffect, useState } from 'react'
+
import { ContractInterface } from 'ethers'
import { ContractJson } from 'models/contracts'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
-import { useEffect, useState } from 'react'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
type JB721DelegateContractName =
| 'JB721TieredGovernance'
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts
index d883ebe226..6d3693fe73 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress.ts
@@ -1,9 +1,10 @@
-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'
+import { readNetwork } from 'constants/networks'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
+import { NetworkName } from 'models/networkName'
+
/**
* Some addresses aren't in the forge deployment manifests, so we have to hardcode them here.
*/
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts
index 2542213e49..2b690fc91b 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegate.ts
@@ -1,6 +1,6 @@
import { Contract } from 'ethers'
import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { useJB721DelegateAbi } from './useJB721DelegateAbi'
export function useJB721TieredDelegate({
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts
index 46f2cc9261..0b6b9b260a 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJB721TieredDelegateStore.ts
@@ -1,6 +1,6 @@
import { Contract } from 'ethers'
import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { useJB721DelegateAbi } from './useJB721DelegateAbi'
export function useJB721TieredDelegateStore({
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts
index 00998b448a..a888dfe62f 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contracts/useJBTiered721DelegateProjectDeployer.ts
@@ -1,7 +1,7 @@
import { Contract } from 'ethers'
import { useLoadContractFromAddress } from 'hooks/useLoadContractFromAddress'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { useJB721DelegateAbi } from 'packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
import { useJB721DelegateContractAddress } from './useJB721DelegateContractAddress'
export function useJBTiered721DelegateProjectDeployer({
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts b/src/packages/v2v3/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts
index 28bdf37e79..94e178aee6 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contracts/useProjectJB721DelegateVersion.ts
@@ -1,9 +1,9 @@
-import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext'
import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import {
- JB721DelegateVersion,
V2V3ContractName,
} from 'packages/v2v3/models/contracts'
+import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext'
import { useContext } from 'react'
/**
diff --git a/src/packages/v2v3/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts b/src/packages/v2v3/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts
index dc0392b183..d123a2ece8 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/contracts/useStoreofJB721TieredDelegate.ts
@@ -1,6 +1,6 @@
import { Contract } from 'ethers'
import { useContractReadValue } from 'hooks/ContractReader'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { useJB721TieredDelegateStore } from './useJB721TieredDelegateStore'
export function useStoreOfJB721TieredDelegate({
diff --git a/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts b/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts
index 26d352af87..56d59ea47a 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts
@@ -1,11 +1,4 @@
-import { t } from '@lingui/macro'
-import { JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN } from 'constants/metadataDomain'
-import { DEFAULT_MEMO } from 'constants/transactionDefaults'
-import { TransactionContext } from 'contexts/Transaction/TransactionContext'
-import { getAddress } from 'ethers/lib/utils'
-import { useWallet } from 'hooks/Wallet'
-import { TransactorInstance } from 'hooks/useTransactor'
-import omit from 'lodash/omit'
+import { GroupedSplits, SplitGroup } from 'packages/v2v3/models/splits'
import {
JB721GovernanceType,
JB721TierParams,
@@ -13,28 +6,37 @@ import {
JBTiered721Flags,
JB_721_TIER_PARAMS_V3_1,
JB_721_TIER_PARAMS_V3_2,
+ JB_721_TIER_PARAMS_V4,
JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1,
} from 'models/nftRewards'
-import { V2V3ContractsContext } from 'packages/v2v3/contexts/Contracts/V2V3ContractsContext'
-import { useJBPrices } from 'packages/v2v3/hooks/JBPrices'
-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 { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx'
-import { useV2ProjectTitle } from 'packages/v2v3/hooks/useProjectTitle'
-import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption'
import {
JBPayDataSourceFundingCycleMetadata,
V2V3FundAccessConstraint,
V2V3FundingCycleData,
} from 'packages/v2v3/models/fundingCycle'
-import { GroupedSplits, SplitGroup } from 'packages/v2v3/models/splits'
-import { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle'
-import { useContext } from 'react'
+
+import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate'
+import { DEFAULT_MEMO } from 'constants/transactionDefaults'
import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/shared/v2ProjectDefaultState'
+import { JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN } from 'constants/metadataDomain'
+import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx'
+import { TransactionContext } from 'contexts/Transaction/TransactionContext'
+import { TransactorInstance } from 'hooks/useTransactor'
+import { V2V3ContractsContext } from 'packages/v2v3/contexts/Contracts/V2V3ContractsContext'
+import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption'
import { buildDeployTiered721DelegateData } from 'utils/nftRewards'
+import { getAddress } from 'ethers/lib/utils'
+import { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle'
+import omit from 'lodash/omit'
+import { t } from '@lingui/macro'
+import { useContext } from 'react'
+import { useDefaultJBController } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBController'
+import { useDefaultJBETHPaymentTerminal } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBETHPaymentTerminal'
import { useJB721DelegateContractAddress } from '../contracts/useJB721DelegateContractAddress'
+import { useJBPrices } from 'packages/v2v3/hooks/JBPrices'
import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer'
+import { useV2ProjectTitle } from 'packages/v2v3/hooks/useProjectTitle'
+import { useWallet } from 'hooks/Wallet'
interface DeployTiered721DelegateData {
collectionUri: string
@@ -42,7 +44,7 @@ interface DeployTiered721DelegateData {
collectionSymbol: string
currency: V2V3CurrencyOption
governanceType: JB721GovernanceType
- tiers: (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2)[]
+ tiers: (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2 | JB_721_TIER_PARAMS_V4)[]
flags: JBTiered721Flags
}
diff --git a/src/packages/v2v3/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts b/src/packages/v2v3/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts
index be155aad2c..78a3762644 100644
--- a/src/packages/v2v3/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts
+++ b/src/packages/v2v3/hooks/JB721Delegate/transactor/useReconfigureV2V3FundingCycleWithNftsTx.ts
@@ -1,36 +1,37 @@
+import {
+ JBDeployTiered721DelegateData,
+ JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1,
+} from 'models/nftRewards'
+import {
+ JBPayDataSourceFundingCycleMetadata,
+ V2V3FundAccessConstraint,
+ V2V3FundingCycleData,
+} from 'packages/v2v3/models/fundingCycle'
+import { GroupedSplits, SplitGroup } from 'packages/v2v3/models/splits'
+import {
+ buildDeployTiered721DelegateData,
+ buildJB721TierParams,
+ defaultNftCollectionName,
+} from 'utils/nftRewards'
+
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 { JB721DelegateVersion } from 'models/JB721Delegate'
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/shared/v2ProjectDefaultState'
import { NftRewardsData } from 'redux/slices/shared/v2ProjectTypes'
-import {
- buildDeployTiered721DelegateData,
- buildJB721TierParams,
- defaultNftCollectionName,
-} from 'utils/nftRewards'
import { useJB721DelegateContractAddress } from '../contracts/useJB721DelegateContractAddress'
import { useJBTiered721DelegateProjectDeployer } from '../contracts/useJBTiered721DelegateProjectDeployer'
import { useProjectControllerJB721DelegateVersion } from '../contracts/useProjectJB721DelegateVersion'
diff --git a/src/packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate.ts b/src/packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate.ts
index e64f8661cd..432d0b72bd 100644
--- a/src/packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate.ts
+++ b/src/packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate.ts
@@ -1,4 +1,4 @@
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
/**
* The version of the JB721Delegate contracts (NFT Rewards) that should be used to launch projects, funding cycles, etc.
diff --git a/src/packages/v2v3/hooks/transactor/useReconfigureNftCollectionMetadata.ts b/src/packages/v2v3/hooks/transactor/useReconfigureNftCollectionMetadata.ts
index f4bb514c10..b5c9c4d00f 100644
--- a/src/packages/v2v3/hooks/transactor/useReconfigureNftCollectionMetadata.ts
+++ b/src/packages/v2v3/hooks/transactor/useReconfigureNftCollectionMetadata.ts
@@ -1,14 +1,16 @@
import * as constants from '@ethersproject/constants'
+
+import { cidFromUrl, encodeIpfsUri, ipfsUri } from 'utils/ipfs'
+
import { t } from '@lingui/macro'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import { TransactionContext } from 'contexts/Transaction/TransactionContext'
import { useDefaultTokenUriResolver } from 'hooks/DefaultTokenUriResolver/contracts/useDefaultTokenUriResolver'
import { TransactorInstance } from 'hooks/useTransactor'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { NftCollectionMetadata } from 'models/nftRewards'
import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
import { useContext } from 'react'
-import { cidFromUrl, encodeIpfsUri, ipfsUri } from 'utils/ipfs'
import { pinNftCollectionMetadata } from 'utils/nftRewards'
import { useV2ProjectTitle } from '../useProjectTitle'
diff --git a/src/packages/v2v3/models/contracts.ts b/src/packages/v2v3/models/contracts.ts
index 1412219a24..48de68ee25 100644
--- a/src/packages/v2v3/models/contracts.ts
+++ b/src/packages/v2v3/models/contracts.ts
@@ -74,11 +74,3 @@ export const SUPPORTED_CONTROLLERS = [
V2V3ContractName.JBController3_1,
] as const
export type ControllerVersion = (typeof SUPPORTED_CONTROLLERS)[number]
-
-export enum JB721DelegateVersion {
- JB721DELEGATE_V3 = '3',
- JB721DELEGATE_V3_1 = '3-1',
- JB721DELEGATE_V3_2 = '3-2',
- JB721DELEGATE_V3_3 = '3-3',
- JB721DELEGATE_V3_4 = '3-4',
-}
diff --git a/src/packages/v4/contexts/V4SettingsProvider.tsx b/src/packages/v4/contexts/V4SettingsProvider.tsx
index 7584fdfe32..ff076eedd5 100644
--- a/src/packages/v4/contexts/V4SettingsProvider.tsx
+++ b/src/packages/v4/contexts/V4SettingsProvider.tsx
@@ -9,6 +9,7 @@ import { WagmiProvider } from 'wagmi'
import { chainNameMap } from '../utils/networks'
import { EditCycleFormProvider } from '../views/V4ProjectSettings/EditCyclePage/EditCycleFormContext'
import { wagmiConfig } from '../wagmiConfig'
+import { V4NftRewardsProvider } from './V4NftRewards/V4NftRewardsProvider'
import V4ProjectMetadataProvider from './V4ProjectMetadataProvider'
export const V4SettingsProvider: React.FC = ({
@@ -33,15 +34,17 @@ export const V4SettingsProvider: React.FC = ({
metadata: { ipfsGatewayHostname: OPEN_IPFS_GATEWAY_HOSTNAME },
}}
>
-
-
-
-
- {children}
-
-
-
-
+
+
+
+
+
+ {children}
+
+
+
+
+
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..a53b48f518
--- /dev/null
+++ b/src/packages/v4/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure.ts
@@ -0,0 +1,16 @@
+import { V4OperatorPermission } from 'packages/v4/models/v4Permissions'
+import { useV4WalletHasPermission } from '../../useV4WalletHasPermission'
+
+/**
+ * Checks whether the given [projectOwnerAddress] has given the JBTiered721DelegateProjectDeployer
+ * permission to queue rulesets for the given [projectId].
+ *
+ * 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() {
+ const JBTiered721DelegateProjectDeployerCanReconfigure = useV4WalletHasPermission(V4OperatorPermission.QUEUE_RULESETS)
+
+ return JBTiered721DelegateProjectDeployerCanReconfigure
+}
diff --git a/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts
index 04608418c7..9fd869a9a0 100644
--- a/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts
+++ b/src/packages/v4/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts
@@ -1,7 +1,3 @@
-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,
@@ -10,20 +6,13 @@ import {
import {
jbPricesAddress,
useJBContractContext,
- useReadJb721TiersHookStoreTiersOf,
- useWriteJb721TiersHookProjectDeployerLaunchProjectFor,
+ useWriteJb721TiersHookProjectDeployerLaunchProjectFor
} from 'juice-sdk-react'
-import { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle'
import {
JBDeploy721TiersHookConfig,
LaunchProjectWithNftsTxArgs,
} from 'packages/v4/models/nfts'
-import { wagmiConfig } from 'packages/v4/wagmiConfig'
-import { useContext } from 'react'
-import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/shared/v2ProjectDefaultState'
-import { ipfsUri } from 'utils/ipfs'
import { Address, WaitForTransactionReceiptReturnType, zeroAddress } from 'viem'
-import { useChainId } from 'wagmi'
import {
LaunchV2V3ProjectArgs,
transformV2V3CreateArgsToV4,
@@ -34,6 +23,17 @@ import {
SUPPORTED_JB_MULTITERMINAL_ADDRESS,
} from '../../useLaunchProjectTx'
+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 { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle'
+import { wagmiConfig } from 'packages/v4/wagmiConfig'
+import { useContext } from 'react'
+import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/shared/v2ProjectDefaultState'
+import { ipfsUri } from 'utils/ipfs'
+import { useChainId } from 'wagmi'
+
/**
* Return the project ID created from a `launchProjectFor` transaction.
* @param txReceipt receipt of `launchProjectFor` transaction
@@ -201,8 +201,6 @@ export function useLaunchProjectWithNftsTx() {
args,
})
- type x = typeof useReadJb721TiersHookStoreTiersOf
-
onTransactionPendingCallback(hash)
addTransaction?.('Launch Project', { hash })
const transactionReceipt: WaitForTransactionReceiptReturnType =
diff --git a/src/packages/v4/hooks/useHasNftRewards.ts b/src/packages/v4/hooks/useHasNftRewards.ts
new file mode 100644
index 0000000000..7e26319d7f
--- /dev/null
+++ b/src/packages/v4/hooks/useHasNftRewards.ts
@@ -0,0 +1,11 @@
+import { useJBRulesetContext } from "juice-sdk-react"
+import { zeroAddress } from "viem"
+
+export function useHasNftRewards() {
+ const { rulesetMetadata: { data: rulesetMetadata }} =
+ useJBRulesetContext()
+ return (
+ rulesetMetadata?.dataHook &&
+ rulesetMetadata.dataHook !== zeroAddress
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/EditNftsPage.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/EditNftsPage.tsx
new file mode 100644
index 0000000000..f4fac3115b
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/EditNftsPage.tsx
@@ -0,0 +1,30 @@
+import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
+import { useJBRulesetContext } from 'juice-sdk-react'
+import { useNftDeployerCanReconfigure } from 'packages/v2v3/hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure'
+import { useHasNftRewards } from 'packages/v4/hooks/useHasNftRewards'
+import useProjectOwnerOf from 'packages/v4/hooks/useV4ProjectOwnerOf'
+import { useContext } from 'react'
+import { LaunchNftsPage } from './LaunchNftCollection'
+import { EnableNftsCard } from './LaunchNftCollection/EnableNftsCard'
+import { UpdateNftsPage } from './UpdateNftsPage'
+
+export function EditNftsPage() {
+ const { projectId } = useContext(ProjectMetadataContext)
+
+ const { data: projectOwnerAddress } = useProjectOwnerOf()
+ const { rulesetMetadata: { data: _rulesetMetadata } } = useJBRulesetContext()
+ const hasExistingNfts = useHasNftRewards()
+
+ const nftDeployerCanReconfigure = useNftDeployerCanReconfigure({
+ projectId,
+ projectOwnerAddress,
+ })
+
+ if (hasExistingNfts) {
+ return
+ } else if (!nftDeployerCanReconfigure) {
+ return
+ } else {
+ return
+ }
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/EnableNftsCard.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/EnableNftsCard.tsx
new file mode 100644
index 0000000000..9fd6f4d9a4
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/EnableNftsCard.tsx
@@ -0,0 +1,39 @@
+import { Trans } from '@lingui/macro'
+import { Button } from 'antd'
+import Image from "next/legacy/image"
+import { useState } from 'react'
+import { EnableNftsModal } from './EnableNftsModal'
+import noNftsImage from '/public/assets/images/settings/no-nfts.webp'
+
+export function EnableNftsCard() {
+ const [enableNftsModalOpen, setEnableNftsModalOpen] = useState(false)
+ return (
+ <>
+
+
+
+ You haven't launched an NFT collection yet.
+
+ setEnableNftsModalOpen(true)}>
+
+ Enable NFTs
+
+
+
+
+ setEnableNftsModalOpen(false)}
+ />
+ >
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/EnableNftsModal.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/EnableNftsModal.tsx
new file mode 100644
index 0000000000..47d2858843
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/EnableNftsModal.tsx
@@ -0,0 +1,64 @@
+import { Trans, t } from '@lingui/macro'
+
+import ExternalLink from 'components/ExternalLink'
+import TooltipIcon from 'components/TooltipIcon'
+import TransactionModal from 'components/modals/TransactionModal'
+import { reloadWindow } from 'utils/windowUtils'
+import { useSetNftOperatorPermissionsTx } from 'packages/v2v3/hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx'
+import { useState } from 'react'
+
+// V4TODO: this whole component needs to be v4-ified
+export function EnableNftsModal({
+ open,
+ onClose,
+}: {
+ open: boolean
+ onClose: VoidFunction
+}) {
+ const [loading, setLoading] = useState(false)
+
+ const setNftOperatorPermissionsTx = useSetNftOperatorPermissionsTx()
+
+ const setPermissions = async () => {
+ setLoading(true)
+ await setNftOperatorPermissionsTx(undefined, {
+ onConfirmed: () => {
+ setLoading(false)
+ reloadWindow()
+ },
+ onError() {
+ setLoading(false)
+ },
+ })
+ }
+
+ return (
+
+
+ To add NFTs to your next cycle, you'll need to{' '}
+ grant NFT permissions .
+ {' '}
+
+ Allow the{' '}
+
+ Juicebox NFT deployer contract
+ {' '}
+ to edit this project's cycle.
+
+ }
+ />
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/LaunchNftsCollection.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/LaunchNftsCollection.tsx
new file mode 100644
index 0000000000..fa900a1651
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/LaunchNftsCollection.tsx
@@ -0,0 +1,64 @@
+import { AddNftCollectionForm } from 'components/NftRewards/AddNftCollectionForm/AddNftCollectionForm'
+import { Button } from 'antd'
+import { Trans } from '@lingui/macro'
+import TransactionModal from 'components/modals/TransactionModal'
+import { TransactionSuccessModal } from '../../EditCyclePage/TransactionSuccessModal'
+import { useAppSelector } from 'redux/hooks/useAppSelector'
+import { useLaunchNftsForm } from './hooks/useLaunchNftsForm'
+
+// v4TODO: this whole component needs to be v4-ified
+export function LaunchNftsPage() {
+ const {
+ form,
+ launchCollection,
+ launchButtonLoading,
+ launchTxPending,
+ successModalOpen,
+ setSuccessModalOpen,
+ } = useLaunchNftsForm()
+
+ const postPayModalData = useAppSelector(
+ state => state.creatingV2Project.nftRewards.postPayModal,
+ )
+ const nftRewardsData = useAppSelector(
+ state => state.creatingV2Project.nftRewards,
+ )
+
+ return (
+ <>
+
+ Deploy NFT collection
+
+ }
+ />
+
+ setSuccessModalOpen(false)}
+ content={
+ <>
+
+ Your new NFTs have been deployed
+
+
+
+ New NFTs will be available in your next cycle as long as it
+ starts after your edit deadline.
+
+
+ >
+ }
+ />
+ >
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/hooks/useLaunchNftsForm.ts b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/hooks/useLaunchNftsForm.ts
new file mode 100644
index 0000000000..294aa175cf
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/hooks/useLaunchNftsForm.ts
@@ -0,0 +1,122 @@
+import {
+ DEFAULT_NFT_FLAGS,
+ DEFAULT_NFT_PRICING,
+} from 'redux/slices/editingV2Project'
+import {
+ EditingFundingCycleConfig,
+ useEditingFundingCycleConfig,
+} from 'packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useEditingFundingCycleConfig'
+import {
+ defaultNftCollectionDescription,
+ defaultNftCollectionName,
+ pinNftCollectionMetadata,
+ pinNftRewards,
+} from 'utils/nftRewards'
+
+import { JB721GovernanceType } from 'models/nftRewards'
+import { NftRewardsFormProps } from 'components/NftRewards/AddNftCollectionForm/AddNftCollectionForm'
+import { useAppSelector } from 'redux/hooks/useAppSelector'
+import { useForm } from 'antd/lib/form/Form'
+import { useReconfigureFundingCycle } from 'packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useReconfigureFundingCycle'
+import { useState } from 'react'
+
+// v4TODO: this whole component needs to be v4-ified
+export const useLaunchNftsForm = () => {
+ const [form] = useForm()
+
+ const [ipfsUploading, setIpfsUploading] = useState(false)
+ const [successModalOpen, setSuccessModalOpen] = useState(false)
+
+ const {
+ projectMetadata: { logoUri, name: projectName },
+ } = useAppSelector(state => state.editingV2Project)
+
+ const editingFundingCycleConfig = useEditingFundingCycleConfig()
+ const {
+ reconfigureLoading,
+ reconfigureFundingCycle,
+ txPending: launchTxPending,
+ } = useReconfigureFundingCycle({
+ editingFundingCycleConfig,
+ memo: 'First NFT collection',
+ launchedNewNfts: true,
+ onComplete: () => setSuccessModalOpen(true),
+ })
+
+ const launchButtonLoading = ipfsUploading || reconfigureLoading
+
+ const {
+ editingPayoutGroupedSplits,
+ editingReservedTokensGroupedSplits,
+ editingFundingCycleMetadata,
+ editingFundingCycleData,
+ editingFundAccessConstraints,
+ editingMustStartAtOrAfter,
+ } = editingFundingCycleConfig
+
+ const launchCollection = async () => {
+ setIpfsUploading(true)
+ const formValues = form.getFieldsValue(true) as NftRewardsFormProps
+ const newRewardTiers = formValues.rewards
+ const collectionName =
+ formValues.collectionName ?? defaultNftCollectionName(projectName)
+ const collectionDescription =
+ formValues.collectionDescription ??
+ defaultNftCollectionDescription(projectName)
+ const collectionLogoUri = logoUri ?? ''
+ const collectionInfoUri = ''
+
+ const [rewardTiersCIDs, nftCollectionMetadataUri] = await Promise.all([
+ newRewardTiers ? pinNftRewards(newRewardTiers) : [],
+ pinNftCollectionMetadata({
+ collectionName,
+ collectionDescription,
+ collectionLogoUri,
+ collectionInfoUri,
+ }),
+ ])
+
+ const latestEditingData: EditingFundingCycleConfig = {
+ editingPayoutGroupedSplits,
+ editingReservedTokensGroupedSplits,
+ editingFundingCycleMetadata,
+ editingFundingCycleData,
+ editingFundAccessConstraints,
+ editingNftRewards: {
+ rewardTiers: newRewardTiers,
+ collectionMetadata: {
+ uri: nftCollectionMetadataUri,
+ symbol: formValues.collectionSymbol,
+ name: collectionName,
+ description: collectionDescription,
+ },
+ CIDs: rewardTiersCIDs,
+ postPayModal: {
+ ctaText: formValues.postPayButtonText,
+ ctaLink: formValues.postPayButtonLink,
+ content: formValues.postPayMessage,
+ },
+ flags: {
+ ...DEFAULT_NFT_FLAGS,
+ preventOverspending:
+ formValues.preventOverspending ??
+ DEFAULT_NFT_FLAGS.preventOverspending,
+ },
+ governanceType: JB721GovernanceType.NONE,
+ pricing: DEFAULT_NFT_PRICING, // TODO add to form
+ },
+ editingMustStartAtOrAfter,
+ }
+ reconfigureFundingCycle(latestEditingData)
+ setIpfsUploading(false)
+ }
+
+ return {
+ form,
+ launchButtonLoading,
+ launchCollection,
+ successModalOpen,
+ setSuccessModalOpen,
+ launchTxPending,
+ }
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/index.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/index.tsx
new file mode 100644
index 0000000000..ac2a006b48
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/LaunchNftCollection/index.tsx
@@ -0,0 +1 @@
+export * from './LaunchNftsCollection'
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/EditCollectionDetailsSection.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/EditCollectionDetailsSection.tsx
new file mode 100644
index 0000000000..ead7c72941
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/EditCollectionDetailsSection.tsx
@@ -0,0 +1,158 @@
+import { Button, Form } from 'antd'
+import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
+
+import { Callout } from 'components/Callout/Callout'
+import Loading from 'components/Loading'
+import { MarketplaceFormFields } from './formFields'
+import { NftCollectionDetailsFormItems } from './NftCollectionDetailsFormItems'
+import { NftCollectionMetadata } from 'models/nftRewards'
+import { NftRewardsContext } from 'packages/v2v3/contexts/NftRewards/NftRewardsContext'
+import { Trans } from '@lingui/macro'
+import { useForm } from 'antd/lib/form/Form'
+import { useNftCollectionMetadata } from 'packages/v2v3/hooks/JB721Delegate/useNftCollectionMetadata'
+import { useReconfigureNftCollectionMetadata } from 'packages/v2v3/hooks/transactor/useReconfigureNftCollectionMetadata'
+
+// v4TODO: this whole component needs to be v4-ified
+const useCollectionDetailsForm = () => {
+ const [form] = useForm()
+ const {
+ nftRewards: {
+ collectionMetadata: { uri: collectionMetadataUri },
+ governanceType,
+ },
+ } = useContext(NftRewardsContext)
+
+ const { data: collectionMetadata, isLoading } = useNftCollectionMetadata(
+ collectionMetadataUri,
+ )
+
+ const initialValues: MarketplaceFormFields = useMemo(() => {
+ return {
+ collectionName: collectionMetadata?.name ?? '',
+ collectionDescription: collectionMetadata?.description ?? '',
+ collectionSymbol: collectionMetadata?.symbol ?? '',
+ onChainGovernance: governanceType,
+ }
+ }, [
+ collectionMetadata?.name,
+ collectionMetadata?.description,
+ collectionMetadata?.symbol,
+ governanceType,
+ ])
+
+ return { form, initialValues, isLoading }
+}
+
+export function EditCollectionDetailsSection() {
+ const {
+ form: marketplaceForm,
+ initialValues,
+ isLoading,
+ } = useCollectionDetailsForm()
+ const [formHasUpdated, setFormHasUpdated] = useState()
+ const [txLoading, setTxLoading] = useState()
+ const [txSuccessful, setTxSuccessful] = useState()
+ const [txFailed, setTxFailed] = useState()
+
+ const reconfigureNftCollectionMetadata = useReconfigureNftCollectionMetadata()
+
+ const handleFormChange = useCallback(() => {
+ const hasChanged = (name: string, initialValue: unknown) =>
+ initialValue !== marketplaceForm.getFieldValue(name)
+ const collectionNameChanged = hasChanged(
+ 'collectionName',
+ initialValues.collectionName,
+ )
+ const collectionDescriptionChanged = hasChanged(
+ 'collectionDescription',
+ initialValues.collectionDescription,
+ )
+ const collectionSymbolChanged = hasChanged(
+ 'collectionSymbol',
+ initialValues.collectionSymbol,
+ )
+
+ const hasUpdated =
+ collectionNameChanged ||
+ collectionDescriptionChanged ||
+ collectionSymbolChanged
+
+ setFormHasUpdated(hasUpdated)
+ }, [marketplaceForm, initialValues, setFormHasUpdated])
+
+ useEffect(() => handleFormChange(), [handleFormChange])
+
+ const submitCollectionMetadata = async () => {
+ setTxLoading(true)
+ const newCollectionMetadata: NftCollectionMetadata = {
+ name: marketplaceForm.getFieldValue('collectionName'),
+ description: marketplaceForm.getFieldValue('collectionDescription'),
+ symbol: marketplaceForm.getFieldValue('collectionSymbol'),
+ }
+
+ const txOpts = {
+ onConfirmed() {
+ setTxLoading(false)
+ setTxSuccessful(true)
+ },
+ }
+
+ const txSuccess = await reconfigureNftCollectionMetadata(
+ {
+ ...newCollectionMetadata,
+ },
+ txOpts,
+ )
+
+ if (!txSuccess) {
+ setTxFailed(true)
+ setTxLoading(false)
+ }
+ }
+
+ if (isLoading) return
+
+ return (
+
+
+
+ Changes to your collection details may not be reflected in some
+ marketplaces (like Opensea). Contact the marketplace for support.
+
+
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/EditNftsSection.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/EditNftsSection.tsx
new file mode 100644
index 0000000000..5614d68192
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/EditNftsSection.tsx
@@ -0,0 +1,156 @@
+import { Button, Empty } from 'antd'
+import { Trans, t } from '@lingui/macro'
+import { useCallback, useState } from 'react'
+
+import { Callout } from 'components/Callout/Callout'
+import Loading from 'components/Loading'
+import { RewardsList } from 'components/NftRewards/RewardsList/RewardsList'
+import TransactionModal from 'components/modals/TransactionModal'
+import { TransactionSuccessModal } from '../../EditCyclePage/TransactionSuccessModal'
+import { useAppSelector } from 'redux/hooks/useAppSelector'
+import { useEditingNfts } from '../hooks/useEditingNfts'
+import { useHasNftRewards } from 'packages/v4/hooks/useHasNftRewards'
+import { useUpdateCurrentCollection } from '../hooks/useUpdateCurrentCollection'
+
+// v4TODO: need to build launch NFT capabilities into this
+export function EditNftsSection() {
+ const nftRewardsData = useAppSelector(
+ state => state.editingV2Project.nftRewards,
+ )
+ const [submitLoading, setSubmitLoading] = useState(false)
+ const [successModalOpen, setSuccessModalOpen] = useState(false)
+
+ const { rewardTiers, setRewardTiers, editedRewardTierIds, loading } =
+ useEditingNfts()
+ const hasNftRewards = useHasNftRewards()
+ const { updateExistingCollection, txLoading } = useUpdateCurrentCollection({
+ editedRewardTierIds,
+ rewardTiers,
+ onConfirmed: () => setSuccessModalOpen(true),
+ })
+
+ const showNftRewards = hasNftRewards
+
+ const onNftFormSaved = useCallback(async () => {
+ if (!rewardTiers) return
+
+ setSubmitLoading(true)
+ await updateExistingCollection()
+ setSubmitLoading(false)
+ }, [rewardTiers, updateExistingCollection])
+
+ // const editingFundingCycleConfig = useEditingFundingCycleConfig()
+ // const {
+ // reconfigureLoading: removeDatasourceLoading,
+ // reconfigureFundingCycle,
+ // } = useReconfigureFundingCycle({
+ // editingFundingCycleConfig,
+ // memo: 'Detach NFT collection',
+ // removeDatasource: true,
+ // onComplete: () => setSuccessModalOpen(true),
+ // })
+
+ // const removeDatasource = () => {
+ // reconfigureFundingCycle()
+ // }
+
+ if (loading) return
+
+ const allNftsRemoved = showNftRewards && rewardTiers?.length === 0
+
+ // const hasDataSourceButNoNfts = hasNftRewards && !rewardTiers
+
+ return (
+ <>
+
+ Changes to NFTs will take effect immediately.
+
+
+
+
+
+ {rewardTiers?.length === 0 && (
+
+ )}
+
+
+ {allNftsRemoved && (
+
+
+
+ You're about to delete all NFTs from your collection. This will
+ take effect immediately, and you can add NFTs back to the
+ collection at any time.
+
+
+ If you want to COMPLETELY detach NFTs from your project, you can
+ do so in the Danger Zone section below.
+
+
+
+ )}
+
+
+
+ Edit NFTs
+
+
+
+ {/* {hasDataSourceButNoNfts && (
+
+
+ Danger Zone
+
+
+ This will remove the NFT Extension Contract from
+ your project's next funding cycle. You can relaunch a new NFT
+ collection for later cycles.
+
+
+
+ Detach NFTs
+
+
+
+ )} */}
+
+
+ setSuccessModalOpen(false)}
+ content={
+ <>
+
+ Your NFTs have been edited successfully
+
+
+
+ New NFTs will available on your project page shortly.
+
+
+ >
+ }
+ />
+ >
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/NftCollectionDetailsFormItems.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/NftCollectionDetailsFormItems.tsx
new file mode 100644
index 0000000000..a335185660
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/NftCollectionDetailsFormItems.tsx
@@ -0,0 +1,28 @@
+import { t } from '@lingui/macro'
+import { Form, Input } from 'antd'
+
+export function NftCollectionDetailsFormItems({
+ isReconfigure,
+}: {
+ isReconfigure?: boolean
+}) {
+ return (
+ <>
+
+
+
+ {!isReconfigure ? (
+
+
+
+ ) : null}
+
+
+
+ >
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/UpdateNftsPage.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/UpdateNftsPage.tsx
new file mode 100644
index 0000000000..037a8c3a6b
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/UpdateNftsPage.tsx
@@ -0,0 +1,17 @@
+import { t } from '@lingui/macro'
+import { Tabs } from 'antd'
+import { EditCollectionDetailsSection } from './EditCollectionDetailsSection'
+import { EditNftsSection } from './EditNftsSection'
+
+export function UpdateNftsPage() {
+ const items = [
+ { label: t`NFTs`, key: 'nfts', children: },
+ {
+ label: t`Collection details`,
+ key: 'collection',
+ children: ,
+ },
+ ]
+
+ return
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/formFields.ts b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/formFields.ts
new file mode 100644
index 0000000000..e909771591
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/formFields.ts
@@ -0,0 +1,5 @@
+export type MarketplaceFormFields = {
+ collectionName: string
+ collectionSymbol: string
+ collectionDescription: string
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/index.tsx b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/index.tsx
new file mode 100644
index 0000000000..511e1182d5
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/UpdateNftsPage/index.tsx
@@ -0,0 +1 @@
+export * from './UpdateNftsPage'
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/hooks/useEditingNfts.ts b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/hooks/useEditingNfts.ts
new file mode 100644
index 0000000000..ea4d3f535e
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/hooks/useEditingNfts.ts
@@ -0,0 +1,49 @@
+import { useContext, useEffect, useState } from 'react'
+
+import { useForm } from 'antd/lib/form/Form'
+import { NftRewardTier } from 'models/nftRewards'
+import { V4NftRewardsContext } from 'packages/v4/contexts/V4NftRewards/V4NftRewardsProvider'
+import { tiersEqual } from 'utils/nftRewards'
+import { MarketplaceFormFields } from '../UpdateNftsPage/formFields'
+
+export function useEditingNfts() {
+ const [rewardTiers, _setRewardTiers] = useState()
+ const [marketplaceForm] = useForm()
+ // a list of the `tierRanks` (IDs) of tiers that have been edited
+ const [editedRewardTierIds, setEditedRewardTierIds] = useState([])
+
+ const { nftRewards, loading } = useContext(V4NftRewardsContext)
+
+ const deriveAndSetEditedIds = (newRewards: NftRewardTier[]) => {
+ if (!nftRewards.rewardTiers) return
+ const editedIds = nftRewards.rewardTiers
+ .filter(
+ oldReward =>
+ // oldReward does not exist (exactly) in newRewards.
+ !newRewards.some(newReward =>
+ tiersEqual({ tier1: oldReward, tier2: newReward }),
+ ),
+ )
+ .map(reward => reward.id)
+
+ setEditedRewardTierIds(editedIds)
+ }
+
+ const setRewardTiers = (newRewards: NftRewardTier[]) => {
+ deriveAndSetEditedIds(newRewards)
+ _setRewardTiers(newRewards)
+ }
+
+ // Load the redux state into the state variable
+ useEffect(() => {
+ _setRewardTiers(nftRewards.rewardTiers)
+ }, [nftRewards.rewardTiers])
+
+ return {
+ rewardTiers,
+ setRewardTiers,
+ marketplaceForm,
+ editedRewardTierIds,
+ loading,
+ }
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditNftsPage/hooks/useUpdateCurrentCollection.ts b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/hooks/useUpdateCurrentCollection.ts
new file mode 100644
index 0000000000..a834432668
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditNftsPage/hooks/useUpdateCurrentCollection.ts
@@ -0,0 +1,91 @@
+import { useJBRulesetContext, useWriteJb721TiersHookAdjustTiers } from 'juice-sdk-react'
+import { JB_721_TIER_PARAMS_V4, NftRewardTier } from 'models/nftRewards'
+import { useCallback, useContext, useState } from 'react'
+import { buildJB721TierParams, pinNftRewards } from 'utils/nftRewards'
+
+import { t } from '@lingui/macro'
+import { waitForTransactionReceipt } from '@wagmi/core'
+import { NEW_NFT_ID_LOWER_LIMIT } from 'components/NftRewards/RewardsList/AddEditRewardModal'
+import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
+import { SUPPORTED_JB_MULTITERMINAL_ADDRESS } from 'packages/v4/hooks/useLaunchProjectTx'
+import { wagmiConfig } from 'packages/v4/wagmiConfig'
+import { emitErrorNotification } from 'utils/notifications'
+import { useChainId } from 'wagmi'
+
+export function useUpdateCurrentCollection({
+ rewardTiers,
+ editedRewardTierIds,
+ onConfirmed,
+}: {
+ rewardTiers: NftRewardTier[] | undefined
+ editedRewardTierIds: number[]
+ onConfirmed?: VoidFunction
+}) {
+ const { addTransaction } = useContext(TxHistoryContext)
+ const { rulesetMetadata: { data: rulesetMetadata }} =
+ useJBRulesetContext()
+
+ const { writeContractAsync: writeAdjustTiers } = useWriteJb721TiersHookAdjustTiers()
+ const chainId = useChainId()
+ const chainIdStr =
+ chainId?.toString() as keyof typeof SUPPORTED_JB_MULTITERMINAL_ADDRESS
+
+ const [txLoading, setTxLoading] = useState(false)
+
+ const updateExistingCollection = useCallback(async () => {
+ if (!rewardTiers || !rulesetMetadata?.dataHook) {
+ emitErrorNotification(t`There are no NFT tiers to update.`)
+ return
+ }
+
+ const newRewardTiers = rewardTiers.filter(
+ rewardTier =>
+ rewardTier.id > NEW_NFT_ID_LOWER_LIMIT || // rewardTiers with id > NEW_NFT_ID_LOWER_LIMIT are new
+ editedRewardTierIds.includes(rewardTier.id),
+ )
+
+ // upload new rewardTiers and get their CIDs
+ const rewardTiersCIDs = await pinNftRewards(newRewardTiers)
+
+ const tiersToAdd = buildJB721TierParams({
+ cids: rewardTiersCIDs,
+ rewardTiers: newRewardTiers,
+ version: JB721DelegateVersion.JB721DELEGATE_V4
+ }) as JB_721_TIER_PARAMS_V4[]
+
+ if (!newRewardTiers) {
+ emitErrorNotification(
+ t`Your project's NFT contract version is not supported.`,
+ )
+ return
+ }
+ try {
+ const hash = await writeAdjustTiers({
+ args: [
+ tiersToAdd,
+ editedRewardTierIds.map(id => BigInt(id)),
+ ],
+ address: rulesetMetadata.dataHook,
+ })
+
+
+ addTransaction?.('Update NFT rewards', { hash })
+ await waitForTransactionReceipt(wagmiConfig, {
+ hash,
+ })
+
+ setTxLoading(false)
+ onConfirmed?.()
+ } catch (e) {
+ setTxLoading(false)
+
+ emitErrorNotification((e as unknown as Error).message)
+ }
+ }, [editedRewardTierIds, writeAdjustTiers, onConfirmed, rewardTiers, addTransaction])
+
+ return {
+ updateExistingCollection,
+ txLoading,
+ }
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx
index c9ff01032d..b70c023c82 100644
--- a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx
+++ b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx
@@ -1,17 +1,21 @@
-import { ChevronRightIcon } from '@heroicons/react/20/solid'
-import { ArrowLeftIcon } from '@heroicons/react/24/outline'
-import { Trans, t } from '@lingui/macro'
import { Button, Layout } from 'antd'
-import Link from 'next/link'
-import { useMemo } from 'react'
-import { twJoin } from 'tailwind-merge'
+import { Trans, t } from '@lingui/macro'
+
import { ArchiveProjectSettingsPage } from './ArchiveProjectSettingsPage'
+import { ArrowLeftIcon } from '@heroicons/react/24/outline'
+import { ChevronRightIcon } from '@heroicons/react/20/solid'
import { CreateErc20TokenSettingsPage } from './CreateErc20TokenSettingsPage'
import { EditCyclePage } from './EditCyclePage/EditCyclePage'
-import { useSettingsPagePath } from './hooks/useSettingsPagePath'
+import { EditNftsPage } from './EditNftsPage/EditNftsPage'
+import Link from 'next/link'
import { ProjectDetailsSettingsPage } from './ProjectDetailsSettingsPage/ProjectDetailsSettingsPage'
-import { SettingsPageKey } from './ProjectSettingsDashboard'
import { ProjectSettingsLayout } from './ProjectSettingsLayout'
+import { SettingsPageKey } from './ProjectSettingsDashboard'
+import { isZeroAddress } from 'utils/address'
+import { twJoin } from 'tailwind-merge'
+import { useJBRulesetContext } from 'juice-sdk-react'
+import { useMemo } from 'react'
+import { useSettingsPagePath } from './hooks/useSettingsPagePath'
const SettingsPageComponents: {
[k in SettingsPageKey]: () => JSX.Element | null
@@ -19,7 +23,7 @@ const SettingsPageComponents: {
general: ProjectDetailsSettingsPage,
// handle: () => null, //ProjectHandleSettingsPage,
cycle: EditCyclePage,
- // nfts: () => null, //EditNftsPage,
+ nfts: EditNftsPage,
payouts: () => null, //PayoutsSettingsPage,
// reservedtokens: () => null, //ReservedTokensSettingsPage,
// transferownership: () => null, //TransferOwnershipSettingsPage,
@@ -38,7 +42,7 @@ const V4SettingsPageKeyTitleMap = (
cycle: t`Cycle configuration`,
payouts: t`Payouts`,
// reservedtokens: t`Reserved token recipients`,
- // nfts: hasExistingNfts ? t`Edit NFT collection` : t`Launch New NFT Collection`,
+ nfts: hasExistingNfts ? t`Edit NFT collection` : t`Launch New NFT Collection`,
// transferownership: t`Transfer ownership`,
archiveproject: t`Archive project`,
// heldfees: t`Process held fees`,
@@ -84,13 +88,15 @@ export function ProjectSettingsContent({
}: {
settingsPageKey: SettingsPageKey
}) {
+ const { rulesetMetadata } = useJBRulesetContext()
+
const ActiveSettingsPage = useMemo(
() => SettingsPageComponents[settingsPageKey],
[settingsPageKey],
)
- // const hasExistingNfts = !isZeroAddress(fundingCycleMetadata?.dataSource)
- const pageTitle = V4SettingsPageKeyTitleMap(false)[settingsPageKey]
+ const hasExistingNfts = !isZeroAddress(rulesetMetadata?.data?.dataHook)
+ const pageTitle = V4SettingsPageKeyTitleMap(hasExistingNfts)[settingsPageKey]
return (
diff --git a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard.tsx b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard.tsx
index 5c5e9e016d..a87e42e946 100644
--- a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard.tsx
+++ b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard.tsx
@@ -1,27 +1,28 @@
-import { Trans } from '@lingui/macro'
-import { Button } from 'antd'
-import EthereumAddress from 'components/EthereumAddress'
-import Loading from 'components/Loading'
import {
NativeTokenValue,
useJBContractContext,
useJBProjectMetadataContext,
} from 'juice-sdk-react'
+
+import { Button } from 'antd'
+import EthereumAddress from 'components/EthereumAddress'
import Link from 'next/link'
+import Loading from 'components/Loading'
+import { ProjectSettingsLayout } from './ProjectSettingsLayout'
+import { Trans } from '@lingui/macro'
+import { V4OperatorPermission } from 'packages/v4/models/v4Permissions'
import { useProjectHasErc20Token } from 'packages/v4/hooks/useProjectHasErc20Token'
-import { useV4BalanceOfNativeTerminal } from 'packages/v4/hooks/useV4BalanceOfNativeTerminal'
import useProjectOwnerOf from 'packages/v4/hooks/useV4ProjectOwnerOf'
-import { useV4WalletHasPermission } from 'packages/v4/hooks/useV4WalletHasPermission'
-import { V4OperatorPermission } from 'packages/v4/models/v4Permissions'
-import { useV4DistributableAmount } from '../V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4DistributableAmount'
-import { ProjectSettingsLayout } from './ProjectSettingsLayout'
import { useSettingsPagePath } from './hooks/useSettingsPagePath'
+import { useV4BalanceOfNativeTerminal } from 'packages/v4/hooks/useV4BalanceOfNativeTerminal'
+import { useV4DistributableAmount } from '../V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4DistributableAmount'
+import { useV4WalletHasPermission } from 'packages/v4/hooks/useV4WalletHasPermission'
export type SettingsPageKey =
| 'general'
// | 'handle' -> commenting out not necessary for v4
| 'cycle'
- // | 'nfts'
+ | 'nfts'
| 'payouts'
// | 'reservedtokens'
// | 'transferownership'
@@ -172,6 +173,18 @@ export function ProjectSettingsDashboard() {
Edit next ruleset
+ NFTs & Rewards}
+ subtitle={Manage your project's NFTs and rewards }
+ >
+
+
Tools}
subtitle={Extended functionality for project owners }
diff --git a/src/pages/api/juicebox/jb-721-delegate/[dataSourceAddress].ts b/src/pages/api/juicebox/jb-721-delegate/[dataSourceAddress].ts
index 7fb4ede6b1..d36fe88dc8 100644
--- a/src/pages/api/juicebox/jb-721-delegate/[dataSourceAddress].ts
+++ b/src/pages/api/juicebox/jb-721-delegate/[dataSourceAddress].ts
@@ -1,15 +1,16 @@
+import { ForgeDeploy, addressFor } from 'forge-run-parser'
+import { NextApiRequest, NextApiResponse } from 'next'
+import { isEqualAddress, isZeroAddress } from 'utils/address'
+
import { readNetwork } from 'constants/networks'
import { readProvider } from 'constants/readProvider'
import { Contract } from 'ethers'
-import { ForgeDeploy, addressFor } from 'forge-run-parser'
import { enableCors } from 'lib/api/nextjs'
import { getLogger } from 'lib/logger'
-import { NextApiRequest, NextApiResponse } from 'next'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { IJB721TieredDelegate_V3_INTERFACE_ID } from 'packages/v2v3/constants/nftRewards'
import { loadJB721DelegateJson } from 'packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateAbi'
import { loadJB721DelegateAddress } from 'packages/v2v3/hooks/JB721Delegate/contracts/useJB721DelegateContractAddress'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
-import { isEqualAddress, isZeroAddress } from 'utils/address'
import JBDelegatesRegistryJson from './IJBDelegatesRegistry.json'
const logger = getLogger('api/juicebox/jb-721-delegate/[dataSourceAddress]')
diff --git a/src/utils/delegateMetadata/encodeDelegateMetadata.ts b/src/utils/delegateMetadata/encodeDelegateMetadata.ts
index c40da21501..4b6858a553 100644
--- a/src/utils/delegateMetadata/encodeDelegateMetadata.ts
+++ b/src/utils/delegateMetadata/encodeDelegateMetadata.ts
@@ -1,12 +1,13 @@
import { BigNumber, BigNumberish, utils } from 'ethers'
+import {
+ JB721DelegatePayMetadata,
+ encodeJb721DelegatePayMetadata,
+} from './encodeJb721DelegateMetadata'
+
import { createMetadata } from 'juicebox-metadata-helper'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { IJBBuybackDelegate_INTERFACE_ID } from 'packages/v2v3/constants/buybackDelegate'
import { IJBTiered721Delegate_V3_4_PAY_ID } from 'packages/v2v3/constants/nftRewards'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
-import {
- JB721DelegatePayMetadata,
- encodeJb721DelegatePayMetadata,
-} from './encodeJb721DelegateMetadata'
/**
* Encode pay metadata for project delegate contracts.
diff --git a/src/utils/delegateMetadata/encodeJb721DelegateMetadata.ts b/src/utils/delegateMetadata/encodeJb721DelegateMetadata.ts
index 3e4cd84944..e11f3b8cd6 100644
--- a/src/utils/delegateMetadata/encodeJb721DelegateMetadata.ts
+++ b/src/utils/delegateMetadata/encodeJb721DelegateMetadata.ts
@@ -1,13 +1,14 @@
-import { DEFAULT_ALLOW_OVERSPENDING } from 'constants/transactionDefaults'
import { constants, utils } from 'ethers'
-import { createMetadata } from 'juicebox-metadata-helper'
import {
- IJB721Delegate_V3_2_INTERFACE_ID,
- IJB721Delegate_V3_INTERFACE_ID,
- IJBTiered721Delegate_V3_2_INTERFACE_ID,
- IJBTiered721Delegate_V3_4_REDEEM_ID,
+ IJB721Delegate_V3_2_INTERFACE_ID,
+ IJB721Delegate_V3_INTERFACE_ID,
+ IJBTiered721Delegate_V3_2_INTERFACE_ID,
+ IJBTiered721Delegate_V3_4_REDEEM_ID,
} from 'packages/v2v3/constants/nftRewards'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
+
+import { DEFAULT_ALLOW_OVERSPENDING } from 'constants/transactionDefaults'
+import { createMetadata } from 'juicebox-metadata-helper'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
interface JB721DELAGATE_V3_PAY_METADATA {
tierIdsToMint: number[]
diff --git a/src/utils/nftRewards.ts b/src/utils/nftRewards.ts
index 6ba1ee9218..3480e7b7a2 100644
--- a/src/utils/nftRewards.ts
+++ b/src/utils/nftRewards.ts
@@ -1,10 +1,4 @@
-import { NftFileType } from 'components/inputs/UploadNoStyle'
-import { VIDEO_FILE_TYPES } from 'constants/fileTypes'
-import { juiceboxEmojiImageUri } from 'constants/images'
-import { WAD_DECIMALS } from 'constants/numbers'
-import { DEFAULT_JB_721_TIER_CATEGORY } from 'constants/transactionDefaults'
import { BigNumber, constants, utils } from 'ethers'
-import { pinJson } from 'lib/api/ipfs'
import {
IPFSNftCollectionMetadata,
IPFSNftRewardTier,
@@ -16,15 +10,23 @@ import {
JBTiered721Flags,
JB_721_TIER_PARAMS_V3_1,
JB_721_TIER_PARAMS_V3_2,
+ JB_721_TIER_PARAMS_V4,
JB_721_TIER_V3_2,
JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1,
NftRewardTier,
} from 'models/nftRewards'
+import { decodeEncodedIpfsUri, encodeIpfsUri, ipfsUri } from 'utils/ipfs'
+
+import { NftFileType } from 'components/inputs/UploadNoStyle'
+import { VIDEO_FILE_TYPES } from 'constants/fileTypes'
+import { juiceboxEmojiImageUri } from 'constants/images'
+import { WAD_DECIMALS } from 'constants/numbers'
+import { DEFAULT_JB_721_TIER_CATEGORY } from 'constants/transactionDefaults'
+import { pinJson } from 'lib/api/ipfs'
+import { JB721DelegateVersion } from 'models/JB721Delegate'
import { DEFAULT_NFT_MAX_SUPPLY } from 'packages/v2v3/constants/nftRewards'
-import { JB721DelegateVersion } from 'packages/v2v3/models/contracts'
import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption'
import { JB721TierV4 } from 'packages/v4/contexts/V4NftRewards/V4NftRewardsProvider'
-import { decodeEncodedIpfsUri, encodeIpfsUri, ipfsUri } from 'utils/ipfs'
export function sortNftsByContributionFloor(
rewardTiers: NftRewardTier[],
@@ -308,6 +310,38 @@ function nftRewardTierToJB721TierParamsV3_2(
}
}
+function nftRewardTierToJB721TierParamsV4(
+ rewardTier: NftRewardTier,
+ cid: string,
+): JB_721_TIER_PARAMS_V4 {
+ const price = utils.parseEther(rewardTier.contributionFloor.toString()).toBigInt();
+ const maxSupply = rewardTier.maxSupply;
+ const initialSupply = maxSupply ?? DEFAULT_NFT_MAX_SUPPLY;
+ const encodedIPFSUri = encodeIpfsUri(cid);
+
+ const reservedRate = rewardTier.reservedRate ?? 0;
+ const reservedTokenBeneficiary = rewardTier.beneficiary ?? constants.AddressZero;
+ const votingUnits = rewardTier.votingWeight ? BigNumber.from(rewardTier.votingWeight).toNumber() : 0;
+
+ return {
+ price,
+ initialSupply,
+ votingUnits,
+ reserveFrequency: reservedRate,
+ reserveBeneficiary: reservedTokenBeneficiary,
+ encodedIPFSUri,
+ category: DEFAULT_JB_721_TIER_CATEGORY,
+ discountPercent: 0,
+ allowOwnerMint: false,
+ useReserveBeneficiaryAsDefault: false,
+ transfersPausable: false,
+ useVotingUnits: true,
+ cannotBeRemoved: false,
+ cannotIncreaseDiscountPercent: false, // Explicitly add all required properties
+ } as JB_721_TIER_PARAMS_V4;
+}
+
+
export function buildJB721TierParams({
cids, // MUST BE SORTED BY CONTRIBUTION FLOOR (TODO: not ideal)
rewardTiers,
@@ -315,8 +349,8 @@ export function buildJB721TierParams({
}: {
cids: string[]
rewardTiers: NftRewardTier[]
- version: JB721DelegateVersion
-}): (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2)[] {
+ version?: JB721DelegateVersion
+}): (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2 | JB_721_TIER_PARAMS_V4)[] {
const sortedRewardTiers = sortNftsByContributionFloor(rewardTiers)
// `cids` are ordered the same as `rewardTiers` so can get corresponding values from same index
@@ -328,7 +362,8 @@ export function buildJB721TierParams({
):
| JB721TierParams
| JB_721_TIER_PARAMS_V3_1
- | JB_721_TIER_PARAMS_V3_2 => {
+ | JB_721_TIER_PARAMS_V3_2
+ | JB_721_TIER_PARAMS_V4 => {
const rewardTier = sortedRewardTiers[index]
if (version === JB721DelegateVersion.JB721DELEGATE_V3) {
return nftRewardTierToJB721TierParamsV3(rewardTier, cid)
@@ -336,6 +371,9 @@ export function buildJB721TierParams({
if (version === JB721DelegateVersion.JB721DELEGATE_V3_1) {
return nftRewardTierToJB721TierParamsV3_1(rewardTier, cid)
}
+ if (version === JB721DelegateVersion.JB721DELEGATE_V4) {
+ return nftRewardTierToJB721TierParamsV4(rewardTier, cid)
+ }
// default return v3.2 params (unchanged in 3.3, 3.4)
return nftRewardTierToJB721TierParamsV3_2(rewardTier, cid)
@@ -346,6 +384,18 @@ export function buildJB721TierParams({
// Tiers MUST BE in ascending order when sent to contract.
// bit bongy, sorry!
+ if (version === JB721DelegateVersion.JB721DELEGATE_V4) {
+ if ((a as JB_721_TIER_PARAMS_V4).price > (b as JB_721_TIER_PARAMS_V4).price) {
+ return 1
+ }
+
+ if ((a as JB_721_TIER_PARAMS_V4).price < (b as JB_721_TIER_PARAMS_V4).price) {
+ return -1
+ }
+
+ return 0
+ }
+
if (
version === JB721DelegateVersion.JB721DELEGATE_V3_2 ||
version === JB721DelegateVersion.JB721DELEGATE_V3_3 ||
@@ -395,7 +445,7 @@ type DeployTiered721DelegateParams = {
collectionName: string
collectionSymbol: string
currency: V2V3CurrencyOption
- tiers: (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2)[]
+ tiers: (JB721TierParams | JB_721_TIER_PARAMS_V3_1 | JB_721_TIER_PARAMS_V3_2 | JB_721_TIER_PARAMS_V4)[]
ownerAddress: string
governanceType: JB721GovernanceType
contractAddresses: {