Skip to content

Commit

Permalink
Fixed token factory mint action.
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso committed Nov 10, 2023
1 parent 1b4c325 commit 50455d2
Show file tree
Hide file tree
Showing 14 changed files with 573 additions and 138 deletions.
7 changes: 7 additions & 0 deletions packages/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@
"pencil": "Pencil",
"people": "People",
"pick": "Mining pick",
"printer": "Printer",
"raisedHand": "Raised hand",
"recycle": "Recycle",
"robot": "Robot",
Expand Down Expand Up @@ -509,6 +510,7 @@
"grantOrRevokeAuthz": "Grant or revoke authorization",
"granteeAddress": "Grantee address",
"granteeAddressTooltip": "The address you are granting or revoking to execute a message on behalf of the DAO.",
"howManyTokensCanTheyMint": "How many ${{tokenSymbol}} can they mint?",
"iContributedPlaceholder": "I contributed...",
"image": "Image",
"imageUrlTooltip": "A link to an image. For example: https://moonphase.is/image.svg",
Expand All @@ -530,6 +532,7 @@
"messageType": "Message type",
"migrateDescription": "This will <1>migrate</1> the selected contract to a new code ID.",
"migrateMessage": "Migrate message",
"minter": "Minter",
"minterContract": "Minter contract",
"minterContractMessage": "Minter contract message",
"multipleChoiceDescription": "This allows proposals to contain multiple choices instead of just `Yes` and `No`.\n\n**CAUTION:** Using more features increases the risk to a DAO because there are more things that can go wrong. You can always enable this later.",
Expand Down Expand Up @@ -869,6 +872,7 @@
"minimumOutputRequiredDescription_gov": "Before the proposal is passed and executed, the swap price will fluctuate. If the price drops and no longer satisfies this minimum output required, the swap will not occur.",
"minimumOutputRequiredDescription_wallet": "The exact swap price will fluctuate during the transaction, but the minimum output amount is guaranteed. If the price drops and no longer satisfies this minimum output required, the swap will not occur.",
"mintActionDescription": "Mint new governance tokens.",
"mintExplanation": "This action mints new tokens, increasing the token supply. With great power comes great responsibility; be careful! If you have an active threshold set, this may lock the DAO.",
"mintNftDescription": "Create a new NFT.",
"mustViewAllActionPagesBeforeVoting": "You must view all action pages before voting.",
"name": "Name",
Expand Down Expand Up @@ -1054,6 +1058,8 @@
},
"updateContractAdminActionDescription": "Update the CosmWasm level admin of a smart contract.",
"updateInfoActionDescription": "Update your DAO's name, image, and description.",
"updateMinterAllowanceDescription": "Allow an account to mint tokens, or remove the allowance.",
"updateMinterAllowanceExplanation": "This action is needed to allow an account to mint tokens.",
"updatePostDescription": "Update a post on the DAO's press.",
"updateProposalSubmissionConfigActionDescription": "Update the proposal submission paramaters for your DAO.",
"updateVotingConfigActionDescription": "Update the voting parameters for your DAO.",
Expand Down Expand Up @@ -1472,6 +1478,7 @@
"upcoming": "Upcoming",
"updateContractAdmin": "Update Contract Admin",
"updateInfo": "Update Info",
"updateMinterAllowance": "Update Minter Allowance",
"updatePost": "Update Post",
"upgradeToV2": "Upgrade to V2",
"validatorActions": "Validator Actions",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ComponentMeta, ComponentStory } from '@storybook/react'

import { AddressInput } from '@dao-dao/stateless'
import { CHAIN_ID, makeReactHookFormDecorator } from '@dao-dao/storybook'
import { TokenType } from '@dao-dao/types'

import { MintData } from '.'
import { MintComponent } from './MintComponent'
import { MintComponent, MintData } from './MintComponent'

export default {
title:
'DAO DAO / packages / stateful / voting-module-adapter / adapters / DaoVotingNativeStaked / actions / Mint',
'DAO DAO / packages / stateful / voting-module-adapter / adapters / DaoVotingTokenStaked / actions / Mint',
component: MintComponent,
decorators: [
makeReactHookFormDecorator<MintData>({
recipient: 'address',
amount: 100000,
}),
],
Expand All @@ -37,5 +38,6 @@ Default.args = {
decimals: 6,
imageUrl: '',
},
AddressInput,
},
}
Original file line number Diff line number Diff line change
@@ -1,56 +1,189 @@
import {
ArrowRightAltRounded,
SubdirectoryArrowRightRounded,
} from '@mui/icons-material'
import clsx from 'clsx'
import { ComponentType, useRef } from 'react'
import { useFormContext } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import useDeepCompareEffect from 'use-deep-compare-effect'

import { InputErrorMessage, NumberInput } from '@dao-dao/stateless'
import { ActionComponent, GenericToken } from '@dao-dao/types'
import {
InputErrorMessage,
NumberInput,
WarningCard,
useChain,
useDetectWrap,
} from '@dao-dao/stateless'
import {
ActionComponent,
ActionKey,
AddressInputProps,
GenericToken,
} from '@dao-dao/types'
import {
convertMicroDenomToDenomWithDecimals,
makeValidateAddress,
validatePositive,
validateRequired,
} from '@dao-dao/utils'

