From e655d8e3d92519ef30cbc7b17f99860df6bb8be2 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Mon, 9 Sep 2024 11:13:19 +0800 Subject: [PATCH] feat: v4 edit cycle tx (#4448) --- src/packages/v4/hooks/useEditRulesetTx.ts | 86 +++++++++++++ src/packages/v4/utils/editRuleset.ts | 115 ++++++++++++++++++ .../ReviewConfirmModal/ReviewConfirmModal.tsx | 36 ++++-- .../ReviewConfirmModal/TokensSectionDiff.tsx | 34 +++--- 4 files changed, 245 insertions(+), 26 deletions(-) create mode 100644 src/packages/v4/hooks/useEditRulesetTx.ts create mode 100644 src/packages/v4/utils/editRuleset.ts diff --git a/src/packages/v4/hooks/useEditRulesetTx.ts b/src/packages/v4/hooks/useEditRulesetTx.ts new file mode 100644 index 0000000000..165755a361 --- /dev/null +++ b/src/packages/v4/hooks/useEditRulesetTx.ts @@ -0,0 +1,86 @@ +import { TxHistoryContext } from 'contexts/Transaction/TxHistoryContext' +import { useWallet } from 'hooks/Wallet' +import { NATIVE_TOKEN } from 'juice-sdk-core' +import { useJBContractContext, useWriteJbControllerLaunchRulesetsFor } from 'juice-sdk-react' +import { useCallback, useContext } from 'react' +import { transformEditCycleFormFieldsToTxArgs } from '../utils/editRuleset' +import { EditCycleFormFields } from '../views/V4ProjectSettings/EditCyclePage/EditCycleFormFields' + +export interface EditRulesetTxOpts { + onTransactionPending: (hash: `0x${string}`) => void + onTransactionConfirmed: () => void + onTransactionError: (error: Error) => void +} + +/** + * Takes data in EditCycleFormFields format, converts it to Edit Ruleset tx format and passes it to `writeEditRuleset` + * @returns A function that deploys a project. + */ +export function useEditRulesetTx() { + const { writeContractAsync: writeEditRuleset } = useWriteJbControllerLaunchRulesetsFor() + const { contracts } = useJBContractContext() + + const { addTransaction } = useContext(TxHistoryContext) + + const { userAddress } = useWallet() + + return useCallback( + async (formValues: EditCycleFormFields, + { + onTransactionPending: onTransactionPendingCallback, + onTransactionConfirmed: onTransactionConfirmedCallback, + onTransactionError: onTransactionErrorCallback, + }: EditRulesetTxOpts + ) => { + if ( + !contracts.controller.data || + !contracts.primaryNativeTerminal.data || + !userAddress + ) { + return + } + + const args = transformEditCycleFormFieldsToTxArgs({ + formValues, + primaryNativeTerminal: contracts.primaryNativeTerminal.data, + tokenAddress: NATIVE_TOKEN + }) + + try { + // SIMULATE TX: + // const encodedData = encodeFunctionData({ + // abi: jbControllerAbi, // ABI of the contract + // functionName: 'launchRulesetsFor', + // args, + // }) + + const hash = await writeEditRuleset({ + address: contracts.controller.data, + args, + }) + + onTransactionPendingCallback(hash) + addTransaction?.('Edit Ruleset', { hash }) + // const transactionReceipt: WaitForTransactionReceiptReturnType = await waitForTransactionReceipt( + // wagmiConfig, + // { + // hash, + // }, + // ) + + onTransactionConfirmedCallback() + } catch (e) { + onTransactionErrorCallback( + (e as Error) ?? new Error('Transaction failed'), + ) + } + }, + [ + contracts.controller.data, + userAddress, + writeEditRuleset, + contracts.primaryNativeTerminal.data, + addTransaction, + ], + ) +} diff --git a/src/packages/v4/utils/editRuleset.ts b/src/packages/v4/utils/editRuleset.ts new file mode 100644 index 0000000000..99f8e11b60 --- /dev/null +++ b/src/packages/v4/utils/editRuleset.ts @@ -0,0 +1,115 @@ +import round from "lodash/round"; +import { otherUnitToSeconds } from "utils/format/formatTime"; +import { EditCycleFormFields } from "../views/V4ProjectSettings/EditCyclePage/EditCycleFormFields"; + +export function transformEditCycleFormFieldsToTxArgs({ + formValues, + primaryNativeTerminal, + tokenAddress, +}: { + formValues: EditCycleFormFields; + primaryNativeTerminal: `0x${string}`; + tokenAddress: `0x${string}`; +}) { + const now = round(new Date().getTime() / 1000); + const mustStartAtOrAfter = now; + + const duration = otherUnitToSeconds({ + duration: formValues.duration, + unit: formValues.durationUnit.value, + }) + const weight = BigInt(formValues.issuanceRate); + const decayPercent = formValues.decayPercent; + const approvalHook = formValues.approvalHook; + + const rulesetConfigurations = [ + { + mustStartAtOrAfter, + duration, + weight, + decayPercent, + approvalHook, + + metadata: { + reservedPercent: formValues.reservedPercent, + redemptionRate: formValues.redemptionRate, + baseCurrency: 1, // Assuming base currency is a constant value, typically USD + pausePay: formValues.pausePay, + pauseRedeem: false, // Defaulting this value since it's not in formValues + pauseCreditTransfers: !formValues.tokenTransfers, + allowOwnerMinting: formValues.allowOwnerMinting, + allowSetCustomToken: false, // Defaulting to false as it's not in formValues + allowTerminalMigration: formValues.allowTerminalMigration, + allowSetTerminals: formValues.allowSetTerminals, + allowSetController: formValues.allowSetController, + allowAddAccountingContext: false, // Defaulting to false as it's not in formValues + allowAddPriceFeed: false, // Defaulting to false as it's not in formValues + ownerMustSendPayouts: false, // Defaulting to false as it's not in formValues + holdFees: formValues.holdFees, + useTotalSurplusForRedemptions: false, // Defaulting to false as it's not in formValues + useDataHookForPay: false, // Defaulting to false as it's not in formValues + useDataHookForRedeem: false, // Defaulting to false as it's not in formValues + dataHook: "0x0000000000000000000000000000000000000000" as `0x${string}`, // Defaulting to a null address + metadata: 0, // Assuming no additional metadata is provided + }, + + splitGroups: [ + { + groupId: BigInt(1), // Assuming 1 for payout splits + splits: formValues.payoutSplits.map((split) => ({ + preferAddToBalance: Boolean(split.preferAddToBalance), + percent: Number(split.percent.value), + projectId: BigInt(split.projectId), + beneficiary: split.beneficiary as `0x${string}`, + lockedUntil: split.lockedUntil ?? 0, + hook: split.hook as `0x${string}`, + })), + }, + { + groupId: BigInt(2), // Assuming 2 for reserved tokens splits + splits: formValues.reservedTokensSplits.map((split) => ({ + preferAddToBalance: Boolean(split.preferAddToBalance), + percent: Number(split.percent.value), + projectId: BigInt(split.projectId), + beneficiary: split.beneficiary as `0x${string}`, + lockedUntil: split.lockedUntil ?? 0, + hook: split.hook as `0x${string}`, + })), + }, + ], + + fundAccessLimitGroups: [ + { + terminal: primaryNativeTerminal, + token: tokenAddress, + payoutLimits: [ + { + amount: BigInt(formValues.payoutLimit ?? "0"), + currency: 1, // Assuming currency is constant (e.g., USD) + }, + ], + surplusAllowances: [ + { + amount: BigInt(0), // Assuming no surplus allowances for now + currency: 1, // Assuming currency is constant (e.g., USD) + }, + ], + }, + ], + }, + ]; + + const terminalConfigurations = [ + { + terminal: primaryNativeTerminal, + accountingContextsToAccept: [] as const, + }, + ]; + + return [ + BigInt(now), // Convert the current timestamp to bigint for the first argument + rulesetConfigurations, + terminalConfigurations, + formValues.memo ?? "", + ] as const; +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx index 980c8c784b..747bf0a2d7 100644 --- a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx @@ -8,6 +8,8 @@ import { useState } from 'react' // import { useReconfigureFundingCycle } from '../../../hooks/useReconfigureFundingCycle' import { useEditCycleFormContext } from '../EditCycleFormContext' // import { usePrepareSaveEditCycleData } from '../hooks/usePrepareSaveEditCycleData' +import { useEditRulesetTx } from 'packages/v4/hooks/useEditRulesetTx' +import { emitErrorNotification } from 'utils/notifications' import { TransactionSuccessModal } from '../TransactionSuccessModal' import { DetailsSectionDiff } from './DetailsSectionDiff' import { PayoutsSectionDiff } from './PayoutsSectionDiff' @@ -26,6 +28,7 @@ export function ReviewConfirmModal({ }) { const [editCycleSuccessModalOpen, setEditCycleSuccessModalOpen] = useState(false) + const [confirmLoading, setConfirmLoading] = useState(false) const { editCycleForm } = useEditCycleFormContext() @@ -38,17 +41,26 @@ export function ReviewConfirmModal({ const memo = useWatch('memo', editCycleForm) // const { editingFundingCycleConfig } = usePrepareSaveEditCycleData() + const editRulesetTx = useEditRulesetTx() - // const { reconfigureLoading, reconfigureFundingCycle } = - // useReconfigureFundingCycle({ - // editingFundingCycleConfig, - // memo: memo ?? '', - // onComplete: () => { - // editCycleForm?.resetFields() - // setEditCycleSuccessModalOpen(true) - // onClose() - // }, - // }) + + const handleConfirm = () => { + setConfirmLoading(true) + editRulesetTx(editCycleForm?.getFieldsValue(true), { + onTransactionPending: () => null, + onTransactionConfirmed: () => { + editCycleForm?.resetFields() + setConfirmLoading(false) + setEditCycleSuccessModalOpen(true) + onClose() + }, + onTransactionError: error => { + console.error(error) + setConfirmLoading(false) + emitErrorNotification(`Error launching ruleset: ${error}`) + }, + }) + } const panelProps = { className: 'text-lg' } @@ -58,12 +70,12 @@ export function ReviewConfirmModal({ open={open} title={Review & confirm} destroyOnClose - onOk={() => null}//reconfigureFundingCycle()} + onOk={handleConfirm} okText={Deploy changes} okButtonProps={{ disabled: !formHasChanges }} cancelButtonProps={{ hidden: true }} onCancel={onClose} - confirmLoading={false}//reconfigureLoading} + confirmLoading={confirmLoading} >

diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx index 56a3c83d22..a761ee95d0 100644 --- a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx @@ -61,7 +61,7 @@ export function TokensSectionDiff() { - {mintRateHasDiff && currentMintRateAfterDiscountRateApplied && ( + {mintRateHasDiff && currentMintRateAfterDiscountRateApplied ? ( } /> - )} - {discountRateHasDiff && currentDiscountRate && ( + ) : null} + + {discountRateHasDiff && currentDiscountRate ? ( - )} - {redemptionHasDiff && currentRedemptionRate && ( + ) : null} + + {redemptionHasDiff && currentRedemptionRate ? ( - )} - {allowMintingHasDiff && ( + ) : null} + + {allowMintingHasDiff ? ( } /> - )} - {tokenTransfersHasDiff && ( + ) : null} + + {tokenTransfersHasDiff ? ( } /> - )} - {reservedRateHasDiff && currentReservedRate && ( + ) : null} + + {reservedRateHasDiff && currentReservedRate ? ( {currentReservedRate}%} /> - )} - {reservedSplitsHasDiff && ( + ) : null} + + {reservedSplitsHasDiff ? (

Reserved recipients: @@ -136,7 +142,7 @@ export function TokensSectionDiff() { showDiffs />
- )} + ) : null}
} />