Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: NFTs in v4 create #4504

Merged
merged 6 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@
"graphql": "^16.8.1",
"he": "^1.2.0",
"jsonwebtoken": "^9.0.0",
"juice-sdk-core": "^11.0.0-alpha",
"juice-sdk-react": "^11.0.0-alpha",
"juice-sdk-core": "^11.5.0-alpha",
"juice-sdk-react": "^11.6.0-alpha",
"juicebox-metadata-helper": "0.1.7",
"less": "4.1.2",
"lodash": "^4.17.21",
Expand Down
6 changes: 4 additions & 2 deletions src/packages/v4/components/Create/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import Loading from 'components/Loading'
import { readNetwork } from 'constants/networks'
import { NetworkName } from 'models/networkName'
import { useRouter } from 'next/router'
import { CreateBadge } from './components/CreateBadge'
import { FundingCyclesPage } from './components/pages/FundingCycles/FundingCyclesPage'
import { NftRewardsPage } from './components/pages/NftRewards/NftRewardsPage'
import { PayoutsPage } from './components/pages/PayoutsPage/PayoutsPage'
import { ProjectDetailsPage } from './components/pages/ProjectDetails/ProjectDetailsPage'
import { ProjectTokenPage } from './components/pages/ProjectToken/ProjectTokenPage'
Expand Down Expand Up @@ -86,7 +88,7 @@ export function Create() {
>
<ProjectTokenPage />
</Wizard.Page>
{/* <Wizard.Page
<Wizard.Page
name="nftRewards"
title={
<div className="flex items-center gap-3">
Expand All @@ -99,7 +101,7 @@ export function Create() {
}
>
<NftRewardsPage />
</Wizard.Page> */}
</Wizard.Page>
<Wizard.Page
name="reconfigurationRules"
title={<Trans>Edit Deadline</Trans>}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useContext, useMemo } from 'react'