import { useActionOptions } from '../../../../../actions'
import { UpdateMinterAllowanceData } from '../UpdateMinterAllowance/UpdateMinterAllowanceComponent'

export type MintData = {
recipient: string
amount: number
}

export interface MintOptions {
export type MintOptions = {
govToken: GenericToken
AddressInput: ComponentType<AddressInputProps<MintData>>
}

export const MintComponent: ActionComponent<MintOptions> = ({
fieldNamePrefix,
errors,
isCreating,
options: { govToken },
options: { govToken, AddressInput },
allActionsWithData,
index,
addAction,
}) => {
const { t } = useTranslation()
const { context } = useActionOptions()
const { register, watch, setValue } = useFormContext()
const { address } = useActionOptions()
const { register, watch, setValue, getValues } = useFormContext<MintData>()
const { bech32_prefix: bech32Prefix } = useChain()

const amount = watch((fieldNamePrefix + 'amount') as 'amount')

const { containerRef, childRef, wrapped } = useDetectWrap()
const Icon = wrapped ? SubdirectoryArrowRightRounded : ArrowRightAltRounded

// Ensure an UpdateMinterAllowance action exists before this one for the
// needed amount, or create/update otherwise. The needed amount is the sum of
// all mint actions.
const totalAmountNeeded = allActionsWithData
.filter(({ actionKey }) => actionKey === ActionKey.Mint)
.reduce(
(acc, { data }) => acc + ((data as MintData | undefined)?.amount || 0),
0
)
const firstMintActionIndex = allActionsWithData.findIndex(
({ actionKey }) => actionKey === ActionKey.Mint
)
const updateMinterAllowanceActionIndex = allActionsWithData.findIndex(
({ actionKey, data }) =>
actionKey === ActionKey.UpdateMinterAllowance &&
(data as UpdateMinterAllowanceData | undefined)?.minter === address
)
// Prevents double-add on initial render.
const created = useRef(false)
useDeepCompareEffect(() => {
if (
!isCreating ||
!addAction ||
// If this is not the first mint action, don't do anything.
firstMintActionIndex !== index
) {
return
}

// If no action exists, create one right before.
if (updateMinterAllowanceActionIndex === -1) {
// Prevents double-add on initial render.
if (created.current) {
return
}
created.current = true

addAction(
{
actionKey: ActionKey.UpdateMinterAllowance,
data: {
minter: address,
allowance: amount,
} as UpdateMinterAllowanceData,
},
index
)
} else {
// Path to the allowance field on the update minter allowance action.
const existingAllowanceFieldName = fieldNamePrefix.replace(
new RegExp(`${index}\\.data.$`),
`${updateMinterAllowanceActionIndex}.data.allowance`
)

// Otherwise if the amount isn't correct, update the existing one.
if (getValues(existingAllowanceFieldName as any) !== totalAmountNeeded) {
setValue(existingAllowanceFieldName as any, totalAmountNeeded)
}
}
}, [
addAction,
address,
amount,
fieldNamePrefix,
firstMintActionIndex,
getValues,
index,
isCreating,
setValue,
totalAmountNeeded,
updateMinterAllowanceActionIndex,
])

return (
<>
<NumberInput
containerClassName="w-full"
disabled={!isCreating}
error={errors?.amount}
fieldName={fieldNamePrefix + 'amount'}
min={convertMicroDenomToDenomWithDecimals(1, govToken.decimals)}
register={register}
setValue={setValue}
sizing="none"
step={convertMicroDenomToDenomWithDecimals(1, govToken.decimals)}
unit={'$' + govToken.symbol}
validation={[validateRequired, validatePositive]}
watch={watch}
<WarningCard
className="max-w-prose"
content={t('info.mintExplanation')}
/>

{errors?.amount && (
<div className="-mt-2 flex flex-col gap-1">
<div
className="flex min-w-0 flex-row flex-wrap items-stretch justify-between gap-x-3 gap-y-1"
ref={containerRef}
>
<NumberInput
disabled={!isCreating}
error={errors?.amount}
fieldName={(fieldNamePrefix + 'amount') as 'amount'}
min={convertMicroDenomToDenomWithDecimals(1, govToken.decimals)}
register={register}
setValue={setValue}
step={convertMicroDenomToDenomWithDecimals(1, govToken.decimals)}
unit={'$' + govToken.symbol}
validation={[validateRequired, validatePositive]}
watch={watch}
/>

<div
className="flex min-w-0 grow flex-row items-stretch gap-2 sm:gap-3"
ref={childRef}
>
<div
className={clsx('flex flex-row items-center', wrapped && 'pl-1')}
>
<Icon className="!h-6 !w-6 text-text-secondary" />
</div>

<AddressInput
containerClassName="grow"
disabled={!isCreating}
error={errors?.recipient}
fieldName={(fieldNamePrefix + 'recipient') as 'recipient'}
register={register}
validation={[validateRequired, makeValidateAddress(bech32Prefix)]}
/>
</div>
</div>

{(errors?.amount || errors?.recipient) && (
<div className="-mt-4 flex flex-col gap-1">
<InputErrorMessage error={errors?.amount} />
<InputErrorMessage error={errors?.recipient} />
</div>
)}

<p className="caption-text italic">
{t('info.tokensWillBeSentToTreasury')}
</p>
</>
)
}
Loading

0 comments on commit 50455d2

Please sign in to comment.