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 ( + <> +
+ {`No + + You haven't launched an NFT collection yet. + + +
+ + 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. + + +
+ +
+ + {txSuccessful ? ( + + Saved! + + ) : txFailed ? ( + + Something went wrong! + + ) : null} +
+ +
+ ) +} 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. +

+
+
+ )} + + + + {/* {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. +

+ +
+ )} */} + + + 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} + > +
    +
  • + + NFTs + +
  • +
+
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: {