From bcf2960063e3b06718b407fe2606306b6e3a01e7 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Wed, 13 Nov 2024 13:12:11 -0500 Subject: [PATCH] support multi-NFT burn and duplicating when minting --- packages/i18n/locales/bad/translation.json | 8 +- packages/i18n/locales/en/translation.json | 9 +- packages/i18n/locales/es/translation.json | 12 +- .../actions/BurnNft/Component.stories.tsx | 4 +- .../core/actions/BurnNft/Component.tsx | 122 +++++++------ .../actions/core/actions/BurnNft/index.tsx | 171 ++++++++++++------ .../actions/core/actions/MintNft/MintNft.tsx | 3 +- .../actions/core/actions/MintNft/index.tsx | 10 +- .../actions/MintNft/stateless/MintNft.tsx | 37 +++- .../actions/core/actions/MintNft/types.ts | 2 +- .../core/actions/TransferNft/index.tsx | 14 +- .../Press/actions/DeletePost/index.tsx | 16 +- 12 files changed, 255 insertions(+), 153 deletions(-) diff --git a/packages/i18n/locales/bad/translation.json b/packages/i18n/locales/bad/translation.json index b6e350365a..2bea3ee8bc 100644 --- a/packages/i18n/locales/bad/translation.json +++ b/packages/i18n/locales/bad/translation.json @@ -190,7 +190,7 @@ "searchDaos": "bad bad", "selectAllNfts": "bad bad bad bad", "selectChain": "bad bad", - "selectNft": "bad bad", + "selectNfts": "bad bad", "selectToken": "bad bad", "selectValidator": "bad bad", "selectWidget": "bad bad", @@ -854,7 +854,7 @@ "blocksBehind": "bad bad", "bulkImportActionExplanation": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad", "bulkImportActionsDescription": "bad bad bad bad bad", - "burnNftDescription": "bad bad bad", + "burnNftsDescription": "bad bad bad", "bypassSimulationExplanation": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad", "cannotRemoveNoneOption": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad", "catchingUp": "bad bad", @@ -1210,7 +1210,6 @@ "to": "bad", "today": "bad", "token": "bad", - "tokenBurned": "bad bad bad bad bad bad bad", "tokenDaoNotMemberInfo_dao": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad", "tokenDaoNotMemberInfo_proposal": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad", "tokenOnChain": "bad bad bad", @@ -1220,6 +1219,7 @@ "token_one": "bad", "token_other": "bad", "tokens": "bad", + "tokensBurned": "bad bad bad bad bad bad bad", "tokensWillBeSentToTreasury": "bad bad bad bad bad bad bad bad bad", "tokensWillBeSplitAmongContributors": "bad bad bad bad bad bad", "tos": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad", @@ -1424,7 +1424,7 @@ "beginVesting": "bad bad", "browserWallets": "bad bad", "bulkImportActions": "bad bad bad", - "burnNft": "bad bad", + "burnNfts": "bad bad", "cancelVesting": "bad bad", "canceled": "bad", "casting": "bad", diff --git a/packages/i18n/locales/en/translation.json b/packages/i18n/locales/en/translation.json index 87c6dfa76d..9b9ea341f9 100644 --- a/packages/i18n/locales/en/translation.json +++ b/packages/i18n/locales/en/translation.json @@ -237,7 +237,6 @@ "selectAccount": "Select account", "selectAllNfts": "Select all {{count}} NFTs", "selectChain": "Select chain", - "selectNft": "Select NFT", "selectNfts": "Select NFT(s)", "selectToken": "Select token", "selectValidator": "Select validator", @@ -1023,7 +1022,7 @@ "blocksBehind": "Blocks behind", "bulkImportActionExplanation": "Choose a JSON or CSV file below that matches the format described in <2>this guide.", "bulkImportActionsDescription": "Import many actions at once.", - "burnNftDescription": "Burn an NFT.", + "burnNftsDescription": "Burn NFT(s).", "bypassSimulationExplanation": "Simulating the proposal failed. If you don't know what this means, one of the actions above is probably misconfigured. If you are sure you want to publish it anyway, click the button again.", "cannotRemoveNoneOption": "You cannot remove this option. It will be added to the end of the list.", "cannotWithdrawImmediateRewardDistribution": "There are no rewards to withdraw from an immediate distribution.", @@ -1467,7 +1466,6 @@ "to": "to", "today": "today", "token": "token", - "tokenBurned": "NFT with ID {{tokenId}} has been burned.", "tokenCreationNoTokenFactory": "Token creation is not supported by this chain because it does not have the token factory module.", "tokenCreationUnderDevelopment": "Token creation is under development and will be available soon.", "tokenDaoNotMemberInfo_dao": "You are not a member of {{daoName}}. To become a member, you must obtain and stake ${{tokenSymbol}}.", @@ -1480,6 +1478,7 @@ "token_one": "token", "token_other": "tokens", "tokens": "tokens", + "tokensBurned": "NFTs have been burned: {{tokenIds}}.", "tokensWillBeSentToTreasury": "These tokens will be sent to the DAO's treasury.", "tokensWillBeSplitAmongContributors": "{{tokens}} will be split among contributors.", "tokensWillBeWithdrawn": "{{amount}} ${{tokenSymbol}} would be withdrawn if this were executed right now.", @@ -1726,7 +1725,7 @@ "breakdown": "Breakdown", "browserWallets": "Browser wallets", "bulkImportActions": "Bulk Import Actions", - "burnNft": "Burn NFT", + "burnNfts": "Burn NFT(s)", "cancelVesting": "Cancel vesting", "canceled": "Canceled", "casting": "Casting", @@ -2023,7 +2022,7 @@ "saved": "Saved", "scanQrCode": "Scan QR Code", "search": "Search", - "selectNftToBurn": "Select NFT To Burn", + "selectNftsToBurn": "Select NFT(s) To Burn", "selectNftsToTransfer": "Select NFT(s) To Transfer", "send": "Send", "setAdminToParent": "Set admin to {{parent}}", diff --git a/packages/i18n/locales/es/translation.json b/packages/i18n/locales/es/translation.json index c5c57ea5bf..4353943f49 100644 --- a/packages/i18n/locales/es/translation.json +++ b/packages/i18n/locales/es/translation.json @@ -235,7 +235,7 @@ "selectAccount": "Seleccionar cuenta", "selectAllNfts": "Seleccionar todos los {{count}} NFTs", "selectChain": "Seleccionar cadena", - "selectNft": "Seleccionar NFT", + "selectNfts": "Seleccionar NFT(s)", "selectToken": "Seleccionar token", "selectValidator": "Seleccionar validador", "selectWidget": "Seleccionar widget", @@ -1031,7 +1031,7 @@ "blocksBehind": "Bloques detrás", "bulkImportActionExplanation": "Elige un archivo JSON o CSV a continuación que coincida con el formato descrito en <2>esta guía.", "bulkImportActionsDescription": "Importa muchas acciones a la vez.", - "burnNftDescription": "Quemar un NFT.", + "burnNftsDescription": "Quemar NFT(s).", "bypassSimulationExplanation": "La simulación de la propuesta falló. Si no sabes lo que esto significa, probablemente una de las acciones anteriores esté mal configurada. Si estás seguro de que deseas publicarlo de todos modos, haz clic en el botón de nuevo.", "cannotRemoveNoneOption": "No puedes eliminar esta opción. Se agregará al final de la lista.", "catchingUp": "Poniéndose al día...", @@ -1496,7 +1496,6 @@ "to": "a", "today": "hoy", "token": "token", - "tokenBurned": "El NFT con ID {{tokenId}} ha sido quemado.", "tokenCreationNoTokenFactory": "La creación de tokens no es compatible con esta cadena porque no tiene el módulo de fábrica de tokens.", "tokenCreationUnderDevelopment": "La creación de tokens está en desarrollo y estará disponible pronto.", "tokenDaoNotMemberInfo_dao": "No eres miembro de {{daoName}}. Para convertirte en miembro, debes obtener y apostar ${{tokenSymbol}}.", @@ -1509,6 +1508,7 @@ "token_one": "token", "token_other": "tokens", "tokens": "tokens", + "tokensBurned": "Los NFTs han sido quemados: {{tokenIds}}.", "tokensStaked": "{{amount}} ${{tokenSymbol}} depositados", "tokensWillBeSentToTreasury": "Estos tokens serán enviados al tesoro del DAO.", "tokensWillBeSplitAmongContributors": "{{tokens}} se dividirán entre los contribuyentes.", @@ -1754,7 +1754,7 @@ "blockCompleted": "Bloque completado", "browserWallets": "Billeteras del navegador", "bulkImportActions": "Importar acciones en masa", - "burnNft": "Quemar NFT", + "burnNfts": "Quemar NFT(s)", "cancelVesting": "Cancelar adquisición", "canceled": "Cancelado", "casting": "Casting", @@ -2036,8 +2036,8 @@ "saved": "Guardado", "scanQrCode": "Escanear código QR", "search": "Buscar", - "selectNftToBurn": "Seleccionar NFT para quemar", - "selectNftToTransfer": "Seleccionar NFT para transferir", + "selectNftsToBurn": "Seleccionar NFT(s) para quemar", + "selectNftsToTransfer": "Seleccionar NFT(s) para transferir", "send": "Enviar", "setAdminToParent": "Establecer admin a {{parent}}", "setItem": "Establecer ítem", diff --git a/packages/stateful/actions/core/actions/BurnNft/Component.stories.tsx b/packages/stateful/actions/core/actions/BurnNft/Component.stories.tsx index 5bce7396dc..20d7548ebc 100644 --- a/packages/stateful/actions/core/actions/BurnNft/Component.stories.tsx +++ b/packages/stateful/actions/core/actions/BurnNft/Component.stories.tsx @@ -39,10 +39,10 @@ Default.args = { isCreating: true, errors: {}, options: { - nftInfo: { + nftInfos: { loading: false, errored: false, - data: selected, + data: [selected], }, options: { loading: false, diff --git a/packages/stateful/actions/core/actions/BurnNft/Component.tsx b/packages/stateful/actions/core/actions/BurnNft/Component.tsx index 6241f1900f..c6351a1d06 100644 --- a/packages/stateful/actions/core/actions/BurnNft/Component.tsx +++ b/packages/stateful/actions/core/actions/BurnNft/Component.tsx @@ -19,17 +19,19 @@ import { import { getNftKey } from '@dao-dao/utils' export type BurnNftData = { - chainId: string - collection: string - tokenId: string + nfts: { + chainId: string + collection: string + tokenId: string + }[] } export interface BurnNftOptions { // The set of NFTs that may be burned as part of this action. options: LoadingDataWithError - // Information about the NFT currently selected. If errored, it may be burnt. - // If undefined, no NFT is selected. - nftInfo: LoadingDataWithError | undefined + // Information about the NFTs currently selected. If errored, it may be burnt. + // If undefined, no NFTs are selected. + nftInfos: LoadingDataWithError | undefined NftSelectionModal: ComponentType } @@ -37,66 +39,51 @@ export const BurnNft: ActionComponent = ({ fieldNamePrefix, isCreating, errors, - options: { options, nftInfo, NftSelectionModal }, + options: { options, nftInfos, NftSelectionModal }, }) => { const { t } = useTranslation() - const { watch, setValue, setError, clearErrors } = + const { watch, setValue, getValues, setError, clearErrors } = useFormContext() - const chainId = watch((fieldNamePrefix + 'chainId') as 'chainId') - const tokenId = watch((fieldNamePrefix + 'tokenId') as 'tokenId') - const collection = watch((fieldNamePrefix + 'collection') as 'collection') - - const selectedKey = getNftKey(chainId, collection, tokenId) + const nfts = watch((fieldNamePrefix + 'nfts') as 'nfts') + const selectedKeys = nfts.map((nft) => + getNftKey(nft.chainId, nft.collection, nft.tokenId) + ) useEffect(() => { - if ( - !selectedKey || - // If selected, make sure it exists in options. - (!options.loading && - !options.errored && - !options.data.some((nft) => nft.key === selectedKey)) - ) { - if (!errors?.collection) { - setError((fieldNamePrefix + 'collection') as 'collection', { - type: 'required', - message: t('error.noNftSelected'), - }) - } + if (!nfts.length) { + setError((fieldNamePrefix + 'nfts') as 'nfts', { + type: 'required', + message: t('error.noNftSelected'), + }) } else { - if (errors?.collection) { - clearErrors((fieldNamePrefix + 'collection') as 'collection') - } + clearErrors((fieldNamePrefix + 'nfts') as 'nfts') } - }, [ - selectedKey, - setError, - clearErrors, - t, - fieldNamePrefix, - options, - errors?.collection, - ]) + }, [nfts.length, setError, clearErrors, t, fieldNamePrefix]) // Show modal initially if creating and no NFT already selected. const [showModal, setShowModal] = useState( - isCreating && !selectedKey + isCreating && !nfts.length ) return ( <>
- {nftInfo && - (nftInfo.loading ? ( + {nftInfos && + (nftInfos.loading ? ( - ) : !nftInfo.errored ? ( - + ) : !nftInfos.errored ? ( +
+ {nftInfos.data.map(({ key, ...nftInfo }) => ( + + ))} +
) : ( // If errored loading NFT and not creating, token likely burned. - nftInfo.errored && + nftInfos.errored && !isCreating && (

- {t('info.tokenBurned', { tokenId })} + {t('info.tokensBurned', { tokenIds: selectedKeys.join(', ') })}

) ))} @@ -105,14 +92,14 @@ export const BurnNft: ActionComponent = ({ )} @@ -127,25 +114,42 @@ export const BurnNft: ActionComponent = ({ onClick: () => setShowModal(false), }} header={{ - title: t('title.selectNftToBurn'), + title: t('title.selectNftsToBurn'), }} nfts={options} onClose={() => setShowModal(false)} onNftClick={(nft) => { - if (nft.key === selectedKey) { - setValue((fieldNamePrefix + 'chainId') as 'chainId', '') - setValue((fieldNamePrefix + 'tokenId') as 'tokenId', '') - setValue((fieldNamePrefix + 'collection') as 'collection', '') - } else { - setValue((fieldNamePrefix + 'chainId') as 'chainId', nft.chainId) - setValue((fieldNamePrefix + 'tokenId') as 'tokenId', nft.tokenId) + const selected = getValues((fieldNamePrefix + 'nfts') as 'nfts') + + // If the NFT is already selected, remove it. + if ( + selected.some( + (n) => + n.chainId === nft.chainId && + n.collection === nft.collectionAddress && + n.tokenId === nft.tokenId + ) + ) { setValue( - (fieldNamePrefix + 'collection') as 'collection', - nft.collectionAddress + (fieldNamePrefix + 'nfts') as 'nfts', + nfts.filter( + (n) => + getNftKey(n.chainId, n.collection, n.tokenId) !== nft.key + ) ) + } else { + // Otherwise, add the NFT. + setValue((fieldNamePrefix + 'nfts') as 'nfts', [ + ...selected, + { + chainId: nft.chainId, + collection: nft.collectionAddress, + tokenId: nft.tokenId, + }, + ]) } }} - selectedKeys={selectedKey ? [selectedKey] : []} + selectedKeys={selectedKeys} visible={showModal} /> )} diff --git a/packages/stateful/actions/core/actions/BurnNft/index.tsx b/packages/stateful/actions/core/actions/BurnNft/index.tsx index 3cb30837b2..a5feb7f115 100644 --- a/packages/stateful/actions/core/actions/BurnNft/index.tsx +++ b/packages/stateful/actions/core/actions/BurnNft/index.tsx @@ -1,4 +1,4 @@ -import { useQueryClient } from '@tanstack/react-query' +import { useQueries, useQueryClient } from '@tanstack/react-query' import { useFormContext } from 'react-hook-form' import { nftQueries } from '@dao-dao/state/query' @@ -26,13 +26,13 @@ import { import { combineLoadingDataWithErrors, getChainAddressForActionOptions, + makeCombineQueryResultsIntoLoadingDataWithError, makeExecuteSmartContractMessage, maybeMakePolytoneExecuteMessages, objectMatchesStructure, } from '@dao-dao/utils' import { NftSelectionModal } from '../../../../components' -import { useQueryLoadingDataWithError } from '../../../../hooks' import { useCw721CommonGovernanceTokenInfoIfExists } from '../../../../voting-module-adapter' import { BurnNft, BurnNftData } from './Component' @@ -47,11 +47,7 @@ const Component: ActionComponent = (props) => { const { denomOrAddress: governanceCollectionAddress } = useCw721CommonGovernanceTokenInfoIfExists() ?? {} - const chainId = watch((props.fieldNamePrefix + 'chainId') as 'chainId') - const tokenId = watch((props.fieldNamePrefix + 'tokenId') as 'tokenId') - const collection = watch( - (props.fieldNamePrefix + 'collection') as 'collection' - ) + const nfts = watch((props.fieldNamePrefix + 'nfts') as 'nfts') const options = useCachedLoadingWithError( props.isCreating @@ -67,11 +63,14 @@ const Component: ActionComponent = (props) => { }) : undefined ) - const nftInfo = useQueryLoadingDataWithError( - chainId && tokenId && collection - ? nftQueries.cardInfo(queryClient, { chainId, collection, tokenId }) - : undefined - ) + const nftInfos = useQueries({ + queries: nfts.length + ? nfts.map(({ chainId, collection, tokenId }) => + nftQueries.cardInfo(queryClient, { chainId, collection, tokenId }) + ) + : [], + combine: makeCombineQueryResultsIntoLoadingDataWithError(), + }) const allChainOptions = options.loading || options.errored @@ -87,7 +86,7 @@ const Component: ActionComponent = (props) => { {...props} options={{ options: allChainOptions, - nftInfo: chainId && tokenId && collection ? nftInfo : undefined, + nftInfos: nfts.length ? nftInfos : undefined, NftSelectionModal, }} /> @@ -99,64 +98,130 @@ export class BurnNftAction extends ActionBase { public readonly Component = Component protected _defaults: BurnNftData = { - chainId: '', - collection: '', - tokenId: '', + nfts: [], } constructor(options: ActionOptions) { super(options, { Icon: FireEmoji, - label: options.t('title.burnNft'), - description: options.t('info.burnNftDescription'), + label: options.t('title.burnNfts'), + description: options.t('info.burnNftsDescription'), // This must be after the Press widget's Delete Post action. matchPriority: -80, }) } - encode({ chainId, collection, tokenId }: BurnNftData): UnifiedCosmosMsg[] { - return maybeMakePolytoneExecuteMessages( - this.options.chain.chainId, - chainId, - makeExecuteSmartContractMessage({ + encode({ nfts }: BurnNftData): UnifiedCosmosMsg[] { + // Group NFTs by chain. + const nftsByChain = Object.entries( + nfts.reduce( + (acc, nft) => ({ + ...acc, + [nft.chainId]: [...(acc[nft.chainId] ?? []), nft], + }), + {} as Record + ) + ) + + return nftsByChain.flatMap(([chainId, nfts]) => + maybeMakePolytoneExecuteMessages( + this.options.chain.chainId, chainId, - sender: getChainAddressForActionOptions(this.options, chainId) || '', - contractAddress: collection, - msg: { - burn: { - token_id: tokenId, - }, - }, - }) + nfts.map(({ collection, tokenId }) => + makeExecuteSmartContractMessage({ + chainId, + sender: + getChainAddressForActionOptions(this.options, chainId) || '', + contractAddress: collection, + msg: { + burn: { + token_id: tokenId, + }, + }, + }) + ) + ) ) } - match([{ decodedMessage }]: ProcessedMessage[]): ActionMatch { - return objectMatchesStructure(decodedMessage, { - wasm: { - execute: { - contract_addr: {}, - funds: {}, - msg: { - burn: { - token_id: {}, + // This should match one or more burns, including wrapped/cross-chain messages + // with one or more burns. + handleMessages(messages: ProcessedMessage[]) { + const all = messages.map( + ({ isWrapped, decodedMessages, account: { chainId } }) => { + const burns = decodedMessages.map((decodedMessage) => + objectMatchesStructure(decodedMessage, { + wasm: { + execute: { + contract_addr: {}, + funds: {}, + msg: { + burn: { + token_id: {}, + }, + }, + }, }, - }, - }, - }, - }) + }) + ? { + chainId, + collection: decodedMessage.wasm.execute.contract_addr, + tokenId: decodedMessage.wasm.execute.msg.burn.token_id, + } + : null + ) + + // All messages must be burns for this to match. Otherwise, we may match + // a cross-chain execute and accidentally conceal other messages. + const allAreBurns = burns.every((b) => !!b) + + return allAreBurns + ? { + isWrapped, + burns, + } + : null + } + ) + + // If the first is not a burn, match none. + if (!all.length || !all[0]) { + return [] + } + + // Select all adjacent burns starting with the first. + const burns = [all[0]] + for (const burn of all.slice(1)) { + if (!burn) { + break + } + + burns.push(burn) + } + + return burns + } + + match(messages: ProcessedMessage[]): ActionMatch { + const burns = this.handleMessages(messages) + return burns.reduce( + // If wrapped execute, only match the one wrapped execute that contains + // the other burns. + (acc, group) => acc + (group.isWrapped ? 1 : group.burns.length), + 0 + ) } - decode([ - { - decodedMessage, - account: { chainId }, - }, - ]: ProcessedMessage[]): BurnNftData { + decode(messages: ProcessedMessage[]): BurnNftData { + const burns = this.handleMessages(messages) return { - chainId, - collection: decodedMessage.wasm.execute.contract_addr, - tokenId: decodedMessage.wasm.execute.msg.burn.token_id, + nfts: burns.flatMap(({ burns }) => + burns.map(({ chainId, collection, tokenId }) => ({ + chainId, + collection, + tokenId, + })) + ), } } } diff --git a/packages/stateful/actions/core/actions/MintNft/MintNft.tsx b/packages/stateful/actions/core/actions/MintNft/MintNft.tsx index ff1b2149a6..1f34899485 100644 --- a/packages/stateful/actions/core/actions/MintNft/MintNft.tsx +++ b/packages/stateful/actions/core/actions/MintNft/MintNft.tsx @@ -74,7 +74,8 @@ export const MintNft: ActionComponent = (props) => { creatingCollectionInfo.loading ? undefined : { - key: chainId + collectionAddress + mintMsg.token_id, + key: + mintMsg.token_uri || chainId + collectionAddress + mintMsg.token_id, collectionAddress, collectionName: (!creatingCollectionInfo.errored && diff --git a/packages/stateful/actions/core/actions/MintNft/index.tsx b/packages/stateful/actions/core/actions/MintNft/index.tsx index 129de2c91d..bb5a3af61f 100644 --- a/packages/stateful/actions/core/actions/MintNft/index.tsx +++ b/packages/stateful/actions/core/actions/MintNft/index.tsx @@ -144,10 +144,12 @@ const Component: ActionComponent = (props) => { )} -
- - -
+ {!!(props.errors?.contractChosen || props.errors?.mintMsg?.token_uri) && ( +
+ + +
+ )} ) } diff --git a/packages/stateful/actions/core/actions/MintNft/stateless/MintNft.tsx b/packages/stateful/actions/core/actions/MintNft/stateless/MintNft.tsx index 4a8af5fc0b..86c564aacd 100644 --- a/packages/stateful/actions/core/actions/MintNft/stateless/MintNft.tsx +++ b/packages/stateful/actions/core/actions/MintNft/stateless/MintNft.tsx @@ -3,10 +3,12 @@ import { SubdirectoryArrowRightRounded, } from '@mui/icons-material' import clsx from 'clsx' +import cloneDeep from 'lodash.clonedeep' import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { + Button, HorizontalNftCard, InputErrorMessage, InputLabel, @@ -14,10 +16,10 @@ import { useChain, useDetectWrap, } from '@dao-dao/stateless' -import { ActionComponent } from '@dao-dao/types' +import { ActionComponent, ActionKey } from '@dao-dao/types' import { makeValidateAddress, validateRequired } from '@dao-dao/utils' -import { MintNftOptions } from '../types' +import { MintNftData, MintNftOptions } from '../types' // Form displayed when the user is minting a new NFT. export const MintNft: ActionComponent = ({ @@ -25,11 +27,13 @@ export const MintNft: ActionComponent = ({ errors, isCreating, options: { nftInfo, AddressInput }, + addAction, + index, }) => { const { t } = useTranslation() const { bech32Prefix } = useChain() - const { register } = useFormContext() + const { getValues, register } = useFormContext() const { containerRef, childRef, wrapped } = useDetectWrap() const Icon = wrapped ? SubdirectoryArrowRightRounded : ArrowRightAltRounded @@ -51,7 +55,9 @@ export const MintNft: ActionComponent = ({ className="w-auto" disabled={!isCreating} error={errors?.mintMsg?.token_id} - fieldName={fieldNamePrefix + 'mintMsg.token_id'} + fieldName={ + (fieldNamePrefix + 'mintMsg.token_id') as 'mintMsg.token_id' + } register={register} validation={[validateRequired]} /> @@ -70,13 +76,34 @@ export const MintNft: ActionComponent = ({ containerClassName="grow" disabled={!isCreating} error={errors?.mintMsg?.owner} - fieldName={fieldNamePrefix + 'mintMsg.owner'} + fieldName={(fieldNamePrefix + 'mintMsg.owner') as 'mintMsg.owner'} register={register} validation={[validateRequired, makeValidateAddress(bech32Prefix)]} />
+ {isCreating && addAction && ( + + )} + diff --git a/packages/stateful/actions/core/actions/MintNft/types.ts b/packages/stateful/actions/core/actions/MintNft/types.ts index 018864b950..ecb86a8c02 100644 --- a/packages/stateful/actions/core/actions/MintNft/types.ts +++ b/packages/stateful/actions/core/actions/MintNft/types.ts @@ -48,5 +48,5 @@ export interface ChooseExistingNftCollectionOptions { export interface MintNftOptions { nftInfo: NftCardInfo addCollectionToDao?: () => void - AddressInput: ComponentType + AddressInput: ComponentType> } diff --git a/packages/stateful/actions/core/actions/TransferNft/index.tsx b/packages/stateful/actions/core/actions/TransferNft/index.tsx index 85356114e2..4b49693f62 100644 --- a/packages/stateful/actions/core/actions/TransferNft/index.tsx +++ b/packages/stateful/actions/core/actions/TransferNft/index.tsx @@ -160,11 +160,11 @@ export class TransferNftAction extends ActionBase { ) } - // This should match one or more identical same-chain transfers or one - // cross-chain message with one or more idential transfers. The only thing - // that can differ is the NFT being transferred. + // This should match one or more identical transfers or one + // wrapped/cross-chain message with one or more idential transfers. The only + // thing that can differ is the NFT being transferred. handleMessages(_messages: ProcessedMessage[]) { - const messages = _messages[0].isCrossChain + const messages = _messages[0].isWrapped ? _messages[0].wrappedMessages : _messages @@ -254,9 +254,9 @@ export class TransferNftAction extends ActionBase { match(messages: ProcessedMessage[]): ActionMatch { const transfers = this.handleMessages(messages) - // If wrapped cross-chain execute, only match the cross-chain execute, and - // only if all messages are transfers. - return messages[0].isCrossChain && + // If wrapped execute, only match the one wrapped execute that contains the + // other transfers, and only if all messages are transfers. + return messages[0].isWrapped && transfers.length === messages[0].wrappedMessages.length ? 1 : transfers.length diff --git a/packages/stateful/widgets/widgets/Press/actions/DeletePost/index.tsx b/packages/stateful/widgets/widgets/Press/actions/DeletePost/index.tsx index d179497da0..19c3779e03 100644 --- a/packages/stateful/widgets/widgets/Press/actions/DeletePost/index.tsx +++ b/packages/stateful/widgets/widgets/Press/actions/DeletePost/index.tsx @@ -79,16 +79,20 @@ export class DeletePostAction extends ActionBase { encode({ id }: DeletePostData): UnifiedCosmosMsg[] { return this.burnNftAction.encode({ - chainId: this.pressChainId, - collection: this.pressData.contract, - tokenId: id, + nfts: [ + { + chainId: this.pressChainId, + collection: this.pressData.contract, + tokenId: id, + }, + ], }) } - match(messages: ProcessedMessage[]): ActionMatch { + match([message]: ProcessedMessage[]): ActionMatch { return ( - this.burnNftAction.match(messages) && - messages[0].decodedMessage.wasm.execute.contract_addr === + this.burnNftAction.match([message]) && + message.decodedMessage.wasm.execute.contract_addr === this.pressData.contract ) }