From 71b3940647d9f3608c74789b46e599a7ecb517c3 Mon Sep 17 00:00:00 2001 From: Joseph Chalabi <100090645+chalabi2@users.noreply.github.com> Date: Wed, 23 Oct 2024 00:21:18 -0700 Subject: [PATCH] finish multimint transactions --- components/factory/components/MyDenoms.tsx | 57 ++++------- components/factory/forms/MintForm.tsx | 50 ++-------- components/factory/modals/BurnModal.tsx | 75 +++++++------- components/factory/modals/MintModal.tsx | 35 +++---- components/factory/modals/denomInfo.tsx | 4 +- .../factory/modals/multiMfxBurnModal.tsx | 98 ++++++++++++++++--- .../factory/modals/multiMfxMintModal.tsx | 96 +++++++++++++++--- pages/factory/index.tsx | 1 - 8 files changed, 252 insertions(+), 164 deletions(-) diff --git a/components/factory/components/MyDenoms.tsx b/components/factory/components/MyDenoms.tsx index 4fe18d03..57c8fe4f 100644 --- a/components/factory/components/MyDenoms.tsx +++ b/components/factory/components/MyDenoms.tsx @@ -12,7 +12,7 @@ import { PiInfo } from 'react-icons/pi'; import { ExtendedMetadataSDKType, shiftDigits } from '@/utils'; import { MultiMintModal } from '@/components/factory/modals'; import { MultiBurnModal } from '../modals/multiMfxBurnModal'; - +import { usePoaGetAdmin } from '@/hooks'; export default function MyDenoms({ denoms, isLoading, @@ -45,6 +45,8 @@ export default function MyDenoms({ } }; + const { poaAdmin, isPoaAdminLoading } = usePoaGetAdmin(); + useEffect(() => { const { denom, action } = router.query; if (denom && typeof denom === 'string') { @@ -217,6 +219,8 @@ export default function MyDenoms({ onClose={handleCloseModal} /> { - // ... existing update logic ... - }} - addPayoutPair={() => { - // ... existing add logic ... - }} - removePayoutPair={index => { - // ... existing remove logic ... - }} - handleMultiMint={async () => { - // ... existing multi mint logic ... - handleCloseModal(); - }} - isSigning={false} /> { - // Implementation will be handled in BurnForm - }} - addBurnPair={() => { - // Implementation will be handled in BurnForm - }} - removeBurnPair={index => { - // Implementation will be handled in BurnForm - }} - handleMultiBurn={async () => { - // Implementation will be handled in BurnForm - handleCloseModal(); - }} - isSigning={false} /> ); @@ -341,16 +325,17 @@ function TokenRow({ - e.stopPropagation()} // Stop propagation at the cell level - > + e.stopPropagation()}>
- diff --git a/components/factory/forms/MintForm.tsx b/components/factory/forms/MintForm.tsx index 02135835..91f84cd9 100644 --- a/components/factory/forms/MintForm.tsx +++ b/components/factory/forms/MintForm.tsx @@ -14,11 +14,6 @@ import Yup from '@/utils/yupExtensions'; import { NumberInput, TextInput } from '@/components/react/inputs'; import { ExtendedMetadataSDKType, truncateString } from '@/utils'; -interface PayoutPair { - address: string; - amount: string; -} - export default function MintForm({ isAdmin, admin, @@ -41,12 +36,9 @@ export default function MintForm({ const [amount, setAmount] = useState(''); const [recipient, setRecipient] = useState(address); - const { setToastMessage } = useToast(); const { tx, isSigning, setIsSigning } = useTx(chainName); const { estimateFee } = useFeeEstimation(chainName); const { mint } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl; - const { payout } = liftedinit.manifest.v1.MessageComposer.withTypeUrl; - const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; const exponent = denom?.denom_units?.find((unit: { denom: string }) => unit.denom === denom.display)?.exponent || @@ -70,39 +62,15 @@ export default function MintForm({ const amountInBaseUnits = BigInt(parseFloat(amount) * Math.pow(10, exponent)).toString(); let msg; - if (isMFX) { - const payoutMsg = payout({ - authority: admin ?? '', - payoutPairs: [ - { - address: recipient, - coin: { denom: denom.base, amount: amountInBaseUnits }, - }, - ], - }); - const encodedMessage = Any.fromPartial({ - typeUrl: payoutMsg.typeUrl, - value: MsgPayout.encode(payoutMsg.value).finish(), - }); - msg = submitProposal({ - groupPolicyAddress: admin ?? '', - messages: [encodedMessage], - metadata: '', - proposers: [address ?? ''], - title: `Manifest Module Control: Mint MFX`, - summary: `This proposal includes a mint action for MFX.`, - exec: 0, - }); - } else { - msg = mint({ - amount: { - amount: amountInBaseUnits, - denom: denom.base, - }, - sender: address, - mintToAddress: recipient, - }); - } + + msg = mint({ + amount: { + amount: amountInBaseUnits, + denom: denom.base, + }, + sender: address, + mintToAddress: recipient, + }); const fee = await estimateFee(address ?? '', [msg]); await tx([msg], { diff --git a/components/factory/modals/BurnModal.tsx b/components/factory/modals/BurnModal.tsx index a1c8121b..f7a9163b 100644 --- a/components/factory/modals/BurnModal.tsx +++ b/components/factory/modals/BurnModal.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import BurnForm from '@/components/factory/forms/BurnForm'; import { useGroupsByAdmin, usePoaGetAdmin } from '@/hooks'; @@ -23,6 +23,7 @@ export default function BurnModal({ onClose: () => void; onSwitchToMultiBurn: () => void; }) { + const [isMultiBurnOpen, setIsMultiBurnOpen] = useState(false); const { poaAdmin, isPoaAdminLoading } = usePoaGetAdmin(); const { groupByAdmin, isGroupByAdminLoading } = useGroupsByAdmin( poaAdmin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj' @@ -38,40 +39,46 @@ export default function BurnModal({ onSwitchToMultiBurn(); }; + const handleMultiBurnClose = () => { + setIsMultiBurnOpen(false); + }; + return ( - -
-
- -
-

- Burn{' '} - - {truncateString(denom.display ?? 'Denom', 20).toUpperCase()} - -

-
- {isLoading ? ( -
- ) : ( - - )} + <> + +
+
+ +
+

+ Burn{' '} + + {truncateString(denom.display ?? 'Denom', 20).toUpperCase()} + +

+
+ {isLoading ? ( +
+ ) : ( + + )} +
-
-
- -
-
+
+ +
+ + ); } diff --git a/components/factory/modals/MintModal.tsx b/components/factory/modals/MintModal.tsx index 1acfca54..9e3e1d3e 100644 --- a/components/factory/modals/MintModal.tsx +++ b/components/factory/modals/MintModal.tsx @@ -13,7 +13,9 @@ export default function MintModal({ totalSupply, isOpen, onClose, - onSwitchToMultiMint, // Add this prop + onSwitchToMultiMint, + admin, + isPoaAdminLoading, }: { denom: ExtendedMetadataSDKType | null; address: string; @@ -22,14 +24,14 @@ export default function MintModal({ totalSupply: string; isOpen: boolean; onClose: () => void; - onSwitchToMultiMint: () => void; // New prop + onSwitchToMultiMint: () => void; + admin: string; + isPoaAdminLoading: boolean; }) { const [isMultiMintOpen, setIsMultiMintOpen] = useState(false); - const [payoutPairs, setPayoutPairs] = useState([{ address: '', amount: '' }]); - const { poaAdmin, isPoaAdminLoading } = usePoaGetAdmin(); const { groupByAdmin, isGroupByAdminLoading } = useGroupsByAdmin( - poaAdmin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj' + admin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj' ); const members = groupByAdmin?.groups?.[0]?.members; @@ -39,7 +41,7 @@ export default function MintModal({ if (!denom) return null; const handleMultiMintOpen = () => { - onSwitchToMultiMint(); // Use the new prop instead of managing state internally + onSwitchToMultiMint(); }; const handleMultiMintClose = () => { @@ -67,7 +69,7 @@ export default function MintModal({ ) : ( - {/* Render MultiMintModal at the same level */} { - const newPairs = [...payoutPairs]; - newPairs[index][field] = value; - setPayoutPairs(newPairs); - }} - addPayoutPair={() => setPayoutPairs([...payoutPairs, { address: '', amount: '' }])} - removePayoutPair={index => setPayoutPairs(payoutPairs.filter((_, i) => i !== index))} - handleMultiMint={async () => { - // ... handle multi mint logic ... - handleMultiMintClose(); - }} - isSigning={false} // Add your signing state here + admin={admin ?? ''} + address={address} + denom={denom} + exponent={denom?.denom_units?.[1]?.exponent || 0} + refetch={refetch} /> ); diff --git a/components/factory/modals/denomInfo.tsx b/components/factory/modals/denomInfo.tsx index 536b75b3..ddf6de3e 100644 --- a/components/factory/modals/denomInfo.tsx +++ b/components/factory/modals/denomInfo.tsx @@ -7,8 +7,8 @@ import { useRouter } from 'next/router'; export const DenomInfoModal: React.FC<{ denom: MetadataSDKType | null; modalId: string; - isOpen: boolean; - onClose: () => void; + isOpen?: boolean; + onClose?: () => void; }> = ({ denom, modalId, isOpen, onClose }) => { return ( diff --git a/components/factory/modals/multiMfxBurnModal.tsx b/components/factory/modals/multiMfxBurnModal.tsx index 6dec0aee..0b96c5dc 100644 --- a/components/factory/modals/multiMfxBurnModal.tsx +++ b/components/factory/modals/multiMfxBurnModal.tsx @@ -1,10 +1,15 @@ -import React from 'react'; -import { PiPlusCircle, PiMinusCircle } from 'react-icons/pi'; +// TODO: Double check that this file is unecessary +import React, { useState } from 'react'; + import { Formik, Form, FieldArray, Field, FieldProps } from 'formik'; import Yup from '@/utils/yupExtensions'; import { NumberInput, TextInput } from '@/components/react/inputs'; import { PiAddressBook } from 'react-icons/pi'; import { PlusIcon, MinusIcon } from '@/components/icons'; +import { useTx, useFeeEstimation } from '@/hooks'; +import { chainName } from '@/config'; +import { cosmos, osmosis } from '@chalabi/manifestjs'; +import { Any } from '@chalabi/manifestjs/dist/codegen/google/protobuf/any'; interface BurnPair { address: string; @@ -14,12 +19,11 @@ interface BurnPair { interface MultiBurnModalProps { isOpen: boolean; onClose: () => void; - burnPairs: BurnPair[]; - updateBurnPair: (index: number, field: 'address' | 'amount', value: string) => void; - addBurnPair: () => void; - removeBurnPair: (index: number) => void; - handleMultiBurn: () => void; - isSigning: boolean; + admin: string; + address: string; + denom: any; + exponent: number; + refetch: () => void; } const BurnPairSchema = Yup.object().shape({ @@ -42,13 +46,79 @@ const MultiBurnSchema = Yup.object().shape({ export function MultiBurnModal({ isOpen, onClose, - burnPairs, - updateBurnPair, - addBurnPair, - removeBurnPair, - handleMultiBurn, - isSigning, + admin, + address, + denom, + exponent, + refetch, }: MultiBurnModalProps) { + const [burnPairs, setBurnPairs] = useState([{ address: '', amount: '' }]); + const { tx, isSigning, setIsSigning } = useTx(chainName); + const { estimateFee } = useFeeEstimation(chainName); + const { burn } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl; + const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; + + const updateBurnPair = (index: number, field: 'address' | 'amount', value: string) => { + const newPairs = [...burnPairs]; + newPairs[index] = { ...newPairs[index], [field]: value }; + setBurnPairs(newPairs); + }; + + const addBurnPair = () => { + setBurnPairs([...burnPairs, { address: '', amount: '' }]); + }; + + const removeBurnPair = (index: number) => { + setBurnPairs(burnPairs.filter((_, i) => i !== index)); + }; + + const handleMultiBurn = async (values: { burnPairs: BurnPair[] }) => { + setIsSigning(true); + try { + const messages = values.burnPairs.map(pair => + burn({ + sender: pair.address, + burnFromAddress: pair.address, + amount: { + denom: denom.base, + amount: BigInt(parseFloat(pair.amount) * Math.pow(10, exponent)).toString(), + }, + }) + ); + + const encodedMessages = messages.map(msg => + Any.fromPartial({ + typeUrl: msg.typeUrl, + // @ts-ignore + value: msg.value, + }) + ); + + const msg = submitProposal({ + groupPolicyAddress: admin, + messages: encodedMessages, + metadata: '', + proposers: [address], + title: `Manifest Module Control: Multi Burn MFX`, + summary: `This proposal includes a multi-burn action for MFX.`, + exec: 0, + }); + + const fee = await estimateFee(address, [msg]); + await tx([msg], { + fee, + onSuccess: () => { + refetch(); + onClose(); + }, + }); + } catch (error) { + console.error('Error during multi-burn:', error); + } finally { + setIsSigning(false); + } + }; + return (
diff --git a/components/factory/modals/multiMfxMintModal.tsx b/components/factory/modals/multiMfxMintModal.tsx index 1c9db485..7e96f8c4 100644 --- a/components/factory/modals/multiMfxMintModal.tsx +++ b/components/factory/modals/multiMfxMintModal.tsx @@ -1,10 +1,14 @@ -// MultiMintModal.tsx -import React, { useRef } from 'react'; +import React, { useState } from 'react'; import { Formik, Form, FieldArray, Field, FieldProps } from 'formik'; import Yup from '@/utils/yupExtensions'; import { NumberInput, TextInput } from '@/components/react/inputs'; import { PiAddressBook } from 'react-icons/pi'; -import { TrashIcon, PlusIcon, MinusIcon } from '@/components/icons'; +import { PlusIcon, MinusIcon } from '@/components/icons'; +import { useTx, useFeeEstimation } from '@/hooks'; +import { chainName } from '@/config'; +import { cosmos, liftedinit } from '@chalabi/manifestjs'; +import { Any } from '@chalabi/manifestjs/dist/codegen/google/protobuf/any'; +import { MsgPayout } from '@chalabi/manifestjs/dist/codegen/liftedinit/manifest/v1/tx'; interface PayoutPair { address: string; @@ -14,12 +18,11 @@ interface PayoutPair { interface MultiMintModalProps { isOpen: boolean; onClose: () => void; - payoutPairs: PayoutPair[]; - updatePayoutPair: (index: number, field: 'address' | 'amount', value: string) => void; - addPayoutPair: () => void; - removePayoutPair: (index: number) => void; - handleMultiMint: () => void; - isSigning: boolean; + admin: string; + address: string; + denom: any; + exponent: number; + refetch: () => void; } const PayoutPairSchema = Yup.object().shape({ @@ -42,13 +45,76 @@ const MultiMintSchema = Yup.object().shape({ export function MultiMintModal({ isOpen, onClose, - payoutPairs, - updatePayoutPair, - addPayoutPair, - removePayoutPair, - handleMultiMint, - isSigning, + admin, + address, + denom, + exponent, + refetch, }: MultiMintModalProps) { + const [payoutPairs, setPayoutPairs] = useState([{ address: '', amount: '' }]); + const { tx, isSigning, setIsSigning } = useTx(chainName); + const { estimateFee } = useFeeEstimation(chainName); + const { payout } = liftedinit.manifest.v1.MessageComposer.withTypeUrl; + const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; + + const updatePayoutPair = (index: number, field: 'address' | 'amount', value: string) => { + const newPairs = [...payoutPairs]; + newPairs[index] = { ...newPairs[index], [field]: value }; + setPayoutPairs(newPairs); + }; + + const addPayoutPair = () => { + setPayoutPairs([...payoutPairs, { address: '', amount: '' }]); + }; + + const removePayoutPair = (index: number) => { + setPayoutPairs(payoutPairs.filter((_, i) => i !== index)); + }; + + const handleMultiMint = async (values: { payoutPairs: PayoutPair[] }) => { + setIsSigning(true); + try { + const payoutMsg = payout({ + authority: admin, + payoutPairs: values.payoutPairs.map(pair => ({ + address: pair.address, + coin: { + denom: denom.base, + amount: BigInt(parseFloat(pair.amount) * Math.pow(10, exponent)).toString(), + }, + })), + }); + + const encodedMessage = Any.fromPartial({ + typeUrl: payoutMsg.typeUrl, + value: MsgPayout.encode(payoutMsg.value).finish(), + }); + + const msg = submitProposal({ + groupPolicyAddress: admin, + messages: [encodedMessage], + metadata: '', + proposers: [address], + title: `Manifest Module Control: Multi Mint MFX`, + summary: `This proposal includes a multi-mint action for MFX.`, + exec: 0, + }); + + const fee = await estimateFee(address, [msg]); + await tx([msg], { + fee, + onSuccess: () => { + refetch(); + onClose(); + }, + }); + } catch (error) { + console.error('Error during multi-mint:', error); + } finally { + setIsSigning(false); + } + }; + return (
diff --git a/pages/factory/index.tsx b/pages/factory/index.tsx index 880844d3..65d2d04f 100644 --- a/pages/factory/index.tsx +++ b/pages/factory/index.tsx @@ -29,7 +29,6 @@ export default function Factory() { const isError = isDenomsError || isMetadatasError || isBalancesError || isTotalSupplyError; const combinedData = useMemo(() => { - console.log('Recalculating combinedData', { denoms, metadatas, balances, totalSupply }); if (denoms?.denoms && metadatas?.metadatas && balances && totalSupply) { const mfxBalance = balances.find(bal => bal.denom === 'umfx')?.amount || '0'; const mfxSupply = totalSupply.find(supply => supply.denom === 'umfx')?.amount || '0';