import { t } from '@lingui/macro'
import { CreatePage } from 'models/createPage'
import { useCallback, useContext, useMemo } from 'react'
import { useAppSelector } from 'redux/hooks/useAppSelector'
import { useEditingCreateFurthestPageReached } from 'redux/hooks/useEditingCreateFurthestPageReached'
import { WizardContext } from '../contexts/WizardContext'
Expand All @@ -11,7 +12,7 @@ const stepNames = (): Record<string, string> => {
fundingCycles: t`Rulesets`,
payouts: t`Payouts`,
projectToken: t`Token`,
// nftRewards: t`NFTs`,
nftRewards: t`NFTs`,
reconfigurationRules: t`Deadline`,
reviewDeploy: t`Deploy`,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { WizardContext } from '../../Wizard/contexts/WizardContext'
import { FundingConfigurationReview } from './components/FundingConfigurationReview/FundingConfigurationReview'
import { ProjectDetailsReview } from './components/ProjectDetailsReview/ProjectDetailsReview'
import { ProjectTokenReview } from './components/ProjectTokenReview/ProjectTokenReview'
import { RewardsReview } from './components/RewardsReview/RewardsReview'
import { RulesReview } from './components/RulesReview/RulesReview'

enum ReviewDeployKey {
Expand Down Expand Up @@ -164,7 +165,7 @@ export const ReviewDeployPage = () => {
>
<ProjectTokenReview />
</CreateCollapse.Panel>
{/* <CreateCollapse.Panel
<CreateCollapse.Panel
key={ReviewDeployKey.Rewards}
collapsible={nftRewardsAreSet ? 'header' : 'disabled'}
header={
Expand All @@ -174,7 +175,7 @@ export const ReviewDeployPage = () => {
}
>
<RewardsReview />
</CreateCollapse.Panel> */}
</CreateCollapse.Panel>
<CreateCollapse.Panel
key={ReviewDeployKey.Rules}
header={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,83 @@
import { TransactionCallbacks } from 'models/transaction'
import { useLaunchProjectWithNftsTx } from 'packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx'
import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate'
import { useCallback, useMemo } from 'react'
import { ONE_BILLION } from 'constants/numbers'
import { DEFAULT_JB_721_TIER_CATEGORY } from 'constants/transactionDefaults'
import { JBTiered721Flags, NftRewardTier } from 'models/nftRewards'
import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx'
import { useLaunchProjectWithNftsTx } from 'packages/v4/hooks/useLaunchProjectWithNftsTx'
import { JB721TierConfig, JB721TiersHookFlags } from 'packages/v4/models/nfts'
import { useCallback } from 'react'
import {
useAppSelector,
useEditingV2V3FundAccessConstraintsSelector,
useEditingV2V3FundingCycleDataSelector,
useEditingV2V3FundingCycleMetadataSelector,
} from 'redux/hooks/useAppSelector'
import { DEFAULT_NFT_FLAGS } from 'redux/slices/editingV2Project'
import { encodeIpfsUri } from 'utils/ipfs'
import { NFT_FUNDING_CYCLE_METADATA_OVERRIDES } from 'utils/nftFundingCycleMetadataOverrides'
import { buildJB721TierParams } from 'utils/nftRewards'
import { sortNftsByContributionFloor } from 'utils/nftRewards'
import { Address, parseEther, zeroAddress } from 'viem'
export const DEFAULT_NFT_MAX_SUPPLY = ONE_BILLION - 1

function nftRewardTierToJB721TierConfig(
rewardTier: NftRewardTier,
cid: string,
): JB721TierConfig {
const price = parseEther(rewardTier.contributionFloor.toString())
const initialSupply = rewardTier.maxSupply ?? DEFAULT_NFT_MAX_SUPPLY
const encodedIPFSUri = encodeIpfsUri(cid)

const reserveFrequency = rewardTier.reservedRate
? rewardTier.reservedRate - 1
: 0
const reserveBeneficiary =
(rewardTier.beneficiary as Address | undefined) ?? zeroAddress
const votingUnits = parseInt(rewardTier.votingWeight ?? '0')
// should default to 0, with useVotingUnits `true`, to save gas

return {
price,
initialSupply,
votingUnits,
reserveFrequency,
reserveBeneficiary,
encodedIPFSUri,
allowOwnerMint: false,
useReserveBeneficiaryAsDefault: false,
transfersPausable: false,
useVotingUnits: true,
cannotBeRemoved: false,
cannotIncreaseDiscountPercent: false,
discountPercent: 0,
remainingSupply: initialSupply,
category: DEFAULT_JB_721_TIER_CATEGORY,
resolvedUri: '',
}
}

function buildJB721TierParams({
cids, // MUST BE SORTED BY CONTRIBUTION FLOOR (TODO: not ideal)
rewardTiers,
}: {
cids: string[]
rewardTiers: NftRewardTier[]
}): JB721TierConfig[] {
const sortedRewardTiers = sortNftsByContributionFloor(rewardTiers)

return cids.map((cid, index) => {
const rewardTier = sortedRewardTiers[index]

return nftRewardTierToJB721TierConfig(rewardTier, cid)
})
}

function toV4Flags(v2v3Flags: JBTiered721Flags): JB721TiersHookFlags {
return {
noNewTiersWithOwnerMinting: v2v3Flags.lockManualMintingChanges,
noNewTiersWithReserves: v2v3Flags.lockReservedTokenChanges,
noNewTiersWithVotes: v2v3Flags.lockVotingUnitChanges,
preventOverspending: v2v3Flags.preventOverspending,
}
}

/**
* Hook that returns a function that deploys a project with NFT rewards.
Expand All @@ -33,22 +100,12 @@ export const useDeployNftProject = () => {
const fundingCycleData = useEditingV2V3FundingCycleDataSelector()
const fundAccessConstraints = useEditingV2V3FundAccessConstraintsSelector()

const collectionName = useMemo(
() =>
nftRewards.collectionMetadata.name
? nftRewards.collectionMetadata.name
: projectMetadata.name,
[nftRewards.collectionMetadata.name, projectMetadata.name],
)
const collectionSymbol = useMemo(
() => nftRewards.collectionMetadata.symbol ?? '',
[nftRewards.collectionMetadata.symbol],
)
const nftFlags = useMemo(
() => nftRewards.flags ?? DEFAULT_NFT_FLAGS,
[nftRewards.flags],
)
const governanceType = nftRewards.governanceType
const collectionName = nftRewards.collectionMetadata.name
? nftRewards.collectionMetadata.name
: projectMetadata.name
const collectionSymbol = nftRewards.collectionMetadata.symbol ?? ''
const nftFlags = nftRewards.flags ?? DEFAULT_NFT_FLAGS
// const governanceType = nftRewards.governanceType
const currency = nftRewards.pricing.currency

/**
Expand All @@ -63,15 +120,14 @@ export const useDeployNftProject = () => {
rewardTierCids,
nftCollectionMetadataUri,

onDone,
onConfirmed,
onCancelled,
onError,
onTransactionPending,
onTransactionConfirmed,
onTransactionError,
}: {
metadataCid: string
rewardTierCids: string[]
nftCollectionMetadataUri: string
} & TransactionCallbacks) => {
} & LaunchTxOpts) => {
if (!collectionName) throw new Error('No collection name or project name')
if (!(rewardTierCids.length && nftRewards.rewardTiers))
throw new Error('No NFTs')
Expand All @@ -80,8 +136,8 @@ export const useDeployNftProject = () => {
const tiers = buildJB721TierParams({
cids: rewardTierCids,
rewardTiers: nftRewards.rewardTiers,
version: DEFAULT_JB_721_DELEGATE_VERSION,
})
const flags = toV4Flags(nftFlags)

return await launchProjectWithNftsTx(
{
Expand All @@ -90,9 +146,8 @@ export const useDeployNftProject = () => {
collectionName,
collectionSymbol,
currency,
governanceType,
tiers,
flags: nftFlags,
flags,
},
projectData: {
owner: inputProjectOwner?.length ? inputProjectOwner : undefined,
Expand All @@ -108,10 +163,9 @@ export const useDeployNftProject = () => {
},
},
{
onDone,
onConfirmed,
onCancelled,
onError,
onTransactionPending,
onTransactionConfirmed,
onTransactionError,
},
)
},
Expand All @@ -123,7 +177,6 @@ export const useDeployNftProject = () => {
reservedTokensGroupedSplits,
launchProjectWithNftsTx,
collectionSymbol,
governanceType,
inputProjectOwner,
fundingCycleData,
mustStartAtOrAfter,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { uploadProjectMetadata } from 'lib/api/ipfs'
import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx'
import { useCallback, useState } from 'react'
import { useAppDispatch } from 'redux/hooks/useAppDispatch'
import {
useAppSelector,
useEditingV2V3FundAccessConstraintsSelector,
useEditingV2V3FundingCycleDataSelector,
useEditingV2V3FundingCycleMetadataSelector,
} from 'redux/hooks/useAppSelector'

import { uploadProjectMetadata } from 'lib/api/ipfs'
import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx'
import { useAppDispatch } from 'redux/hooks/useAppDispatch'
import { editingV2ProjectActions } from 'redux/slices/editingV2Project'
import { emitErrorNotification } from 'utils/notifications'
import { useDeployNftProject } from './hooks/NFT/useDeployNftProject'
import { useIsNftProject } from './hooks/NFT/useIsNftProject'
import { useUploadNftRewards } from './hooks/NFT/useUploadNftRewards'
import { useDeployStandardProject } from './hooks/useDeployStandardProject'

const JUICEBOX_DOMAIN = 'juicebox'
Expand All @@ -22,10 +26,9 @@ export const useDeployProject = () => {
const [isDeploying, setIsDeploying] = useState<boolean>(false)
const [transactionPending, setTransactionPending] = useState<boolean>()

// const isNftProject = useIsNftProject()
// const uploadNftRewards = useUploadNftRewards()
// const deployNftProject = useDeployNftProject()

const isNftProject = useIsNftProject()
const uploadNftRewards = useUploadNftRewards()
const deployNftProject = useDeployNftProject()
const deployStandardProject = useDeployStandardProject()

const {
Expand Down Expand Up @@ -89,15 +92,15 @@ export const useDeployProject = () => {
setIsDeploying(false)
throw new Error('Error deploying project.')
}
// let nftCids: Awaited<ReturnType<typeof uploadNftRewards>> | undefined
// try {
// if (isNftProject) {
// nftCids = await uploadNftRewards()
// }
// } catch (error) {
// handleDeployFailure(error)
// return
// }
let nftCids: Awaited<ReturnType<typeof uploadNftRewards>> | undefined
try {
if (isNftProject) {
nftCids = await uploadNftRewards()
}
} catch (error) {
handleDeployFailure(error)
return
}

let softTargetAmount: string | undefined
let softTargetCurrency: string | undefined
Expand All @@ -120,42 +123,42 @@ export const useDeployProject = () => {
}

try {
// let tx
// if (isNftProject) {
// tx = await deployNftProject({
// metadataCid: projectMetadataCid,
// rewardTierCids: nftCids!.rewardTiers,
// nftCollectionMetadataUri: nftCids!.nfCollectionMetadata,
// ...operationCallbacks(onProjectDeployed),
// })
// } else {
const tx = await deployStandardProject({
metadataCid: projectMetadataCid,
...operationCallbacks(onProjectDeployed),
})
// }
// if (!tx) {
setIsDeploying(false)
setTransactionPending(false)
let tx
if (isNftProject) {
tx = await deployNftProject({
metadataCid: projectMetadataCid,
rewardTierCids: nftCids!.rewardTiers,
nftCollectionMetadataUri: nftCids!.nfCollectionMetadata,
...operationCallbacks(onProjectDeployed),
})
} else {
tx = await deployStandardProject({
metadataCid: projectMetadataCid,
...operationCallbacks(onProjectDeployed),
})
}
if (!tx) {
setIsDeploying(false)
setTransactionPending(false)
return
// }
}
} catch (error) {
handleDeployFailure(error)
return
}
},
[
// deployNftProject,
deployNftProject,
deployStandardProject,
fundAccessConstraints,
fundingCycleData,
fundingCycleMetadata,
handleDeployFailure,
// isNftProject,
isNftProject,
operationCallbacks,
postPayModal,
projectMetadata,
// uploadNftRewards,
uploadNftRewards,
],
)
return {
Expand Down
3 changes: 2 additions & 1 deletion src/packages/v4/contexts/V4ProjectMetadataProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useJBProjectMetadataContext } from 'juice-sdk-react'

import { PV_V4 } from 'constants/pv'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import { useJBProjectMetadataContext } from 'juice-sdk-react'
import { PropsWithChildren } from 'react'

export default function V4ProjectMetadataProvider({
Expand Down
Loading
Loading