diff --git a/src/components/strings.tsx b/src/components/strings.tsx index 36d781c32f..2d80c61a48 100644 --- a/src/components/strings.tsx +++ b/src/components/strings.tsx @@ -35,6 +35,21 @@ export const CYCLE_EXPLANATION = ( ) +export const RULESET_EXPLANATION = ( + +

With unlocked rulesets, you can edit your project's rules at any time.

+

+ With locked rulesets, you can lock your project's rules for a period of time + (like 3 minutes, 2 years, or 14 days), helping you build trust with your + supporters. +

+

+ This choice isn't permanent — you can switch between locked and unlocked + rulesets in the future. +

+
+) + export const LOCKED_PAYOUT_EXPLANATION = ( If locked, this payout can't be edited or removed until the lock expires or diff --git a/src/packages/v2v3/constants/juiceboxTokens.ts b/src/constants/juiceboxTokens.ts similarity index 100% rename from src/packages/v2v3/constants/juiceboxTokens.ts rename to src/constants/juiceboxTokens.ts diff --git a/src/locales/messages.pot b/src/locales/messages.pot index 1dfce92901..5f24b83532 100644 --- a/src/locales/messages.pot +++ b/src/locales/messages.pot @@ -179,6 +179,9 @@ msgstr "" msgid "Payout recipients:" msgstr "" +msgid "Your project's first ruleset will start on <0>{0} at {1}. Your project will be visible on <1>juicebox.money once you finish setting your project up, but supporters won't be able to pay or interact with it until the first ruleset begins." +msgstr "" + msgid "Lock until" msgstr "" @@ -437,6 +440,9 @@ msgstr "" msgid "Payer issuance rate" msgstr "" +msgid "None of your project's ETH can be paid out. All ETH will stay in your project for token redemptions or use in future rulesets." +msgstr "" + msgid "Project Details" msgstr "" @@ -542,6 +548,9 @@ msgstr "" msgid "This cycle has upcoming changes" msgstr "" +msgid "<0/> Your project's rules cannot be edited during the first ruleset." +msgstr "" + msgid "While enabled, the project owner can change the project's <0>payment terminals at any time." msgstr "" @@ -557,6 +566,9 @@ msgstr "" msgid "You would receive <0/>" msgstr "" +msgid "Simple token rules that will work for most projects. You can edit these rules in future rulesets." +msgstr "" + msgid "The issuance reduction rate is disabled if you are using unlocked cycles (because they have no duration)." msgstr "" @@ -833,6 +845,9 @@ msgstr "" msgid "Automated" msgstr "" +msgid "Ruleset" +msgstr "" + msgid "Back to settings" msgstr "" @@ -1046,6 +1061,9 @@ msgstr "" msgid "No overflow" msgstr "" +msgid "A fixed amount of ETH can be paid out from your project each ruleset. You can send specific ETH amounts (or ETH amounts based on USD values) to one or more recipients. Any remaining ETH will stay in your project for token redemptions or use in future rulesets." +msgstr "" + msgid "Export tokens CSV" msgstr "" @@ -1181,6 +1199,9 @@ msgstr "" msgid "No results" msgstr "" +msgid "The project's owner can edit the project's rules and start new rulesets at any time." +msgstr "" + msgid "ETH transfers to project" msgstr "" @@ -1202,6 +1223,9 @@ msgstr "" msgid "Fee from <0><1/>" msgstr "" +msgid "Reserved percent" +msgstr "" + msgid "Later" msgstr "" @@ -1214,6 +1238,9 @@ msgstr "" msgid "{receivedTokenSymbolText} Token" msgstr "" +msgid "Ruleset #1 starts when you create your project. With unlocked rulesets, you can edit your project's rules at any time. This gives you more flexibility, but may appear risky to supporters. Switching to locked rulesets will help you build supporter confidence." +msgstr "" + msgid "Issuance reduction rate:" msgstr "" @@ -1418,6 +1445,9 @@ msgstr "" msgid "<0>Juicebox is a <1>governance-minimal protocol. There are only a few levers that can be tuned, none of which impose changes for users without their consent. The Juicebox governance smart contract can adjust these levers.<2>The Juicebox protocol is governed by a community of JBX token holders who vote on proposals fortnightly.<3>Juicebox is on-chain and non-custodial. Project creators actually own their projects, and JuiceboxDAO has no way to access project's ETH or change their rules." msgstr "" +msgid "<0>With unlocked rulesets, you can edit your project's rules at any time.<1>With locked rulesets, you can lock your project's rules for a period of time (like 3 minutes, 2 years, or 14 days), helping you build trust with your supporters.<2>This choice isn't permanent — you can switch between locked and unlocked rulesets in the future." +msgstr "" + msgid "Pay {projectTitle}" msgstr "" @@ -1784,6 +1814,9 @@ msgstr "" msgid "Created project" msgstr "" +msgid "Next ruleset, the project will issue {0} tokens per 1 ETH. The ruleset after that, the project will issue {1} tokens per 1 ETH." +msgstr "" + msgid "An address is required" msgstr "" @@ -1802,6 +1835,9 @@ msgstr "" msgid "Project rules" msgstr "" +msgid "Set a duration for locked rulesets." +msgstr "" + msgid "Upload" msgstr "" @@ -2105,6 +2141,9 @@ msgstr "" msgid "Reset website" msgstr "" +msgid "Decay percent" +msgstr "" + msgid "Payout allocated to this project's {versionName} payment terminal. <0>Learn more." msgstr "" @@ -2420,6 +2459,9 @@ msgstr "" msgid "While enabled, the project owner can change the project's <0>controller at any time." msgstr "" +msgid "<0>With Locked Rulesets, your project's rules are locked for a period of time.<1><2>This helps build trust with your contributors." +msgstr "" + msgid "Ruleset #" msgstr "" @@ -2501,6 +2543,9 @@ msgstr "" msgid "Disclose any details to your contributors before they pay your project." msgstr "" +msgid "The issuance rate is reduced by this percentage every ruleset (every <0>{0}). The higher this rate, the more incentive to pay this project earlier." +msgstr "" + msgid "One or more reserved token recipients" msgstr "" @@ -2615,6 +2660,9 @@ msgstr "" msgid "Your edits will take effect in <0>cycle #{0}. The current cycle (#{currentFCNumber}) won't be altered." msgstr "" +msgid "Leave this blank to start your first ruleset immediately after you finish setting up your project." +msgstr "" + msgid "All of this project's ETH will be paid out. Token holders will receive <0>no ETH when redeeming their tokens." msgstr "" @@ -2642,6 +2690,9 @@ msgstr "" msgid "Give permissions to {0} on project #{projectId}" msgstr "" +msgid "Pay out ETH from your project to any Ethereum wallet or Juicebox project. ETH which <0>isn't paid out will be available for token redemptions, or for use in future rulesets. Payouts reset each ruleset." +msgstr "" + msgid "<0>Juicebox has had <1>multiple security audits, and has handled tens of thousands of ETH through its protocol.<2>However, Juicebox is still experimental software. Although the Juicebox contract team have done their part to shape the smart contracts for public use and have tested the code thoroughly, the risk of exploits is never 0%.<3>Due to their public nature, any exploits to the contracts may have irreversible consequences, including loss of ETH. Please use Juicebox with caution.<4><5>Learn more about the risks." msgstr "" @@ -2855,6 +2906,9 @@ msgstr "" msgid "Paying another Juicebox project may mint its tokens. Select an address to receive these tokens." msgstr "" +msgid "Set a future date & time to start your project's first ruleset." +msgstr "" + msgid "Get help planning or setting up my project." msgstr "" @@ -3071,6 +3125,9 @@ msgstr "" msgid "Connect wallet to deploy" msgstr "" +msgid "Unlocked Rulesets" +msgstr "" + msgid "Your wallet isn't allowed to process held fees." msgstr "" @@ -3086,6 +3143,9 @@ msgstr "" msgid "Check User Wallet Address" msgstr "" +msgid "Rulesets" +msgstr "" + msgid "Set ENS text record for {ensName}" msgstr "" @@ -3140,6 +3200,9 @@ msgstr "" msgid "Yes, start over" msgstr "" +msgid "Ruleset #1 starts when you create your project. With locked rulesets, if you edit your project's rules during Ruleset #1, those edits will be <0>queued for the next ruleset." +msgstr "" + msgid "All {tokensText} will go to the project owner:" msgstr "" @@ -3314,6 +3377,9 @@ msgstr "" msgid "{0} is not a valid integer" msgstr "" +msgid "Rulesets & Payouts" +msgstr "" + msgid "The unallocated portion of your total will go to the wallet that owns the project by default." msgstr "" @@ -3431,6 +3497,9 @@ msgstr "" msgid "No changes" msgstr "" +msgid "After {0} (your first ruleset), your project will not issue any tokens unless you edit the issuance rate." +msgstr "" + msgid "Project ENS name" msgstr "" @@ -3446,6 +3515,9 @@ msgstr "" msgid "After {0} (your first cycle), your project will not issue any tokens unless you edit the issuance rate." msgstr "" +msgid "In other words: instead of taking effect immediately, those edits will take effect when the next ruleset starts (Ruleset #2). If you need more flexibility, switch to unlocked rulesets." +msgstr "" + msgid "New NFTs will available on your project page shortly." msgstr "" @@ -3560,6 +3632,9 @@ msgstr "" msgid "Redeem {tokensLabel} for ETH" msgstr "" +msgid "Locked Rulesets" +msgstr "" + msgid "Made a mistake?" msgstr "" @@ -4223,6 +4298,9 @@ msgstr "" msgid "The maximum supply of this NFT in circulation." msgstr "" +msgid "Each ruleset, the project will issue {discountRate}% fewer tokens per ETH." +msgstr "" + msgid "Payout and reserved token recipients cannot exceed 100%" msgstr "" @@ -4301,6 +4379,9 @@ msgstr "" msgid "End" msgstr "" +msgid "The issuance reduction rate is disabled if you are using unlocked rulesets (because they have no duration)." +msgstr "" + msgid "Message sent!" msgstr "" diff --git a/src/packages/v2v3/components/Create/hooks/DeployProject/useDeployProject.ts b/src/packages/v2v3/components/Create/hooks/DeployProject/useDeployProject.ts index cb09e58ba1..fa363ac619 100644 --- a/src/packages/v2v3/components/Create/hooks/DeployProject/useDeployProject.ts +++ b/src/packages/v2v3/components/Create/hooks/DeployProject/useDeployProject.ts @@ -56,7 +56,7 @@ const getProjectIdFromLaunchReceipt = ( * Attempt to find the transaction receipt from a transaction hash. * Will retry up to 5 times with a 2 second delay between each attempt. If no - * receipt is found after 5 attempts, undefined is returned. + * receipt is not found after 5 attempts, undefined is returned. * * @param txHash transaction hash * @returns transaction receipt or undefined diff --git a/src/packages/v2v3/components/Create/hooks/useLoadInitialStateFromQuery.ts b/src/packages/v2v3/components/Create/hooks/useLoadInitialStateFromQuery.ts index ae6e947017..a2d3097086 100644 --- a/src/packages/v2v3/components/Create/hooks/useLoadInitialStateFromQuery.ts +++ b/src/packages/v2v3/components/Create/hooks/useLoadInitialStateFromQuery.ts @@ -1,10 +1,10 @@ +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import isEqual from 'lodash/isEqual' import { CreatePage } from 'models/createPage' import { ProjectTokensSelection } from 'models/projectTokenSelection' import { TreasurySelection } from 'models/treasurySelection' import { useRouter } from 'next/router' import { ballotStrategiesFn } from 'packages/v2v3/constants/ballotStrategies' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' import { useDefaultJBETHPaymentTerminal } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBETHPaymentTerminal' import { MAX_DISTRIBUTION_LIMIT } from 'packages/v2v3/utils/math' import { useEffect, useState } from 'react' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useInitialEditingData.ts b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useInitialEditingData.ts index e5a7cb07bc..d2a9075db0 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useInitialEditingData.ts +++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useInitialEditingData.ts @@ -1,9 +1,9 @@ +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { ETH_PAYOUT_SPLIT_GROUP, RESERVED_TOKEN_SPLIT_GROUP, } from 'constants/splits' import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' import { V2V3ContractsContext } from 'packages/v2v3/contexts/Contracts/V2V3ContractsContext' import { NftRewardsContext } from 'packages/v2v3/contexts/NftRewards/NftRewardsContext' import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/hooks/usePrepareSaveEditCycleData.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/hooks/usePrepareSaveEditCycleData.tsx index 54a83857fc..4153ce30b5 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/hooks/usePrepareSaveEditCycleData.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/hooks/usePrepareSaveEditCycleData.tsx @@ -1,5 +1,5 @@ import { BigNumber } from '@ethersproject/bignumber' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' import { V2V3FundAccessConstraint, diff --git a/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts b/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts index c28f7ef2e0..cbbeba406d 100644 --- a/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts +++ b/src/packages/v2v3/hooks/JB721Delegate/transactor/useLaunchProjectWithNftsTx.ts @@ -20,7 +20,7 @@ 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 { LaunchProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' +import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' import { useV2ProjectTitle } from 'packages/v2v3/hooks/useProjectTitle' import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption' import { @@ -61,7 +61,7 @@ interface JB721DelegateLaunchFundingCycleData { interface LaunchProjectWithNftsTxArgs { tiered721DelegateData: DeployTiered721DelegateData - projectData: LaunchProjectData + projectData: LaunchV2V3ProjectData } type JB721DelegateLaunchProjectData = JB721DelegateLaunchFundingCycleData & { diff --git a/src/packages/v2v3/hooks/contractReader/useProjectDistributionLimit.ts b/src/packages/v2v3/hooks/contractReader/useProjectDistributionLimit.ts index 8654993b24..2bba1d8a25 100644 --- a/src/packages/v2v3/hooks/contractReader/useProjectDistributionLimit.ts +++ b/src/packages/v2v3/hooks/contractReader/useProjectDistributionLimit.ts @@ -1,5 +1,5 @@ +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { BigNumber } from 'ethers' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' import { V2V3ContractName } from 'packages/v2v3/models/contracts' import { useContext } from 'react' diff --git a/src/packages/v2v3/hooks/contractReader/useProjectPrimaryEthTerminalAddress.ts b/src/packages/v2v3/hooks/contractReader/useProjectPrimaryEthTerminalAddress.ts index 000b2d67e9..93f34c7b29 100644 --- a/src/packages/v2v3/hooks/contractReader/useProjectPrimaryEthTerminalAddress.ts +++ b/src/packages/v2v3/hooks/contractReader/useProjectPrimaryEthTerminalAddress.ts @@ -1,4 +1,4 @@ -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { V2V3ContractName } from 'packages/v2v3/models/contracts' import useV2ContractReader from './useV2ContractReader' diff --git a/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3.ts b/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3.ts index 9fd936041a..7b597cf2ca 100644 --- a/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3.ts +++ b/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3.ts @@ -1,6 +1,6 @@ +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { DEFAULT_MEMO, DEFAULT_METADATA } from 'constants/transactionDefaults' import { BigNumber } from 'ethers' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' export function getAddToBalanceArgsV3({ projectId, diff --git a/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3_1.ts b/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3_1.ts index 06f2c558c8..bf643fad7f 100644 --- a/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3_1.ts +++ b/src/packages/v2v3/hooks/transactor/AddToBalanceTx/useAddToBalanceArgsV3_1.ts @@ -1,6 +1,6 @@ +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { DEFAULT_MEMO, DEFAULT_METADATA } from 'constants/transactionDefaults' import { BigNumber } from 'ethers' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' export function getAddToBalanceArgsV3_1({ projectId, diff --git a/src/packages/v2v3/hooks/transactor/useDistributePayouts.ts b/src/packages/v2v3/hooks/transactor/useDistributePayouts.ts index 1c299b93e1..250fac7d28 100644 --- a/src/packages/v2v3/hooks/transactor/useDistributePayouts.ts +++ b/src/packages/v2v3/hooks/transactor/useDistributePayouts.ts @@ -1,4 +1,5 @@ import { t } from '@lingui/macro' +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { DEFAULT_MEMO, DEFAULT_METADATA, @@ -8,7 +9,6 @@ import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext' import { TransactionContext } from 'contexts/Transaction/TransactionContext' import { BigNumber } from 'ethers' import { TransactorInstance } from 'hooks/useTransactor' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' import { PaymentTerminalVersion, diff --git a/src/packages/v2v3/hooks/transactor/useLaunchProjectTx.ts b/src/packages/v2v3/hooks/transactor/useLaunchProjectTx.ts index 4a427fc026..83384ddcf6 100644 --- a/src/packages/v2v3/hooks/transactor/useLaunchProjectTx.ts +++ b/src/packages/v2v3/hooks/transactor/useLaunchProjectTx.ts @@ -21,7 +21,7 @@ import { useContext } from 'react' import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' import { useV2ProjectTitle } from '../useProjectTitle' -export interface LaunchProjectData { +export interface LaunchV2V3ProjectData { projectMetadataCID: string fundingCycleData: V2V3FundingCycleData fundingCycleMetadata: V2V3FundingCycleMetadata @@ -31,7 +31,7 @@ export interface LaunchProjectData { owner?: string // If not provided, the current user's address will be used. } -export function useLaunchProjectTx(): TransactorInstance { +export function useLaunchProjectTx(): TransactorInstance { const { transactor } = useContext(TransactionContext) const { contracts } = useContext(V2V3ContractsContext) const defaultJBController = useDefaultJBController() diff --git a/src/packages/v2v3/hooks/transactor/usePayETHPaymentTerminalTx.ts b/src/packages/v2v3/hooks/transactor/usePayETHPaymentTerminalTx.ts index e31c3b08b7..94ac58f052 100644 --- a/src/packages/v2v3/hooks/transactor/usePayETHPaymentTerminalTx.ts +++ b/src/packages/v2v3/hooks/transactor/usePayETHPaymentTerminalTx.ts @@ -1,11 +1,11 @@ import { t } from '@lingui/macro' +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { DEFAULT_MIN_RETURNED_TOKENS } from 'constants/transactionDefaults' import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext' import { TransactionContext } from 'contexts/Transaction/TransactionContext' import { BigNumber } from 'ethers' import { TransactorInstance } from 'hooks/useTransactor' import { useProjectIsOFACListed } from 'packages/v2v3/components/V2V3Project/ProjectDashboard/hooks/useProjectIsOFACListed' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' import { V2V3ProjectContractsContext } from 'packages/v2v3/contexts/ProjectContracts/V2V3ProjectContractsContext' import { useContext } from 'react' import { useV2V3BlockedProject } from '../useBlockedProject' diff --git a/src/packages/v2v3/hooks/transactor/useReconfigureV2V3FundingCycleTx.ts b/src/packages/v2v3/hooks/transactor/useReconfigureV2V3FundingCycleTx.ts index 53f1cec29c..ca4b0c94ac 100644 --- a/src/packages/v2v3/hooks/transactor/useReconfigureV2V3FundingCycleTx.ts +++ b/src/packages/v2v3/hooks/transactor/useReconfigureV2V3FundingCycleTx.ts @@ -8,10 +8,10 @@ import { isValidMustStartAtOrAfter } from 'packages/v2v3/utils/fundingCycle' import { useContext } from 'react' import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' import { useV2ProjectTitle } from '../useProjectTitle' -import { LaunchProjectData } from './useLaunchProjectTx' +import { LaunchV2V3ProjectData } from './useLaunchProjectTx' export type ReconfigureFundingCycleTxParams = Omit< - LaunchProjectData, + LaunchV2V3ProjectData, 'projectMetadataCID' > & { memo?: string diff --git a/src/packages/v4/components/Create/Create.tsx b/src/packages/v4/components/Create/Create.tsx index 6e67c424ec..2657e4b7c6 100644 --- a/src/packages/v4/components/Create/Create.tsx +++ b/src/packages/v4/components/Create/Create.tsx @@ -1,15 +1,14 @@ import { t, Trans } from '@lingui/macro' import { DeployButtonText } from 'components/buttons/DeployProjectButtonText' +import Loading from 'components/Loading' import { - CYCLE_EXPLANATION, RECONFIG_RULES_EXPLANATION, + RULESET_EXPLANATION, } from 'components/strings' 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' @@ -24,7 +23,7 @@ export function Create() { const deployedProjectId = router.query.deployedProjectId as string const initialStateLoading = useLoadingInitialStateFromQuery() - if (initialStateLoading) return <>XX// + if (initialStateLoading) return if (deployedProjectId) { const projectId = parseInt(deployedProjectId) @@ -48,8 +47,8 @@ export function Create() { @@ -60,8 +59,8 @@ export function Create() { Pay out ETH from your project to any Ethereum wallet or Juicebox project. ETH which isn't paid out will be available for - token redemptions, or for use in future cycles. Payouts reset - each cycle. + token redemptions, or for use in future rulesets. Payouts reset + each ruleset. } > @@ -82,7 +81,7 @@ export function Create() { > - @@ -95,7 +94,7 @@ export function Create() { } > - + */} Edit Deadline} diff --git a/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts b/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts index 1947d1ff9c..2c58a52f47 100644 --- a/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts +++ b/src/packages/v4/components/Create/components/Wizard/hooks/useSteps.ts @@ -8,10 +8,10 @@ import { WizardContext } from '../contexts/WizardContext' const stepNames = (): Record => { return { projectDetails: t`Details`, - fundingCycles: t`Cycles`, + fundingCycles: t`Rulesets`, payouts: t`Payouts`, projectToken: t`Token`, - nftRewards: t`NFTs`, + // nftRewards: t`NFTs`, reconfigurationRules: t`Deadline`, reviewDeploy: t`Deploy`, } diff --git a/src/packages/v4/components/Create/components/pages/FundingCycles/FundingCyclesPage.tsx b/src/packages/v4/components/Create/components/pages/FundingCycles/FundingCyclesPage.tsx index 7caef86242..0bda8587e0 100644 --- a/src/packages/v4/components/Create/components/pages/FundingCycles/FundingCyclesPage.tsx +++ b/src/packages/v4/components/Create/components/pages/FundingCycles/FundingCyclesPage.tsx @@ -41,16 +41,16 @@ const FundingCycleCallout: React.FC> = () => {

- Cycle #1 starts when you create your project. With locked cycles, - if you edit your project's rules during Cycle #1, those edits will - be queued for the next cycle. + Ruleset #1 starts when you create your project. With locked rulesets, + if you edit your project's rules during Ruleset #1, those edits will + be queued for the next ruleset.

In other words: instead of taking effect immediately, those edits - will take effect when the next cycle starts (Cycle #2). If you - need more flexibility, switch to unlocked cycles. + will take effect when the next ruleset starts (Ruleset #2). If you + need more flexibility, switch to unlocked rulesets.

@@ -59,10 +59,10 @@ const FundingCycleCallout: React.FC> = () => { return ( - Cycle #1 starts when you create your project. With unlocked cycles, + Ruleset #1 starts when you create your project. With unlocked rulesets, you can edit your project's rules at any time. This gives you more flexibility, but may appear risky to supporters. Switching to locked - cycles will help you build supporter confidence. + rulesets will help you build supporter confidence. ) @@ -117,13 +117,13 @@ export const FundingCyclesPage = () => { name="automated" title={
- Locked Cycles{' '} + Locked Rulesets{' '}

- With Locked Cycles, your project's rules are + With Locked Rulesets, your project's rules are locked for a period of time.

@@ -137,20 +137,20 @@ export const FundingCyclesPage = () => { />

} - description={t`Set a duration for locked cycles.`} + description={t`Set a duration for locked rulesets.`} icon={} > Your project's rules cannot be - edited during the first cycle. + edited during the first ruleset. } rules={lockPageRulesWrapper([ - durationMustExistRule({ label: t`Cycle duration` }), + durationMustExistRule({ label: t`Ruleset duration` }), ])} > @@ -158,8 +158,8 @@ export const FundingCyclesPage = () => { } /> @@ -183,14 +183,14 @@ export const FundingCyclesPage = () => { Set a future date & time to start your project's first - cycle. + ruleset. } extra={ launchDate ? ( - Your project's first cycle will start on{' '} + Your project's first ruleset will start on{' '} { . Your project will be visible on{' '} juicebox.money once you finish setting your project up, but supporters won't be able to - pay or interact with it until the first cycle begins. + pay or interact with it until the first ruleset begins. ) : ( - Leave this blank to start your first cycle immediately + Leave this blank to start your first ruleset immediately after you finish setting up your project. ) diff --git a/src/packages/v4/components/Create/components/pages/FundingCycles/hooks/useFundingCyclesForm.ts b/src/packages/v4/components/Create/components/pages/FundingCycles/hooks/useFundingCyclesForm.ts index 31f668ec79..50593d7694 100644 --- a/src/packages/v4/components/Create/components/pages/FundingCycles/hooks/useFundingCyclesForm.ts +++ b/src/packages/v4/components/Create/components/pages/FundingCycles/hooks/useFundingCyclesForm.ts @@ -35,7 +35,7 @@ export const useFundingCyclesForm = () => { : undefined if (!fundingCycleData.duration?.length || selection !== 'automated') { - // Return default values if the user hasn't selected a funding cycle type yet. + // Return default values if the user hasn't selected a funding ruleset type yet. return { duration: { duration: 14, unit: 'days' }, selection, launchDate } } diff --git a/src/packages/v4/components/Create/components/pages/PayoutsPage/components/TreasuryOptionsRadio.tsx b/src/packages/v4/components/Create/components/pages/PayoutsPage/components/TreasuryOptionsRadio.tsx index 0389eae6df..b595c6d095 100644 --- a/src/packages/v4/components/Create/components/pages/PayoutsPage/components/TreasuryOptionsRadio.tsx +++ b/src/packages/v4/components/Create/components/pages/PayoutsPage/components/TreasuryOptionsRadio.tsx @@ -45,11 +45,11 @@ export function TreasuryOptionsRadio() { const calloutText = useMemo(() => { switch (treasuryOption) { case 'amount': - return t`A fixed amount of ETH can be paid out from your project each cycle. You can send specific ETH amounts (or ETH amounts based on USD values) to one or more recipients. Any remaining ETH will stay in your project for token redemptions or use in future cycles.` + return t`A fixed amount of ETH can be paid out from your project each ruleset. You can send specific ETH amounts (or ETH amounts based on USD values) to one or more recipients. Any remaining ETH will stay in your project for token redemptions or use in future rulesets.` case 'unlimited': return t`All of your project's ETH can be paid out at any time. You can send percentages of that ETH to one or more recipients.` case 'zero': - return t`None of your project's ETH can be paid out. All ETH will stay in your project for token redemptions or use in future cycles.` + return t`None of your project's ETH can be paid out. All ETH will stay in your project for token redemptions or use in future rulesets.` } }, [treasuryOption]) diff --git a/src/packages/v4/components/Create/components/pages/ProjectToken/ProjectTokenPage.tsx b/src/packages/v4/components/Create/components/pages/ProjectToken/ProjectTokenPage.tsx index f28ba94cac..1201c3c2eb 100644 --- a/src/packages/v4/components/Create/components/pages/ProjectToken/ProjectTokenPage.tsx +++ b/src/packages/v4/components/Create/components/pages/ProjectToken/ProjectTokenPage.tsx @@ -73,7 +73,7 @@ export const ProjectTokenPage: React.FC< description={ Simple token rules that will work for most projects. You can - edit these rules in future cycles. + edit these rules in future rulesets. } > diff --git a/src/packages/v4/components/Create/components/pages/ProjectToken/components/CustomTokenSettings/CustomTokenSettings.tsx b/src/packages/v4/components/Create/components/pages/ProjectToken/components/CustomTokenSettings/CustomTokenSettings.tsx index 1a245d56ef..c3489e99c5 100644 --- a/src/packages/v4/components/Create/components/pages/ProjectToken/components/CustomTokenSettings/CustomTokenSettings.tsx +++ b/src/packages/v4/components/Create/components/pages/ProjectToken/components/CustomTokenSettings/CustomTokenSettings.tsx @@ -88,7 +88,7 @@ export const CustomTokenSettings = () => { - +
Set aside a percentage of token issuance for the wallets and @@ -128,13 +128,15 @@ export const CustomTokenSettings = () => { - +
- - The issuance rate is reduced by this percentage every cycle (every{' '} - {formatFundingCycleDuration(duration)}). The higher this rate, the - more incentive to pay this project earlier. - + + + The issuance rate is reduced by this percentage every ruleset (every{' '} + {formatFundingCycleDuration(duration)}). The higher this rate, the + more incentive to pay this project earlier. + + { The issuance reduction rate is disabled if you are using - unlocked cycles (because they have no duration). + unlocked rulesets (because they have no duration). ) : ( @@ -168,22 +170,22 @@ export const CustomTokenSettings = () => { ) : discountRate === 100 ? ( After {formatFundingCycleDuration(duration)} (your first - cycle), your project will not issue any tokens unless you edit + ruleset), your project will not issue any tokens unless you edit the issuance rate. ) : ( <>

- Each cycle, the project will issue {discountRate}% fewer + Each ruleset, the project will issue {discountRate}% fewer tokens per ETH.{' '}

- Next cycle, the project will issue{' '} + Next ruleset, the project will issue{' '} {formatAmount(secondFundingCycleMintRate)} tokens per 1 - ETH. The cycle after that, the project will issue{' '} + ETH. The ruleset after that, the project will issue{' '} {formatAmount(thirdFundingCycleMintRate)} tokens per 1 ETH. diff --git a/src/packages/v4/components/Create/components/pages/ProjectToken/components/DefaultSettings.tsx b/src/packages/v4/components/Create/components/pages/ProjectToken/components/DefaultSettings.tsx index ac3008a5e6..9aafd3154e 100644 --- a/src/packages/v4/components/Create/components/pages/ProjectToken/components/DefaultSettings.tsx +++ b/src/packages/v4/components/Create/components/pages/ProjectToken/components/DefaultSettings.tsx @@ -24,11 +24,11 @@ export const DefaultSettings: React.FC< )} tokens / ETH`, tooltip: MINT_RATE_EXPLANATION, }, - [t`Reserved rate`]: { + [t`Reserved percent`]: { data: `${ProjectTokenForm.DefaultSettings.reservedTokensPercentage}%`, tooltip: RESERVED_RATE_EXPLANATION, }, - [t`Issuance reduction rate`]: { + [t`Decay percent`]: { data: `${ProjectTokenForm.DefaultSettings.discountRate}%`, tooltip: DISCOUNT_RATE_EXPLANATION, }, diff --git a/src/packages/v4/components/Create/components/pages/ReconfigurationRules/ReconfigurationRulesPage.tsx b/src/packages/v4/components/Create/components/pages/ReconfigurationRules/ReconfigurationRulesPage.tsx index bf24fbeb3a..2ca930ce45 100644 --- a/src/packages/v4/components/Create/components/pages/ReconfigurationRules/ReconfigurationRulesPage.tsx +++ b/src/packages/v4/components/Create/components/pages/ReconfigurationRules/ReconfigurationRulesPage.tsx @@ -5,12 +5,11 @@ import { Callout } from 'components/Callout/Callout' import { JuiceSwitch } from 'components/inputs/JuiceSwitch' import { CONTROLLER_CONFIG_EXPLANATION, - CONTROLLER_MIGRATION_EXPLANATION, HOLD_FEES_EXPLANATION, PAUSE_PAYMENTS_EXPLANATION, RECONFIG_RULES_WARN, TERMINAL_CONFIG_EXPLANATION, - TERMINAL_MIGRATION_EXPLANATION, + TERMINAL_MIGRATION_EXPLANATION } from 'components/strings' import { CREATE_FLOW } from 'constants/fathomEvents' import { FEATURE_FLAGS } from 'constants/featureFlags' @@ -125,12 +124,6 @@ export const ReconfigurationRulesPage = () => { > - - -

diff --git a/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx b/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx index 12b411967e..17d5d5554e 100644 --- a/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx +++ b/src/packages/v4/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx @@ -10,13 +10,13 @@ import { emitConfirmationDeletionModal } from 'hooks/emitConfirmationDeletionMod import useMobile from 'hooks/useMobile' import { useModal } from 'hooks/useModal' import { useRouter } from 'next/router' -import { useDeployProject } from 'packages/v2v3/components/Create/hooks/DeployProject/useDeployProject' import { useCallback, useContext, useEffect, useMemo, useState } from 'react' import { useDispatch } from 'react-redux' import { useAppSelector } from 'redux/hooks/useAppSelector' import { useSetCreateFurthestPageReached } from 'redux/hooks/useEditingCreateFurthestPageReached' import { editingV2ProjectActions } from 'redux/slices/editingV2Project' import { helpPagePath } from 'utils/helpPagePath' +import { useDeployProject } from '../../../hooks/DeployProject/useDeployProject' import { CreateBadge } from '../../CreateBadge' import { CreateCollapse } from '../../CreateCollapse/CreateCollapse' import { Wizard } from '../../Wizard/Wizard' @@ -24,7 +24,6 @@ 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 { @@ -93,7 +92,7 @@ export const ReviewDeployPage = () => { transactionModal.open() await deployProject({ - onProjectDeployed: deployedProjectId => { + onProjectDeployed: (deployedProjectId: number) => { router.push({ query: { deployedProjectId } }, '/create', { shallow: true, }) @@ -149,7 +148,7 @@ export const ReviewDeployPage = () => { key={ReviewDeployKey.FundingConfiguration} header={
- Cycles & Payouts + Rulesets & Payouts
} > @@ -165,7 +164,7 @@ export const ReviewDeployPage = () => { > - { } > - + */} { console.info('Deploy: SUCCESS', projectId) const router = useRouter() const { chain } = useWallet() + const chainId = useChainId() let deployGreeting = t`Your project was successfully created!` if (chain?.name) { deployGreeting = t`Your project was successfully created on ${chain.name}!` } + const projectRoute = v4ProjectRoute({ projectId, chainId }) + const [gotoProjectClicked, setGotoProjectClicked] = useState(false) /** @@ -45,10 +50,10 @@ export const DeploySuccess = ({ projectId }: { projectId: number }) => { const handleGoToProject = useCallback(() => { setGotoProjectClicked(true) router.push( - `/v2/p/${projectId}?${NEW_DEPLOY_QUERY_PARAM}=1`, - `/v2/p/${projectId}`, + `${projectRoute}?${NEW_DEPLOY_QUERY_PARAM}=1`, + projectRoute, ) - }, [projectId, router]) + }, [router, projectRoute]) return (
diff --git a/src/packages/v4/components/Create/components/pages/ReviewDeploy/components/FundingConfigurationReview/FundingConfigurationReview.tsx b/src/packages/v4/components/Create/components/pages/ReviewDeploy/components/FundingConfigurationReview/FundingConfigurationReview.tsx index b3ed62e039..40a6578c03 100644 --- a/src/packages/v4/components/Create/components/pages/ReviewDeploy/components/FundingConfigurationReview/FundingConfigurationReview.tsx +++ b/src/packages/v4/components/Create/components/pages/ReviewDeploy/components/FundingConfigurationReview/FundingConfigurationReview.tsx @@ -12,7 +12,7 @@ export const FundingConfigurationReview = () => { <>
{fundingCycles}
} /> { } /> {formatReservedRate( @@ -60,7 +60,7 @@ export const ProjectTokenReview = () => { } /> {formatDiscountRate( diff --git a/src/packages/v4/components/Create/hooks/DeployProject/hooks/useDeployStandardProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/hooks/useDeployStandardProject.ts index a3dc8d3be4..2ec6c7a771 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/hooks/useDeployStandardProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/hooks/useDeployStandardProject.ts @@ -1,5 +1,4 @@ -import { TransactionCallbacks } from 'models/transaction' -import { useLaunchProjectTx } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' +import { LaunchTxOpts, useLaunchProjectTx } from 'packages/v4/hooks/useLaunchProjectTx' import { useCallback } from 'react' import { useAppSelector, @@ -9,14 +8,13 @@ import { } from 'redux/hooks/useAppSelector' /** - * Hook that returns a function that deploys a project. + * Hook that returns a function that deploys a v4 project. * - * The distinction is made between standard and NFT projects because the NFT - * project contract uses more gas. + * Takes data from the redux store built for v2v3 projects, data is converted to v4 format in useLaunchProjectTx. * @returns A function that deploys a project. */ export const useDeployStandardProject = () => { - const launchProject = useLaunchProjectTx() + const launchProjectTx = useLaunchProjectTx() const { payoutGroupedSplits, reservedTokensGroupedSplits, @@ -30,18 +28,14 @@ export const useDeployStandardProject = () => { const deployStandardProjectCallback = useCallback( async ({ metadataCid, - - onDone, - onConfirmed, - onCancelled, + onTransactionPending, + onTransactionConfirmed, + onTransactionError }: { metadataCid: string - } & Pick< - TransactionCallbacks, - 'onCancelled' | 'onConfirmed' | 'onDone' - >) => { + } & LaunchTxOpts) => { const groupedSplits = [payoutGroupedSplits, reservedTokensGroupedSplits] - return await launchProject( + return await launchProjectTx( { owner: inputProjectOwner?.length ? inputProjectOwner : undefined, projectMetadataCID: metadataCid, @@ -52,16 +46,16 @@ export const useDeployStandardProject = () => { groupedSplits, }, { - onDone, - onConfirmed, - onCancelled, + onTransactionPending, + onTransactionConfirmed, + onTransactionError }, ) }, [ payoutGroupedSplits, reservedTokensGroupedSplits, - launchProject, + launchProjectTx, inputProjectOwner, fundingCycleData, fundingCycleMetadata, diff --git a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts index cb09e58ba1..a0de85a48a 100644 --- a/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts +++ b/src/packages/v4/components/Create/hooks/DeployProject/useDeployProject.ts @@ -1,7 +1,5 @@ -import { readProvider } from 'constants/readProvider' -import { BigNumber, providers } from 'ethers' import { uploadProjectMetadata } from 'lib/api/ipfs' -import { TransactionCallbacks } from 'models/transaction' +import { LaunchTxOpts } from 'packages/v4/hooks/useLaunchProjectTx' import { useCallback, useState } from 'react' import { useAppDispatch } from 'redux/hooks/useAppDispatch' import { @@ -12,70 +10,9 @@ import { } from 'redux/hooks/useAppSelector' 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 CREATE_EVENT_IDX = 2 -const NFT_CREATE_EVENT_IDX = 3 -const PROJECT_ID_TOPIC_IDX = 1 - const JUICEBOX_DOMAIN = 'juicebox' -const JUICECROWD_DOMAIN = 'juicecrowd' - -/** - * Return the project ID created from a `launchProjectFor` transaction. - * @param txReceipt receipt of `launchProjectFor` transaction - */ -const getProjectIdFromNftLaunchReceipt = ( - txReceipt: providers.TransactionReceipt, -): number => { - const projectIdHex: unknown | undefined = - txReceipt?.logs[NFT_CREATE_EVENT_IDX]?.topics?.[PROJECT_ID_TOPIC_IDX] - const projectId = BigNumber.from(projectIdHex).toNumber() - - return projectId -} - -/** - * Return the project ID created from a `launchProjectFor` transaction. - * @param txReceipt receipt of `launchProjectFor` transaction - */ -const getProjectIdFromLaunchReceipt = ( - txReceipt: providers.TransactionReceipt, -): number => { - const projectIdHex: unknown | undefined = - txReceipt?.logs[CREATE_EVENT_IDX]?.topics?.[PROJECT_ID_TOPIC_IDX] - const projectId = BigNumber.from(projectIdHex).toNumber() - - return projectId -} - -/** - * Attempt to find the transaction receipt from a transaction hash. - - * Will retry up to 5 times with a 2 second delay between each attempt. If no - * receipt is found after 5 attempts, undefined is returned. - * - * @param txHash transaction hash - * @returns transaction receipt or undefined - */ -const findTransactionReceipt = async (txHash: string) => { - let retries = 5 - let receipt - while (retries > 0 && !receipt) { - receipt = await readProvider.getTransactionReceipt(txHash) - if (receipt) break - - retries -= 1 - // wait 2s - await new Promise(r => setTimeout(r, 2000)) - console.info('Retrying tx receipt lookup...') - } - - return receipt -} /** * Hook that returns a function that deploys a project. @@ -85,9 +22,9 @@ export const useDeployProject = () => { const [isDeploying, setIsDeploying] = useState(false) const [transactionPending, setTransactionPending] = useState() - const isNftProject = useIsNftProject() - const uploadNftRewards = useUploadNftRewards() - const deployNftProject = useDeployNftProject() + // const isNftProject = useIsNftProject() + // const uploadNftRewards = useUploadNftRewards() + // const deployNftProject = useDeployNftProject() const deployStandardProject = useDeployStandardProject() @@ -111,46 +48,22 @@ export const useDeployProject = () => { const operationCallbacks = useCallback( ( onProjectDeployed?: (projectId: number) => void, - ): Pick< - TransactionCallbacks, - 'onCancelled' | 'onConfirmed' | 'onDone' | 'onError' - > => ({ - onDone: () => { + ): LaunchTxOpts => ({ + onTransactionPending: () => { console.info('Project transaction executed. Await confirmation...') setTransactionPending(true) }, - onConfirmed: async result => { - const hash = result?.hash - if (!hash) { - return // TODO error notification - } - const txReceipt = await findTransactionReceipt(hash) - if (!txReceipt) { - return // TODO error notification - } - - const projectId = isNftProject - ? getProjectIdFromNftLaunchReceipt(txReceipt) - : getProjectIdFromLaunchReceipt(txReceipt) - - if (projectId === undefined) { - return // TODO error notification - } - + onTransactionConfirmed: async (hash, projectId) => { // Reset the project state dispatch(editingV2ProjectActions.resetState()) onProjectDeployed?.(projectId) }, - onError: error => { + onTransactionError: error => { console.error(error) emitErrorNotification(`Error deploying project: ${error}`) }, - onCancelled: () => { - setIsDeploying(false) - setTransactionPending(false) - }, }), - [dispatch, isNftProject], + [dispatch], ) /** @@ -176,15 +89,15 @@ export const useDeployProject = () => { setIsDeploying(false) throw new Error('Error deploying project.') } - let nftCids: Awaited> | undefined - try { - if (isNftProject) { - nftCids = await uploadNftRewards() - } - } catch (error) { - handleDeployFailure(error) - return - } + // let nftCids: Awaited> | undefined + // try { + // if (isNftProject) { + // nftCids = await uploadNftRewards() + // } + // } catch (error) { + // handleDeployFailure(error) + // return + // } let softTargetAmount: string | undefined let softTargetCurrency: string | undefined @@ -207,42 +120,42 @@ export const useDeployProject = () => { } try { - 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 - } + // 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) + return + // } } catch (error) { handleDeployFailure(error) return } }, [ - deployNftProject, + // deployNftProject, deployStandardProject, fundAccessConstraints, fundingCycleData, fundingCycleMetadata, handleDeployFailure, - isNftProject, + // isNftProject, operationCallbacks, postPayModal, projectMetadata, - uploadNftRewards, + // uploadNftRewards, ], ) return { diff --git a/src/packages/v4/components/Create/hooks/useLoadInitialStateFromQuery.ts b/src/packages/v4/components/Create/hooks/useLoadInitialStateFromQuery.ts index 1b917f4b2d..c437b7a4fd 100644 --- a/src/packages/v4/components/Create/hooks/useLoadInitialStateFromQuery.ts +++ b/src/packages/v4/components/Create/hooks/useLoadInitialStateFromQuery.ts @@ -1,3 +1,4 @@ +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { useJBContractContext } from 'juice-sdk-react' import isEqual from 'lodash/isEqual' import { CreatePage } from 'models/createPage' @@ -5,7 +6,6 @@ import { ProjectTokensSelection } from 'models/projectTokenSelection' import { TreasurySelection } from 'models/treasurySelection' import { useRouter } from 'next/router' import { ballotStrategiesFn } from 'packages/v2v3/constants/ballotStrategies' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' import { MAX_DISTRIBUTION_LIMIT } from 'packages/v2v3/utils/math' import { useEffect, useState } from 'react' import { useDispatch } from 'react-redux' diff --git a/src/packages/v4/hooks/useLaunchProjectTx.ts b/src/packages/v4/hooks/useLaunchProjectTx.ts new file mode 100644 index 0000000000..703340c40c --- /dev/null +++ b/src/packages/v4/hooks/useLaunchProjectTx.ts @@ -0,0 +1,140 @@ +import { waitForTransactionReceipt } from '@wagmi/core' +import { JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN } from 'constants/metadataDomain' +import { DEFAULT_MEMO } from 'constants/transactionDefaults' +import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext' +import { useWallet } from 'hooks/Wallet' +import { NATIVE_TOKEN } from 'juice-sdk-core' +import { useJBContractContext, useWriteJbControllerLaunchProjectFor } from 'juice-sdk-react' +import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchProjectTx' +import { useCallback, useContext } from 'react' +import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project' +import { WaitForTransactionReceiptReturnType } from 'viem' +import { LaunchV2V3ProjectArgs, transformV2V3CreateArgsToV4 } from '../utils/launchProject' +import { wagmiConfig } from '../wagmiConfig' + +const CREATE_EVENT_IDX = 2 +const PROJECT_ID_TOPIC_IDX = 1 +const HEX_BASE = 16 + +export interface LaunchTxOpts { + onTransactionPending: (hash: `0x${string}`) => void + onTransactionConfirmed: (hash: `0x${string}`, projectId: number) => void + onTransactionError: (error: Error) => void +} + +/** + * Return the project ID created from a `launchProjectFor` transaction. + * @param txReceipt receipt of `launchProjectFor` transaction + */ +const getProjectIdFromLaunchReceipt = ( + txReceipt: WaitForTransactionReceiptReturnType, +): number => { + const projectIdHex: string | undefined = + txReceipt?.logs[CREATE_EVENT_IDX]?.topics?.[PROJECT_ID_TOPIC_IDX] + if (!projectIdHex) return 0 + + const projectId = parseInt(projectIdHex, HEX_BASE); + return projectId +} + +/** + * Takes data in V2V3 format, converts it to v4 format and passes it to `writeLaunchProject` + * @returns A function that deploys a project. + */ +export function useLaunchProjectTx() { + const { writeContractAsync: writeLaunchProject } = useWriteJbControllerLaunchProjectFor() + const { contracts } = useJBContractContext() + + const { addTransaction } = useContext(TxHistoryContext) + + const { userAddress } = useWallet() + + return useCallback( + async ({ + owner, + projectMetadataCID, + fundingCycleData, + fundingCycleMetadata, + fundAccessConstraints, + groupedSplits = [], + mustStartAtOrAfter = DEFAULT_MUST_START_AT_OR_AFTER, + }: LaunchV2V3ProjectData, + { + onTransactionPending: onTransactionPendingCallback, + onTransactionConfirmed: onTransactionConfirmedCallback, + onTransactionError: onTransactionErrorCallback, + }: LaunchTxOpts + ) => { + if ( + !contracts.controller.data || + !contracts.primaryNativeTerminal.data || + !userAddress + ) { + return + } + + const _owner = owner && owner.length ? owner : userAddress + + const v2v3Args = [ + _owner, + [projectMetadataCID, JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN], + fundingCycleData, + fundingCycleMetadata, + mustStartAtOrAfter, + groupedSplits, + fundAccessConstraints, + [contracts.primaryNativeTerminal.data], // _terminals, just supporting single for now + // Eventually should be something like: + // getTerminalsFromFundAccessConstraints( + // fundAccessConstraints, + // contracts.primaryNativeTerminal.data, + // ), + DEFAULT_MEMO, + ] as LaunchV2V3ProjectArgs + + const args = transformV2V3CreateArgsToV4({ + v2v3Args, + primaryNativeTerminal: contracts.primaryNativeTerminal.data, + tokenAddress: NATIVE_TOKEN + }) + + try { + // SIMULATE TX: + // const encodedData = encodeFunctionData({ + // abi: jbControllerAbi, // ABI of the contract + // functionName: 'launchProjectFor', + // args, + // }) + + const hash = await writeLaunchProject({ + address: contracts.controller.data, + args, + }) + + onTransactionPendingCallback(hash) + addTransaction?.('Launch Project', { hash }) + const transactionReceipt: WaitForTransactionReceiptReturnType = await waitForTransactionReceipt( + wagmiConfig, + { + hash, + }, + ) + + const newProjectId = getProjectIdFromLaunchReceipt(transactionReceipt) + + onTransactionConfirmedCallback(hash, newProjectId) + } catch (e) { + onTransactionErrorCallback( + (e as Error) ?? new Error('Transaction failed'), + ) + } + }, + [ + contracts.controller.data, + userAddress, + writeLaunchProject, + contracts.primaryNativeTerminal.data, + addTransaction, + ], + ) +} diff --git a/src/packages/v4/utils/launchProject.ts b/src/packages/v4/utils/launchProject.ts new file mode 100644 index 0000000000..0be03d8a6d --- /dev/null +++ b/src/packages/v4/utils/launchProject.ts @@ -0,0 +1,111 @@ +import round from "lodash/round"; +import { V2FundingCycleMetadata } from "packages/v2/models/fundingCycle"; +import { V2V3FundAccessConstraint, V2V3FundingCycleData } from "packages/v2v3/models/fundingCycle"; +import { GroupedSplits, SplitGroup } from "packages/v2v3/models/splits"; +import { V3FundingCycleMetadata } from "packages/v3/models/fundingCycle"; + +export type LaunchV2V3ProjectArgs = [ + string, // _owner + [string, number], // _projectMetadata [projectMetadataCID, JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN] + V2V3FundingCycleData, // _data + V2FundingCycleMetadata | V3FundingCycleMetadata, // _metadata + string, // _mustStartAtOrAfter + GroupedSplits[], // _groupedSplits + V2V3FundAccessConstraint[], // _fundAccessConstraints + string[], // _terminals + string // _memo +]; + +export function transformV2V3CreateArgsToV4({ + v2v3Args, + primaryNativeTerminal, + tokenAddress +}: { + v2v3Args: LaunchV2V3ProjectArgs, + primaryNativeTerminal: `0x${string}` + tokenAddress: `0x${string}` +}) { + const [ + _owner, + _projectMetadata, + _data, + _metadata, + _mustStartAtOrAfter, + _groupedSplits, + _fundAccessConstraints, + _terminals, + _memo + ] = v2v3Args; + + const mustStartAtOrAfterNum = parseInt(_mustStartAtOrAfter) + const now = round(new Date().getTime() / 1000) + + const rulesetConfigurations = [{ + mustStartAtOrAfter: mustStartAtOrAfterNum > now ? mustStartAtOrAfterNum : now, + duration: _data.duration.toNumber(), + weight: _data.weight.toBigInt(), + decayPercent: _data.discountRate.toNumber(), + approvalHook: _data.ballot as `0x${string}`, + + metadata: { + reservedPercent: _metadata.reservedRate.toNumber(), + redemptionRate: _metadata.redemptionRate.toNumber(), + baseCurrency: 1, // Not present in v2v3, passing 1 by default + pausePay: _metadata.pausePay, + pauseRedeem: _metadata.pauseRedeem, + pauseCreditTransfers: Boolean(_metadata.global.pauseTransfers), + allowOwnerMinting: _metadata.allowMinting, + allowSetCustomToken: false, // Assuming false by default + allowTerminalMigration: _metadata.allowTerminalMigration, + allowSetTerminals: _metadata.global.allowSetTerminals, + allowSetController: _metadata.global.allowSetController, + allowAddAccountingContext: false, // Not present in v2v3, passing false by default + allowAddPriceFeed: false, // Not present in v2v3, passing false by default + ownerMustSendPayouts: false, // Not present in v2v3, passing false by default + holdFees: _metadata.holdFees, + useTotalSurplusForRedemptions: _metadata.useTotalOverflowForRedemptions, + useDataHookForPay: _metadata.useDataSourceForPay, + useDataHookForRedeem: _metadata.useDataSourceForRedeem, + dataHook: _metadata.dataSource as `0x${string}`, + metadata: 0, + }, + + splitGroups: _groupedSplits.map(group => ({ + groupId: BigInt(group.group), + splits: group.splits.map(split => ({ + preferAddToBalance: Boolean(split.preferClaimed), + percent: split.percent, + projectId: BigInt(parseInt(split.projectId ?? '0x00', 16)), + beneficiary: split.beneficiary as `0x${string}`, + lockedUntil: split.lockedUntil ?? 0, + hook: split.allocator as `0x${string}`, + })), + })), + + fundAccessLimitGroups: _fundAccessConstraints.map(constraint => ({ + terminal: primaryNativeTerminal, + token: tokenAddress, + payoutLimits: [{ + amount: constraint.distributionLimit.toBigInt(), + currency: constraint.distributionLimitCurrency.toNumber(), + }] as const, + surplusAllowances: [{ + amount: constraint.overflowAllowance.toBigInt(), + currency: constraint.overflowAllowanceCurrency.toNumber(), + }] as const, + })) + }]; + + const terminalConfigurations = _terminals.map(terminal => ({ + terminal: terminal as `0x${string}`, + accountingContextsToAccept: [] as const, + })); + + return [ + _owner as `0x${string}`, + _projectMetadata[0], + rulesetConfigurations, + terminalConfigurations, + _memo, + ] as const; +} diff --git a/src/redux/hooks/useEditingDistributionLimit.ts b/src/redux/hooks/useEditingDistributionLimit.ts index e5afbd2700..b26a460968 100644 --- a/src/redux/hooks/useEditingDistributionLimit.ts +++ b/src/redux/hooks/useEditingDistributionLimit.ts @@ -1,5 +1,5 @@ +import { ETH_TOKEN_ADDRESS } from 'constants/juiceboxTokens' import { BigNumber } from 'ethers' -import { ETH_TOKEN_ADDRESS } from 'packages/v2v3/constants/juiceboxTokens' import { useDefaultJBETHPaymentTerminal } from 'packages/v2v3/hooks/defaultContracts/useDefaultJBETHPaymentTerminal' import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption' import { V2V3_CURRENCY_ETH } from 'packages/v2v3/utils/currency' @@ -9,6 +9,7 @@ import { useAppDispatch } from 'redux/hooks/useAppDispatch' import { useAppSelector } from 'redux/hooks/useAppSelector' import { editingV2ProjectActions } from 'redux/slices/editingV2Project' import { fromWad, parseWad } from 'utils/format/formatNumber' +import { zeroAddress } from 'viem' export interface ReduxDistributionLimit { amount: BigNumber @@ -51,7 +52,6 @@ export const useEditingDistributionLimit = (): [ const setDistributionLimit = useCallback( (input: ReduxDistributionLimit | undefined) => { - if (!defaultJBETHPaymentTerminal) return if (!input) { dispatch(editingV2ProjectActions.setFundAccessConstraints([])) return @@ -60,7 +60,7 @@ export const useEditingDistributionLimit = (): [ dispatch( editingV2ProjectActions.setFundAccessConstraints([ { - terminal: defaultJBETHPaymentTerminal?.address, + terminal: defaultJBETHPaymentTerminal?.address ?? zeroAddress, token: ETH_TOKEN_ADDRESS, distributionLimit: fromWad(input.amount), distributionLimitCurrency, @@ -75,10 +75,8 @@ export const useEditingDistributionLimit = (): [ const setDistributionLimitAmount = useCallback( (input: BigNumber) => { - if (!defaultJBETHPaymentTerminal) return - const currentFundAccessConstraint = fundAccessConstraints?.[0] ?? { - terminal: defaultJBETHPaymentTerminal?.address, + terminal: defaultJBETHPaymentTerminal?.address ?? zeroAddress, token: ETH_TOKEN_ADDRESS, distributionLimitCurrency: V2V3_CURRENCY_ETH.toString(), overflowAllowance: '0', @@ -100,12 +98,11 @@ export const useEditingDistributionLimit = (): [ const setDistributionLimitCurrency = useCallback( (input: V2V3CurrencyOption) => { - if (!defaultJBETHPaymentTerminal) return dispatch( editingV2ProjectActions.setDistributionLimitCurrency(input.toString()), ) }, - [defaultJBETHPaymentTerminal, dispatch], + [dispatch], ) return [