diff --git a/.env.example b/.env.example index 5ee02b1..d2bef2d 100644 --- a/.env.example +++ b/.env.example @@ -1,19 +1,16 @@ -APP_VERSION=0.1.0 +APP_VERSION=0.3.0 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS NEXT_PUBLIC_SG721_CODE_ID=274 NEXT_PUBLIC_VENDING_MINTER_CODE_ID=275 NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1j4qn9krchp5xs8nued4j4vcr4j654wxkhf7acy76734xe5fsz08sku28s2" +NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1c6juqgd7cm80afpmuszun66rl9zdc4kgfht8fk34tfq3zk87l78sdxngzv" +NEXT_PUBLIC_BASE_MINTER_CODE_ID=613 NEXT_PUBLIC_WHITELIST_CODE_ID=277 -NEXT_PUBLIC_API_URL=https:// + +NEXT_PUBLIC_API_URL=https://nft-api.elgafar-1.stargaze-apis.com NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze NEXT_PUBLIC_NETWORK=testnet NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev -NEXT_PUBLIC_WEBSITE_URL=https:// - -NEXT_PUBLIC_S3_BUCKET= # TODO -NEXT_PUBLIC_S3_ENDPOINT= # TODO -NEXT_PUBLIC_S3_KEY= # TODO -NEXT_PUBLIC_S3_REGION= # TODO -NEXT_PUBLIC_S3_SECRET= # TODO \ No newline at end of file +NEXT_PUBLIC_WEBSITE_URL=https:// \ No newline at end of file diff --git a/components/AssetsPreview.tsx b/components/AssetsPreview.tsx index 4056657..d0ba432 100644 --- a/components/AssetsPreview.tsx +++ b/components/AssetsPreview.tsx @@ -2,14 +2,18 @@ import clsx from 'clsx' import { useCallback, useMemo, useState } from 'react' import { getAssetType } from 'utils/getAssetType' +import type { MinterType } from './collections/actions/Combobox' +import { Conditional } from './Conditional' + interface AssetsPreviewProps { assetFilesArray: File[] updateMetadataFileIndex: (index: number) => void + minterType: MinterType } const ITEM_NUMBER = 12 -export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: AssetsPreviewProps) => { +export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex, minterType }: AssetsPreviewProps) => { const [page, setPage] = useState(1) const totalPages = useMemo(() => Math.ceil(assetFilesArray.length / ITEM_NUMBER), [assetFilesArray]) @@ -116,23 +120,25 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse return (
{renderImages()}
-
- - - - - -
+ +
+ + + + + +
+
) } diff --git a/components/ConfirmationModal.tsx b/components/ConfirmationModal.tsx index 82ceb70..edaf792 100644 --- a/components/ConfirmationModal.tsx +++ b/components/ConfirmationModal.tsx @@ -40,7 +40,7 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => { />
- Are you sure to create a collection with the specified assets, metadata and parameters? + Are you sure to proceed with the specified assets, metadata and parameters?
+ +
+
+ +
+ + + + {minterType === 'base' && ( +
+ +
+ )} + +
+ + + +
+ + + + + + +
+
+ + +
+ + +
+ + +
+
+ + + + + + + + + +
+ + + + + + + + +
diff --git a/pages/collections/queries.tsx b/pages/collections/queries.tsx index ea9f5b0..ef2697e 100644 --- a/pages/collections/queries.tsx +++ b/pages/collections/queries.tsx @@ -16,7 +16,7 @@ import { withMetadata } from 'utils/layout' import { links } from 'utils/links' const CollectionQueriesPage: NextPage = () => { - const { minter: minterContract, sg721: sg721Contract } = useContracts() + const { baseMinter: baseMinterContract, vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts() const comboboxState = useQueryComboboxState() const type = comboboxState.value?.id @@ -57,19 +57,25 @@ const CollectionQueriesPage: NextPage = () => { const showTokenIdField = type === 'token_info' const showAddressField = type === 'tokens_minted_to_user' - const minterMessages = useMemo( - () => minterContract?.use(minterContractAddress), - [minterContract, minterContractAddress], + const vendingMinterMessages = useMemo( + () => vendingMinterContract?.use(minterContractAddress), + [vendingMinterContract, minterContractAddress], + ) + const baseMinterMessages = useMemo( + () => baseMinterContract?.use(minterContractAddress), + [baseMinterContract, minterContractAddress], ) const sg721Messages = useMemo(() => sg721Contract?.use(sg721ContractAddress), [sg721Contract, sg721ContractAddress]) const { data: response } = useQuery( - [sg721Messages, minterMessages, type, tokenId, address] as const, + [sg721Messages, baseMinterMessages, vendingMinterMessages, type, tokenId, address] as const, async ({ queryKey }) => { - const [_sg721Messages, _minterMessages, _type, _tokenId, _address] = queryKey + const [_sg721Messages, _baseMinterMessages_, _vendingMinterMessages, _type, _tokenId, _address] = queryKey + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const result = await dispatchQuery({ tokenId: _tokenId, - minterMessages: _minterMessages, + vendingMinterMessages: _vendingMinterMessages, + baseMinterMessages: _baseMinterMessages_, sg721Messages: _sg721Messages, address: _address, type: _type, diff --git a/pages/contracts/baseMinter/execute.tsx b/pages/contracts/baseMinter/execute.tsx new file mode 100644 index 0000000..5288e02 --- /dev/null +++ b/pages/contracts/baseMinter/execute.tsx @@ -0,0 +1,145 @@ +import { Button } from 'components/Button' +import { Conditional } from 'components/Conditional' +import { ContractPageHeader } from 'components/ContractPageHeader' +import { ExecuteCombobox } from 'components/contracts/baseMinter/ExecuteCombobox' +import { useExecuteComboboxState } from 'components/contracts/baseMinter/ExecuteCombobox.hooks' +import { FormControl } from 'components/FormControl' +import { AddressInput, TextInput } from 'components/forms/FormInput' +import { useInputState } from 'components/forms/FormInput.hooks' +import { InputDateTime } from 'components/InputDateTime' +import { JsonPreview } from 'components/JsonPreview' +import { LinkTabs } from 'components/LinkTabs' +import { baseMinterLinkTabs } from 'components/LinkTabs.data' +import { TransactionHash } from 'components/TransactionHash' +import { useContracts } from 'contexts/contracts' +import { useWallet } from 'contexts/wallet' +import type { DispatchExecuteArgs } from 'contracts/baseMinter/messages/execute' +import { dispatchExecute, previewExecutePayload } from 'contracts/baseMinter/messages/execute' +import type { NextPage } from 'next' +import { useRouter } from 'next/router' +import { NextSeo } from 'next-seo' +import type { FormEvent } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { toast } from 'react-hot-toast' +import { FaArrowRight } from 'react-icons/fa' +import { useMutation } from 'react-query' +import { withMetadata } from 'utils/layout' +import { links } from 'utils/links' + +const BaseMinterExecutePage: NextPage = () => { + const { baseMinter: contract } = useContracts() + const wallet = useWallet() + const [lastTx, setLastTx] = useState('') + + const [timestamp, setTimestamp] = useState(undefined) + + const comboboxState = useExecuteComboboxState() + const type = comboboxState.value?.id + + const contractState = useInputState({ + id: 'contract-address', + name: 'contract-address', + title: 'Base Minter Address', + subtitle: 'Address of the Base Minter contract', + }) + + const tokenUriState = useInputState({ + id: 'token-uri', + name: 'token-uri', + title: 'Token URI', + placeholder: 'ipfs://', + }) + const contractAddress = contractState.value + + const showDateField = type === 'update_start_trading_time' + const showTokenUriField = type === 'mint' + + const messages = useMemo(() => contract?.use(contractState.value), [contract, wallet.address, contractState.value]) + const payload: DispatchExecuteArgs = { + startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '', + tokenUri: tokenUriState.value, + contract: contractState.value, + messages, + txSigner: wallet.address, + type, + } + const { isLoading, mutate } = useMutation( + async (event: FormEvent) => { + event.preventDefault() + if (!type) { + throw new Error('Please select message type!') + } + if (!wallet.initialized) { + throw new Error('Please connect your wallet.') + } + const txHash = await toast.promise(dispatchExecute(payload), { + error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`, + loading: 'Executing message...', + success: (tx) => `Transaction ${tx} success!`, + }) + if (txHash) { + setLastTx(txHash) + } + }, + { + onError: (error) => { + toast.error(String(error), { style: { maxWidth: 'none' } }) + }, + }, + ) + + const router = useRouter() + + useEffect(() => { + if (contractAddress.length > 0) { + void router.replace({ query: { contractAddress } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contractAddress]) + useEffect(() => { + const initial = new URL(document.URL).searchParams.get('contractAddress') + if (initial && initial.length > 0) contractState.onChange(initial) + }, []) + + return ( +
+ + + + + +
+ + + + + + + + setTimestamp(date)} value={timestamp} /> + + +
+
+
+ + + + +
+ + + +
+ +
+ ) +} + +export default withMetadata(BaseMinterExecutePage, { center: false }) diff --git a/pages/contracts/minter/index.tsx b/pages/contracts/baseMinter/index.tsx similarity index 100% rename from pages/contracts/minter/index.tsx rename to pages/contracts/baseMinter/index.tsx diff --git a/pages/contracts/baseMinter/instantiate.tsx b/pages/contracts/baseMinter/instantiate.tsx new file mode 100644 index 0000000..8f54656 --- /dev/null +++ b/pages/contracts/baseMinter/instantiate.tsx @@ -0,0 +1,259 @@ +import { coin } from '@cosmjs/proto-signing' +import { Alert } from 'components/Alert' +import { Button } from 'components/Button' +import { Conditional } from 'components/Conditional' +import { ContractPageHeader } from 'components/ContractPageHeader' +import { FormControl } from 'components/FormControl' +import { FormGroup } from 'components/FormGroup' +import { NumberInput, TextInput } from 'components/forms/FormInput' +import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' +import { FormTextArea } from 'components/forms/FormTextArea' +import { InputDateTime } from 'components/InputDateTime' +import { JsonPreview } from 'components/JsonPreview' +import { LinkTabs } from 'components/LinkTabs' +import { baseMinterLinkTabs } from 'components/LinkTabs.data' +import { useContracts } from 'contexts/contracts' +import { useWallet } from 'contexts/wallet' +import type { NextPage } from 'next' +import { NextSeo } from 'next-seo' +import type { FormEvent } from 'react' +import { useState } from 'react' +import { toast } from 'react-hot-toast' +import { FaAsterisk } from 'react-icons/fa' +import { useMutation } from 'react-query' +import { BASE_FACTORY_ADDRESS } from 'utils/constants' +import { withMetadata } from 'utils/layout' +import { links } from 'utils/links' + +import type { CreateBaseMinterResponse } from '../../../contracts/baseFactory/contract' +import { SG721_CODE_ID } from '../../../utils/constants' + +const BaseMinterInstantiatePage: NextPage = () => { + const wallet = useWallet() + const contract = useContracts().baseFactory + + const [timestamp, setTimestamp] = useState() + const [explicit, setExplicit] = useState(false) + + const nameState = useInputState({ + id: 'name', + name: 'name', + title: 'Name', + placeholder: 'My Awesome SG721 Contract', + subtitle: 'Name of the sg721 contract', + }) + + const symbolState = useInputState({ + id: 'symbol', + name: 'symbol', + title: 'Symbol', + placeholder: 'AWSM', + subtitle: 'Symbol of the sg721 contract', + }) + + const codeIdState = useNumberInputState({ + id: 'code-id', + name: 'code-id', + title: 'Code ID', + subtitle: 'Code ID for the sg721 contract', + placeholder: '1', + defaultValue: SG721_CODE_ID, + }) + + const creatorState = useInputState({ + id: 'creator-address', + name: 'creatorAddress', + title: 'Creator Address', + placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...', + subtitle: 'Address of the collection creator', + defaultValue: wallet.address, + }) + + const descriptionState = useInputState({ + id: 'description', + name: 'description', + title: 'Description', + subtitle: 'Description of the collection', + }) + + const imageState = useInputState({ + id: 'image', + name: 'image', + title: 'Image', + subtitle: 'Image of the collection', + placeholder: 'ipfs://bafybe....', + }) + + const externalLinkState = useInputState({ + id: 'external-link', + name: 'externalLink', + title: 'External Link (optional)', + subtitle: 'External link to the collection', + }) + + const royaltyPaymentAddressState = useInputState({ + id: 'royalty-payment-address', + name: 'royaltyPaymentAddress', + title: 'Payment Address', + subtitle: 'Address to receive royalties', + placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...', + }) + + const royaltyShareState = useInputState({ + id: 'royalty-share', + name: 'royaltyShare', + title: 'Share Percentage', + subtitle: 'Percentage of royalties to be paid', + placeholder: '8%', + }) + + const { data, isLoading, mutate } = useMutation( + async (event: FormEvent): Promise => { + event.preventDefault() + if (!contract) { + throw new Error('Smart contract connection failed') + } + + let royaltyInfo = null + if (royaltyPaymentAddressState.value && royaltyShareState.value) { + royaltyInfo = { + payment_address: royaltyPaymentAddressState.value, + share: (Number(royaltyShareState.value) / 100).toString(), + } + } + + const msg = { + create_minter: { + collection_params: { + code_id: codeIdState.value, + name: nameState.value, + symbol: symbolState.value, + info: { + creator: creatorState.value, + description: descriptionState.value, + image: imageState.value, + external_link: externalLinkState.value || null, + explicit_content: explicit, + start_trading_time: timestamp ? (timestamp.getTime() * 1_000_000).toString() : null, + royalty_info: royaltyInfo, + }, + }, + }, + } + + return toast.promise( + contract + .use(BASE_FACTORY_ADDRESS) + ?.createBaseMinter(wallet.address, msg, [coin('1000000000', 'ustars')]) as Promise, + { + loading: 'Instantiating contract...', + error: 'Instantiation failed!', + success: 'Instantiation success!', + }, + ) + }, + { + onError: (error) => { + toast.error(String(error), { style: { maxWidth: 'none' } }) + }, + }, + ) + + const txHash = data?.transactionHash + + return ( +
+ + + + + + + Instantiate success! Here is the transaction result containing the contract address and the transaction + hash. + + +
+
+ + + + + + + + + + + + + + setTimestamp(date)} value={timestamp} /> + +
+
+
+ + Does the collection contain explicit content? + +
+ { + setExplicit(true) + }} + type="radio" + /> + +
+
+ { + setExplicit(false) + }} + type="radio" + /> + +
+
+
+
+
+ + + + + + +
+
+ +
+ + ) +} + +export default withMetadata(BaseMinterInstantiatePage, { center: false }) diff --git a/pages/contracts/minter/migrate.tsx b/pages/contracts/baseMinter/migrate.tsx similarity index 83% rename from pages/contracts/minter/migrate.tsx rename to pages/contracts/baseMinter/migrate.tsx index 3389bf7..60e8b25 100644 --- a/pages/contracts/minter/migrate.tsx +++ b/pages/contracts/baseMinter/migrate.tsx @@ -1,16 +1,16 @@ import { Button } from 'components/Button' import { ContractPageHeader } from 'components/ContractPageHeader' -import { useExecuteComboboxState } from 'components/contracts/minter/ExecuteCombobox.hooks' +import { useExecuteComboboxState } from 'components/contracts/baseMinter/ExecuteCombobox.hooks' import { FormControl } from 'components/FormControl' import { AddressInput, NumberInput } from 'components/forms/FormInput' import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { JsonPreview } from 'components/JsonPreview' import { LinkTabs } from 'components/LinkTabs' -import { minterLinkTabs } from 'components/LinkTabs.data' +import { baseMinterLinkTabs } from 'components/LinkTabs.data' import { TransactionHash } from 'components/TransactionHash' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' -import type { MigrateResponse } from 'contracts/minter' +import type { MigrateResponse } from 'contracts/baseMinter' import type { NextPage } from 'next' import { useRouter } from 'next/router' import { NextSeo } from 'next-seo' @@ -22,8 +22,8 @@ import { useMutation } from 'react-query' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' -const MinterMigratePage: NextPage = () => { - const { minter: contract } = useContracts() +const BaseMinterMigratePage: NextPage = () => { + const { baseMinter: contract } = useContracts() const wallet = useWallet() const [lastTx, setLastTx] = useState('') @@ -34,15 +34,15 @@ const MinterMigratePage: NextPage = () => { id: 'code-id', name: 'code-id', title: 'Code ID', - subtitle: 'Code ID of the New Minter', + subtitle: 'Code ID of the New Base Minter', placeholder: '1', }) const contractState = useInputState({ id: 'contract-address', name: 'contract-address', - title: 'Minter Address', - subtitle: 'Address of the Minter contract', + title: 'Base Minter Address', + subtitle: 'Address of the Base Minter contract', }) const contractAddress = contractState.value @@ -90,13 +90,13 @@ const MinterMigratePage: NextPage = () => { return (
- + - +
@@ -129,4 +129,4 @@ const MinterMigratePage: NextPage = () => { ) } -export default withMetadata(MinterMigratePage, { center: false }) +export default withMetadata(BaseMinterMigratePage, { center: false }) diff --git a/pages/contracts/baseMinter/query.tsx b/pages/contracts/baseMinter/query.tsx new file mode 100644 index 0000000..2e76d00 --- /dev/null +++ b/pages/contracts/baseMinter/query.tsx @@ -0,0 +1,116 @@ +import clsx from 'clsx' +import { ContractPageHeader } from 'components/ContractPageHeader' +import { FormControl } from 'components/FormControl' +import { AddressInput } from 'components/forms/FormInput' +import { useInputState } from 'components/forms/FormInput.hooks' +import { JsonPreview } from 'components/JsonPreview' +import { LinkTabs } from 'components/LinkTabs' +import { baseMinterLinkTabs } from 'components/LinkTabs.data' +import { useContracts } from 'contexts/contracts' +import { useWallet } from 'contexts/wallet' +import type { QueryType } from 'contracts/baseMinter/messages/query' +import { dispatchQuery, QUERY_LIST } from 'contracts/baseMinter/messages/query' +import type { NextPage } from 'next' +import { useRouter } from 'next/router' +import { NextSeo } from 'next-seo' +import { useEffect, useState } from 'react' +import { toast } from 'react-hot-toast' +import { useQuery } from 'react-query' +import { withMetadata } from 'utils/layout' +import { links } from 'utils/links' + +const BaseMinterQueryPage: NextPage = () => { + const { baseMinter: contract } = useContracts() + const wallet = useWallet() + + const contractState = useInputState({ + id: 'contract-address', + name: 'contract-address', + title: 'Base Minter Address', + subtitle: 'Address of the Base Minter contract', + }) + const contractAddress = contractState.value + + const addressState = useInputState({ + id: 'address', + name: 'address', + title: 'Address', + subtitle: 'Address of the user - defaults to current address', + }) + const address = addressState.value + + const [type, setType] = useState('config') + + const { data: response } = useQuery( + [contractAddress, type, contract, wallet, address] as const, + async ({ queryKey }) => { + const [_contractAddress, _type, _contract, _wallet] = queryKey + const messages = contract?.use(_contractAddress) + const result = await dispatchQuery({ + address, + messages, + type, + }) + return result + }, + { + placeholderData: null, + onError: (error: any) => { + toast.error(error.message, { style: { maxWidth: 'none' } }) + }, + enabled: Boolean(contractAddress && contract && wallet), + }, + ) + + const router = useRouter() + + useEffect(() => { + if (contractAddress.length > 0) { + void router.replace({ query: { contractAddress } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contractAddress]) + useEffect(() => { + const initial = new URL(document.URL).searchParams.get('contractAddress') + if (initial && initial.length > 0) contractState.onChange(initial) + }, []) + + return ( +
+ + + + +
+
+ + + + +
+ +
+
+ ) +} + +export default withMetadata(BaseMinterQueryPage, { center: false }) diff --git a/pages/contracts/minter/execute.tsx b/pages/contracts/vendingMinter/execute.tsx similarity index 87% rename from pages/contracts/minter/execute.tsx rename to pages/contracts/vendingMinter/execute.tsx index 1cca98f..ff6caf9 100644 --- a/pages/contracts/minter/execute.tsx +++ b/pages/contracts/vendingMinter/execute.tsx @@ -1,20 +1,20 @@ import { Button } from 'components/Button' import { Conditional } from 'components/Conditional' import { ContractPageHeader } from 'components/ContractPageHeader' -import { ExecuteCombobox } from 'components/contracts/minter/ExecuteCombobox' -import { useExecuteComboboxState } from 'components/contracts/minter/ExecuteCombobox.hooks' +import { ExecuteCombobox } from 'components/contracts/vendingMinter/ExecuteCombobox' +import { useExecuteComboboxState } from 'components/contracts/vendingMinter/ExecuteCombobox.hooks' import { FormControl } from 'components/FormControl' import { AddressInput, NumberInput } from 'components/forms/FormInput' import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { InputDateTime } from 'components/InputDateTime' import { JsonPreview } from 'components/JsonPreview' import { LinkTabs } from 'components/LinkTabs' -import { minterLinkTabs } from 'components/LinkTabs.data' +import { vendingMinterLinkTabs } from 'components/LinkTabs.data' import { TransactionHash } from 'components/TransactionHash' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' -import type { DispatchExecuteArgs } from 'contracts/minter/messages/execute' -import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/minter/messages/execute' +import type { DispatchExecuteArgs } from 'contracts/vendingMinter/messages/execute' +import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/vendingMinter/messages/execute' import type { NextPage } from 'next' import { useRouter } from 'next/router' import { NextSeo } from 'next-seo' @@ -26,8 +26,8 @@ import { useMutation } from 'react-query' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' -const MinterExecutePage: NextPage = () => { - const { minter: contract } = useContracts() +const VendingMinterExecutePage: NextPage = () => { + const { vendingMinter: contract } = useContracts() const wallet = useWallet() const [lastTx, setLastTx] = useState('') @@ -60,8 +60,8 @@ const MinterExecutePage: NextPage = () => { const contractState = useInputState({ id: 'contract-address', name: 'contract-address', - title: 'Minter Address', - subtitle: 'Address of the Minter contract', + title: 'Vending Minter Address', + subtitle: 'Address of the Vending Minter contract', }) const contractAddress = contractState.value @@ -139,13 +139,13 @@ const MinterExecutePage: NextPage = () => { return (
- + - +
@@ -181,4 +181,4 @@ const MinterExecutePage: NextPage = () => { ) } -export default withMetadata(MinterExecutePage, { center: false }) +export default withMetadata(VendingMinterExecutePage, { center: false }) diff --git a/pages/contracts/vendingMinter/index.tsx b/pages/contracts/vendingMinter/index.tsx new file mode 100644 index 0000000..561b4b3 --- /dev/null +++ b/pages/contracts/vendingMinter/index.tsx @@ -0,0 +1 @@ +export { default } from './instantiate' diff --git a/pages/contracts/minter/instantiate.tsx b/pages/contracts/vendingMinter/instantiate.tsx similarity index 93% rename from pages/contracts/minter/instantiate.tsx rename to pages/contracts/vendingMinter/instantiate.tsx index 56d6b16..1f8a64a 100644 --- a/pages/contracts/minter/instantiate.tsx +++ b/pages/contracts/vendingMinter/instantiate.tsx @@ -11,7 +11,7 @@ import { FormTextArea } from 'components/forms/FormTextArea' import { InputDateTime } from 'components/InputDateTime' import { JsonPreview } from 'components/JsonPreview' import { LinkTabs } from 'components/LinkTabs' -import { minterLinkTabs } from 'components/LinkTabs.data' +import { vendingMinterLinkTabs } from 'components/LinkTabs.data' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' import type { NextPage } from 'next' @@ -25,9 +25,9 @@ import { VENDING_FACTORY_ADDRESS } from 'utils/constants' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' -import type { CreateMinterResponse } from '../../../contracts/vendingFactory/contract' +import type { CreateVendingMinterResponse } from '../../../contracts/vendingFactory/contract' -const MinterInstantiatePage: NextPage = () => { +const VendingMinterInstantiatePage: NextPage = () => { const wallet = useWallet() const contract = useContracts().vendingFactory @@ -146,7 +146,7 @@ const MinterInstantiatePage: NextPage = () => { }) const { data, isLoading, mutate } = useMutation( - async (event: FormEvent): Promise => { + async (event: FormEvent): Promise => { event.preventDefault() if (!contract) { throw new Error('Smart contract connection failed') @@ -206,7 +206,9 @@ const MinterInstantiatePage: NextPage = () => { return toast.promise( contract .use(VENDING_FACTORY_ADDRESS) - ?.createMinter(wallet.address, msg, [coin('1000000000', 'ustars')]) as Promise, + ?.createVendingMinter(wallet.address, msg, [ + coin('1000000000', 'ustars'), + ]) as Promise, { loading: 'Instantiating contract...', error: 'Instantiation failed!', @@ -225,13 +227,13 @@ const MinterInstantiatePage: NextPage = () => { return ( - + - + @@ -329,4 +331,4 @@ const MinterInstantiatePage: NextPage = () => { ) } -export default withMetadata(MinterInstantiatePage, { center: false }) +export default withMetadata(VendingMinterInstantiatePage, { center: false }) diff --git a/pages/contracts/vendingMinter/migrate.tsx b/pages/contracts/vendingMinter/migrate.tsx new file mode 100644 index 0000000..5e4952d --- /dev/null +++ b/pages/contracts/vendingMinter/migrate.tsx @@ -0,0 +1,132 @@ +import { Button } from 'components/Button' +import { ContractPageHeader } from 'components/ContractPageHeader' +import { useExecuteComboboxState } from 'components/contracts/vendingMinter/ExecuteCombobox.hooks' +import { FormControl } from 'components/FormControl' +import { AddressInput, NumberInput } from 'components/forms/FormInput' +import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' +import { JsonPreview } from 'components/JsonPreview' +import { LinkTabs } from 'components/LinkTabs' +import { vendingMinterLinkTabs } from 'components/LinkTabs.data' +import { TransactionHash } from 'components/TransactionHash' +import { useContracts } from 'contexts/contracts' +import { useWallet } from 'contexts/wallet' +import type { MigrateResponse } from 'contracts/vendingMinter' +import type { NextPage } from 'next' +import { useRouter } from 'next/router' +import { NextSeo } from 'next-seo' +import type { FormEvent } from 'react' +import { useEffect, useState } from 'react' +import { toast } from 'react-hot-toast' +import { FaArrowRight } from 'react-icons/fa' +import { useMutation } from 'react-query' +import { withMetadata } from 'utils/layout' +import { links } from 'utils/links' + +const VendingMinterMigratePage: NextPage = () => { + const { vendingMinter: contract } = useContracts() + const wallet = useWallet() + + const [lastTx, setLastTx] = useState('') + + const comboboxState = useExecuteComboboxState() + const type = comboboxState.value?.id + const codeIdState = useNumberInputState({ + id: 'code-id', + name: 'code-id', + title: 'Code ID', + subtitle: 'Code ID of the New Vending Minter', + placeholder: '1', + }) + + const contractState = useInputState({ + id: 'contract-address', + name: 'contract-address', + title: 'Vending Minter Address', + subtitle: 'Address of the Vending Minter contract', + }) + const contractAddress = contractState.value + + const { data, isLoading, mutate } = useMutation( + async (event: FormEvent): Promise => { + event.preventDefault() + if (!contract) { + throw new Error('Smart contract connection failed') + } + if (!wallet.initialized) { + throw new Error('Please connect your wallet.') + } + + const migrateMsg = {} + return toast.promise(contract.migrate(contractAddress, codeIdState.value, migrateMsg), { + error: `Migration failed!`, + loading: 'Executing message...', + success: (tx) => { + if (tx) { + setLastTx(tx.transactionHash) + } + return `Transaction success!` + }, + }) + }, + { + onError: (error) => { + toast.error(String(error)) + }, + }, + ) + + const router = useRouter() + + useEffect(() => { + if (contractAddress.length > 0) { + void router.replace({ query: { contractAddress } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contractAddress]) + useEffect(() => { + const initial = new URL(document.URL).searchParams.get('contractAddress') + if (initial && initial.length > 0) contractState.onChange(initial) + }, []) + + return ( +
+ + + + + +
+ + +
+
+
+ + + + +
+ + + +
+ +
+ ) +} + +export default withMetadata(VendingMinterMigratePage, { center: false }) diff --git a/pages/contracts/minter/query.tsx b/pages/contracts/vendingMinter/query.tsx similarity index 82% rename from pages/contracts/minter/query.tsx rename to pages/contracts/vendingMinter/query.tsx index 4f57350..f40747b 100644 --- a/pages/contracts/minter/query.tsx +++ b/pages/contracts/vendingMinter/query.tsx @@ -6,11 +6,11 @@ import { AddressInput } from 'components/forms/FormInput' import { useInputState } from 'components/forms/FormInput.hooks' import { JsonPreview } from 'components/JsonPreview' import { LinkTabs } from 'components/LinkTabs' -import { minterLinkTabs } from 'components/LinkTabs.data' +import { vendingMinterLinkTabs } from 'components/LinkTabs.data' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' -import type { QueryType } from 'contracts/minter/messages/query' -import { dispatchQuery, QUERY_LIST } from 'contracts/minter/messages/query' +import type { QueryType } from 'contracts/vendingMinter/messages/query' +import { dispatchQuery, QUERY_LIST } from 'contracts/vendingMinter/messages/query' import type { NextPage } from 'next' import { useRouter } from 'next/router' import { NextSeo } from 'next-seo' @@ -20,15 +20,15 @@ import { useQuery } from 'react-query' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' -const MinterQueryPage: NextPage = () => { - const { minter: contract } = useContracts() +const VendingMinterQueryPage: NextPage = () => { + const { vendingMinter: contract } = useContracts() const wallet = useWallet() const contractState = useInputState({ id: 'contract-address', name: 'contract-address', - title: 'Minter Address', - subtitle: 'Address of the Minter contract', + title: 'Vending Minter Address', + subtitle: 'Address of the Vending Minter contract', }) const contractAddress = contractState.value @@ -78,13 +78,13 @@ const MinterQueryPage: NextPage = () => { return (
- + - +
@@ -117,4 +117,4 @@ const MinterQueryPage: NextPage = () => { ) } -export default withMetadata(MinterQueryPage, { center: false }) +export default withMetadata(VendingMinterQueryPage, { center: false }) diff --git a/tailwind.config.js b/tailwind.config.js index c03ab1b..9a49a04 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,7 +12,7 @@ module.exports = { theme: { extend: { colors: { - stargaze: { DEFAULT: '#DB2676' }, + stargaze: { DEFAULT: '#DB2676', 80: '#C81F71' }, dark: { DEFAULT: '#06090B' }, gray: { DEFAULT: '#A9A9A9' }, 'dark-gray': { DEFAULT: '#191D20' }, diff --git a/utils/clipboard.ts b/utils/clipboard.ts index d5487b1..a4b3c09 100644 --- a/utils/clipboard.ts +++ b/utils/clipboard.ts @@ -1,5 +1,5 @@ +import type { Renderable } from 'react-hot-toast' import { toast } from 'react-hot-toast' -import type { Renderable } from 'react-hot-toast/dist/core/types' export async function copy(text: string, message: Renderable = 'Copied to clipboard!') { try { diff --git a/utils/constants.ts b/utils/constants.ts index b6933db..38c4d3a 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -2,6 +2,8 @@ export const SG721_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_CODE_ID, 10) export const WHITELIST_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_CODE_ID, 10) export const VENDING_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10) export const VENDING_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_ADDRESS +export const BASE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_ADDRESS +export const BASE_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10) export const PINATA_ENDPOINT_URL = process.env.NEXT_PUBLIC_PINATA_ENDPOINT_URL export const NETWORK = process.env.NEXT_PUBLIC_NETWORK diff --git a/yarn.lock b/yarn.lock index 8034166..159965f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4922,10 +4922,10 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" -goober@^2.1.1: - version "2.1.9" - resolved "https://registry.npmjs.org/goober/-/goober-2.1.9.tgz" - integrity sha512-PAtnJbrWtHbfpJUIveG5PJIB6Mc9Kd0gimu9wZwPyA+wQUSeOeA4x4Ug16lyaaUUKZ/G6QEH1xunKOuXP1F4Vw== +goober@^2.1.10: + version "2.1.11" + resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.11.tgz#bbd71f90d2df725397340f808dbe7acc3118e610" + integrity sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A== hamt-sharding@^2.0.0: version "2.0.1" @@ -6807,11 +6807,11 @@ react-hook-form@^7: integrity sha512-DzjiM6o2vtDGNMB9I4yCqW8J21P314SboNG1O0obROkbg7KVS0I7bMtwSdKyapnCPjHgnxc3L7E5PEdISeEUcQ== react-hot-toast@^2: - version "2.2.0" - resolved "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.2.0.tgz" - integrity sha512-248rXw13uhf/6TNDVzagX+y7R8J183rp7MwUMNkcrBRyHj/jWOggfXTGlM8zAOuh701WyVW+eUaWG2LeSufX9g== + version "2.4.0" + resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.0.tgz#b91e7a4c1b6e3068fc599d3d83b4fb48668ae51d" + integrity sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA== dependencies: - goober "^2.1.1" + goober "^2.1.10" react-icons@^4: version "4.3.1"