diff --git a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts index bebbdb3a5f..da471c5c53 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/hooks/NFT/useDeployNftProject.ts @@ -1,7 +1,10 @@ -import { TransactionCallbacks } from 'models/transaction' -import { DEFAULT_JB_721_DELEGATE_VERSION } from 'packages/v2v3/hooks/defaultContracts/useDefaultJB721Delegate' +import { ONE_BILLION } from 'constants/numbers' +import { DEFAULT_JB_721_TIER_CATEGORY } from 'constants/transactionDefaults' +import { JBTiered721Flags, NftRewardTier } from 'models/nftRewards' +import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx' import { useLaunchProjectWithNftsTx } from 'packages/v4/hooks/useLaunchProjectWithNftsTx' -import { useCallback, useMemo } from 'react' +import { JB721TierConfig, JB721TiersHookFlags } from 'packages/v4/models/nfts' +import { useCallback } from 'react' import { useAppSelector, useEditingV2V3FundAccessConstraintsSelector, @@ -9,8 +12,72 @@ import { useEditingV2V3FundingCycleMetadataSelector, } from 'redux/hooks/useAppSelector' import { DEFAULT_NFT_FLAGS } from 'redux/slices/editingV2Project' +import { encodeIpfsUri } from 'utils/ipfs' import { NFT_FUNDING_CYCLE_METADATA_OVERRIDES } from 'utils/nftFundingCycleMetadataOverrides' -import { buildJB721TierParams } from 'utils/nftRewards' +import { sortNftsByContributionFloor } from 'utils/nftRewards' +import { Address, parseEther, zeroAddress } from 'viem' +export const DEFAULT_NFT_MAX_SUPPLY = ONE_BILLION - 1 + +function nftRewardTierToJB721TierConfig( + rewardTier: NftRewardTier, + cid: string, +): JB721TierConfig { + const price = parseEther(rewardTier.contributionFloor.toString()) + const initialSupply = rewardTier.maxSupply ?? DEFAULT_NFT_MAX_SUPPLY + const encodedIPFSUri = encodeIpfsUri(cid) + + const reserveFrequency = rewardTier.reservedRate + ? rewardTier.reservedRate - 1 + : 0 + const reserveBeneficiary = + (rewardTier.beneficiary as Address | undefined) ?? zeroAddress + const votingUnits = parseInt(rewardTier.votingWeight ?? '0') + // should default to 0, with useVotingUnits `true`, to save gas + + return { + price, + initialSupply, + votingUnits, + reserveFrequency, + reserveBeneficiary, + encodedIPFSUri, + allowOwnerMint: false, + useReserveBeneficiaryAsDefault: false, + transfersPausable: false, + useVotingUnits: true, + cannotBeRemoved: false, + cannotIncreaseDiscountPercent: false, + discountPercent: 0, + remainingSupply: initialSupply, + category: DEFAULT_JB_721_TIER_CATEGORY, + resolvedUri: '', + } +} + +function buildJB721TierParams({ + cids, // MUST BE SORTED BY CONTRIBUTION FLOOR (TODO: not ideal) + rewardTiers, +}: { + cids: string[] + rewardTiers: NftRewardTier[] +}): JB721TierConfig[] { + const sortedRewardTiers = sortNftsByContributionFloor(rewardTiers) + + return cids.map((cid, index) => { + const rewardTier = sortedRewardTiers[index] + + return nftRewardTierToJB721TierConfig(rewardTier, cid) + }) +} + +function toV4Flags(v2v3Flags: JBTiered721Flags): JB721TiersHookFlags { + return { + noNewTiersWithOwnerMinting: v2v3Flags.lockManualMintingChanges, + noNewTiersWithReserves: v2v3Flags.lockReservedTokenChanges, + noNewTiersWithVotes: v2v3Flags.lockVotingUnitChanges, + preventOverspending: v2v3Flags.preventOverspending, + } +} /** * Hook that returns a function that deploys a project with NFT rewards. @@ -33,22 +100,12 @@ export const useDeployNftProject = () => { const fundingCycleData = useEditingV2V3FundingCycleDataSelector() const fundAccessConstraints = useEditingV2V3FundAccessConstraintsSelector() - const collectionName = useMemo( - () => - nftRewards.collectionMetadata.name - ? nftRewards.collectionMetadata.name - : projectMetadata.name, - [nftRewards.collectionMetadata.name, projectMetadata.name], - ) - const collectionSymbol = useMemo( - () => nftRewards.collectionMetadata.symbol ?? '', - [nftRewards.collectionMetadata.symbol], - ) - const nftFlags = useMemo( - () => nftRewards.flags ?? DEFAULT_NFT_FLAGS, - [nftRewards.flags], - ) - const governanceType = nftRewards.governanceType + const collectionName = nftRewards.collectionMetadata.name + ? nftRewards.collectionMetadata.name + : projectMetadata.name + const collectionSymbol = nftRewards.collectionMetadata.symbol ?? '' + const nftFlags = nftRewards.flags ?? DEFAULT_NFT_FLAGS + // const governanceType = nftRewards.governanceType const currency = nftRewards.pricing.currency /** @@ -63,15 +120,14 @@ export const useDeployNftProject = () => { rewardTierCids, nftCollectionMetadataUri, - onDone, - onConfirmed, - onCancelled, - onError, + onTransactionPending, + onTransactionConfirmed, + onTransactionError, }: { metadataCid: string rewardTierCids: string[] nftCollectionMetadataUri: string - } & TransactionCallbacks) => { + } & LaunchTxOpts) => { if (!collectionName) throw new Error('No collection name or project name') if (!(rewardTierCids.length && nftRewards.rewardTiers)) throw new Error('No NFTs') @@ -80,8 +136,8 @@ export const useDeployNftProject = () => { const tiers = buildJB721TierParams({ cids: rewardTierCids, rewardTiers: nftRewards.rewardTiers, - version: DEFAULT_JB_721_DELEGATE_VERSION, }) + const flags = toV4Flags(nftFlags) return await launchProjectWithNftsTx( { @@ -90,9 +146,8 @@ export const useDeployNftProject = () => { collectionName, collectionSymbol, currency, - governanceType, tiers, - flags: nftFlags, + flags, }, projectData: { owner: inputProjectOwner?.length ? inputProjectOwner : undefined, @@ -108,10 +163,9 @@ export const useDeployNftProject = () => { }, }, { - onDone, - onConfirmed, - onCancelled, - onError, + onTransactionPending, + onTransactionConfirmed, + onTransactionError, }, ) }, @@ -123,7 +177,6 @@ export const useDeployNftProject = () => { reservedTokensGroupedSplits, launchProjectWithNftsTx, collectionSymbol, - governanceType, inputProjectOwner, fundingCycleData, mustStartAtOrAfter, diff --git a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts index b97afca36b..843ea6aeb2 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts @@ -29,7 +29,6 @@ export const useDeployProject = () => { const isNftProject = useIsNftProject() const uploadNftRewards = useUploadNftRewards() const deployNftProject = useDeployNftProject() - const deployStandardProject = useDeployStandardProject() const { diff --git a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts index 455dd5553f..ab20f33604 100644 --- a/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts +++ b/src/packages/v4/hooks/useLaunchProjectWithNftsTx.ts @@ -15,6 +15,7 @@ import { } from 'packages/v4/models/nfts' import { useContext } from 'react' import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' +import { ipfsUri } from 'utils/ipfs' import { Address, toBytes, @@ -34,6 +35,7 @@ import { SUPPORTED_JB_CONTROLLER_ADDRESS, SUPPORTED_JB_MULTITERMINAL_ADDRESS, } from './useLaunchProjectTx' + function createSalt() { const base: string = '0x' + Math.random().toString(16).slice(2) // idk lol const salt = toHex(toBytes(base, { size: 32 })) @@ -65,7 +67,6 @@ export function useLaunchProjectWithNftsTx() { const defaultJBController = chainId ? SUPPORTED_JB_CONTROLLER_ADDRESS[chainId] : undefined - const defaultJBETHPaymentTerminal = chainId ? SUPPORTED_JB_MULTITERMINAL_ADDRESS[chainId] : undefined @@ -74,7 +75,7 @@ export function useLaunchProjectWithNftsTx() { : undefined const { writeContractAsync: writeLaunchProject } = - useWriteJb721TiersHookProjectDeployerLaunchProjectFor() + useWriteJb721TiersHookProjectDeployerLaunchProjectFor() return async ( { @@ -130,16 +131,21 @@ export function useLaunchProjectWithNftsTx() { return Promise.resolve(false) } - const _owner = owner?.length ? owner : userAddress + const _owner = (owner?.length ? owner : userAddress) as Address const deployTiered721HookData: JBDeploy721TiersHookConfig = { name: collectionName, symbol: collectionSymbol, - baseUri: '', // ? - tokenUriResolver: zeroAddress, // ? - contractUri: collectionUri, // ? - tiersConfig: tiers, - reserveBeneficiary: zeroAddress, //?; + baseUri: ipfsUri(''), // ? + tokenUriResolver: zeroAddress, + contractUri: ipfsUri(collectionUri), + tiersConfig: { + currency, + decimals: 18, + prices: zeroAddress, + tiers, + }, + reserveBeneficiary: zeroAddress, flags, } @@ -166,12 +172,17 @@ export function useLaunchProjectWithNftsTx() { }) const args = [ - owner, + _owner, deployTiered721HookData, //_deployTiered721HookData - launchProjectData, // _launchProjectData, + { + projectUri: launchProjectData[1], + rulesetConfigurations: launchProjectData[2], + terminalConfigurations: launchProjectData[3], + memo: launchProjectData[4], + }, // _launchProjectData, defaultJBController, - createSalt(), - ] + // createSalt(), + ] as const try { // SIMULATE TX: TODO update for nfts diff --git a/src/packages/v4/models/nfts.ts b/src/packages/v4/models/nfts.ts index cc0d30c1ce..f0fcdd7ec1 100644 --- a/src/packages/v4/models/nfts.ts +++ b/src/packages/v4/models/nfts.ts @@ -1,5 +1,4 @@ import { JBRulesetData, JBRulesetMetadata } from 'juice-sdk-core' - import { jb721TiersHookStoreAbi } from 'juice-sdk-react' import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' import { Address, ContractFunctionReturnType } from 'viem' @@ -8,23 +7,30 @@ import { FundAccessLimitGroup } from './fundAccessLimits' import { LaunchProjectJBTerminal } from './terminals' import { V4CurrencyOption } from './v4CurrencyOption' -type JB721TierParams = Omit< +/** + * @see https://github.com/Bananapus/nana-721-hook/blob/main/src/structs/JB721TierConfig.sol + */ +export type JB721TierConfig = Omit< ContractFunctionReturnType< typeof jb721TiersHookStoreAbi, 'view', 'tiersOf' >[0], - 'id' -> + 'id' | 'votingUnits' +> & { + useReserveBeneficiaryAsDefault: boolean + useVotingUnits: boolean + votingUnits: number +} type JB721InitTiersConfig = { - tiers: JB721TierParams[] + tiers: JB721TierConfig[] currency: number decimals: number prices: Address // JBPrices address } -type JB721TiersHookFlags = { +export type JB721TiersHookFlags = { noNewTiersWithReserves: boolean noNewTiersWithVotes: boolean noNewTiersWithOwnerMinting: boolean @@ -70,7 +76,7 @@ interface DeployTiered721DelegateData { collectionName: string collectionSymbol: string currency: V4CurrencyOption - tiers: JB721TierParams[] + tiers: JB721TierConfig[] flags: JB721TiersHookFlags } diff --git a/src/packages/v4/utils/launchProjectTransformers.ts b/src/packages/v4/utils/launchProjectTransformers.ts index f8a41d2b55..d5cfb39edb 100644 --- a/src/packages/v4/utils/launchProjectTransformers.ts +++ b/src/packages/v4/utils/launchProjectTransformers.ts @@ -123,10 +123,11 @@ export function transformFCMetadataToRulesetMetadata({ } type LaunchProjectJBSplit = Omit & { percent: number } + export type LaunchV4ProjectGroupedSplit = Omit< V4GroupedSplits, - 'splits' -> & { splits: LaunchProjectJBSplit[] } + 'splits' | 'groupId' +> & { splits: LaunchProjectJBSplit[], groupId: bigint } export function transformV2V3SplitsToV4({ v2v3Splits, @@ -135,7 +136,7 @@ export function transformV2V3SplitsToV4({ }): LaunchV4ProjectGroupedSplit[] { return v2v3Splits.map(group => ({ groupId: - group.group === SplitGroup.ETHPayout ? Number(BigInt(NATIVE_TOKEN)) : 1, // TODO dont hardcode reserved token group as 1n + group.group === SplitGroup.ETHPayout ? BigInt(NATIVE_TOKEN) : 1n, // TODO dont hardcode reserved token group as 1n splits: group.splits.map(split => ({ preferAddToBalance: Boolean(split.preferClaimed), percent: split.percent, diff --git a/src/redux/slices/editingV2Project/defaultState.ts b/src/redux/slices/editingV2Project/defaultState.ts index 3467dcf5ac..dacd6c6e16 100644 --- a/src/redux/slices/editingV2Project/defaultState.ts +++ b/src/redux/slices/editingV2Project/defaultState.ts @@ -22,6 +22,7 @@ import { serializeV2V3FundingCycleData, serializeV2V3FundingCycleMetadata, } from 'packages/v2v3/utils/serializers' +import { JB721TiersHookFlags } from 'packages/v4/models/nfts' import { projectDescriptionTemplate } from 'templates/create/projectDescriptionTemplate' import { CreateState, ProjectState } from './types' @@ -92,6 +93,14 @@ export const DEFAULT_NFT_FLAGS: JBTiered721Flags = { preventOverspending: false, } + +export const DEFAULT_NFT_FLAGS_V4: JB721TiersHookFlags = { + noNewTiersWithReserves: false, + noNewTiersWithVotes: false, + noNewTiersWithOwnerMinting: false, + preventOverspending: false, +} + const DEFAULT_PROJECT_METADATA_STATE: ProjectMetadata = { name: '', infoUri: '', diff --git a/src/utils/ipfs.ts b/src/utils/ipfs.ts index c8ea023760..f13a560715 100644 --- a/src/utils/ipfs.ts +++ b/src/utils/ipfs.ts @@ -62,8 +62,8 @@ export function pinataToGatewayUrl(url: string) { * * Hex-encoded CIDs are used to store some CIDs on-chain because they are more gas-efficient. */ -export function encodeIpfsUri(cid: string) { - return '0x' + Buffer.from(base58.decode(cid).slice(2)).toString('hex') +export function encodeIpfsUri(cid: string): `0x${string}` { + return `0x${Buffer.from(base58.decode(cid).slice(2)).toString('hex')}` } /**