From a8c25485544cd1633f4ded748b732d5ba991a375 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 9 Dec 2022 11:27:50 +0300 Subject: [PATCH 01/19] Implement Base Minter Contract dashboard --- .env.example | 4 +- components/LinkTabs.data.ts | 41 ++- components/Sidebar.tsx | 7 +- components/collections/actions/Action.tsx | 8 +- components/collections/actions/actions.ts | 74 ++--- components/collections/queries/Queries.tsx | 2 +- components/collections/queries/query.ts | 16 +- .../ExecuteCombobox.hooks.ts | 2 +- .../ExecuteCombobox.tsx | 4 +- .../vendingMinter/ExecuteCombobox.hooks.ts | 7 + .../vendingMinter/ExecuteCombobox.tsx | 92 +++++++ contexts/contracts.tsx | 26 +- contracts/baseFactory/contract.ts | 93 +++++++ contracts/{minter => baseFactory}/index.ts | 0 contracts/baseFactory/messages/execute.ts | 28 ++ contracts/baseFactory/useContract.ts | 57 ++++ contracts/baseMinter/contract.ts | 243 ++++++++++++++++ contracts/baseMinter/index.ts | 2 + contracts/baseMinter/messages/execute.ts | 82 ++++++ contracts/baseMinter/messages/query.ts | 37 +++ contracts/baseMinter/useContract.ts | 102 +++++++ contracts/sg721/contract.ts | 2 +- contracts/vendingFactory/contract.ts | 26 +- contracts/vendingFactory/messages/execute.ts | 4 +- .../{minter => vendingMinter}/contract.ts | 14 +- contracts/vendingMinter/index.ts | 2 + .../messages/execute.ts | 10 +- .../messages/query.ts | 4 +- .../{minter => vendingMinter}/useContract.ts | 42 +-- env.d.ts | 2 + pages/collections/actions.tsx | 12 +- pages/collections/create.tsx | 22 +- pages/collections/queries.tsx | 14 +- pages/contracts/baseMinter/execute.tsx | 145 ++++++++++ .../{minter => baseMinter}/index.tsx | 0 pages/contracts/baseMinter/instantiate.tsx | 259 ++++++++++++++++++ .../{minter => baseMinter}/migrate.tsx | 26 +- pages/contracts/baseMinter/query.tsx | 116 ++++++++ .../{minter => vendingMinter}/execute.tsx | 28 +- pages/contracts/vendingMinter/index.tsx | 1 + .../{minter => vendingMinter}/instantiate.tsx | 22 +- pages/contracts/vendingMinter/migrate.tsx | 132 +++++++++ .../{minter => vendingMinter}/query.tsx | 24 +- utils/constants.ts | 2 + 44 files changed, 1641 insertions(+), 195 deletions(-) rename components/contracts/{minter => baseMinter}/ExecuteCombobox.hooks.ts (74%) rename components/contracts/{minter => baseMinter}/ExecuteCombobox.tsx (95%) create mode 100644 components/contracts/vendingMinter/ExecuteCombobox.hooks.ts create mode 100644 components/contracts/vendingMinter/ExecuteCombobox.tsx create mode 100644 contracts/baseFactory/contract.ts rename contracts/{minter => baseFactory}/index.ts (100%) create mode 100644 contracts/baseFactory/messages/execute.ts create mode 100644 contracts/baseFactory/useContract.ts create mode 100644 contracts/baseMinter/contract.ts create mode 100644 contracts/baseMinter/index.ts create mode 100644 contracts/baseMinter/messages/execute.ts create mode 100644 contracts/baseMinter/messages/query.ts create mode 100644 contracts/baseMinter/useContract.ts rename contracts/{minter => vendingMinter}/contract.ts (97%) create mode 100644 contracts/vendingMinter/index.ts rename contracts/{minter => vendingMinter}/messages/execute.ts (95%) rename contracts/{minter => vendingMinter}/messages/query.ts (93%) rename contracts/{minter => vendingMinter}/useContract.ts (65%) create mode 100644 pages/contracts/baseMinter/execute.tsx rename pages/contracts/{minter => baseMinter}/index.tsx (100%) create mode 100644 pages/contracts/baseMinter/instantiate.tsx rename pages/contracts/{minter => baseMinter}/migrate.tsx (83%) create mode 100644 pages/contracts/baseMinter/query.tsx rename pages/contracts/{minter => vendingMinter}/execute.tsx (87%) create mode 100644 pages/contracts/vendingMinter/index.tsx rename pages/contracts/{minter => vendingMinter}/instantiate.tsx (93%) create mode 100644 pages/contracts/vendingMinter/migrate.tsx rename pages/contracts/{minter => vendingMinter}/query.tsx (82%) diff --git a/.env.example b/.env.example index 5ee02b1..377c3be 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,11 @@ -APP_VERSION=0.1.0 +APP_VERSION=0.2.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="" +NEXT_PUBLIC_BASE_MINTER_CODE_ID=275 NEXT_PUBLIC_WHITELIST_CODE_ID=277 NEXT_PUBLIC_API_URL=https:// diff --git a/components/LinkTabs.data.ts b/components/LinkTabs.data.ts index c9c7b39..81ba9bb 100644 --- a/components/LinkTabs.data.ts +++ b/components/LinkTabs.data.ts @@ -18,26 +18,49 @@ export const sg721LinkTabs: LinkTabProps[] = [ }, ] -export const minterLinkTabs: LinkTabProps[] = [ +export const vendingMinterLinkTabs: LinkTabProps[] = [ { title: 'Instantiate', - description: `Initialize a new Minter contract`, - href: '/contracts/minter/instantiate', + description: `Initialize a new Vending Minter contract`, + href: '/contracts/vendingMinter/instantiate', }, { title: 'Query', - description: `Dispatch queries with your Minter contract`, - href: '/contracts/minter/query', + description: `Dispatch queries with your Vending Minter contract`, + href: '/contracts/vendingMinter/query', }, { title: 'Execute', - description: `Execute Minter contract actions`, - href: '/contracts/minter/execute', + description: `Execute Vending Minter contract actions`, + href: '/contracts/vendingMinter/execute', }, { title: 'Migrate', - description: `Migrate Minter contract`, - href: '/contracts/minter/migrate', + description: `Migrate Vending Minter contract`, + href: '/contracts/vendingMinter/migrate', + }, +] + +export const baseMinterLinkTabs: LinkTabProps[] = [ + { + title: 'Instantiate', + description: `Initialize a new Base Minter contract`, + href: '/contracts/baseMinter/instantiate', + }, + { + title: 'Query', + description: `Dispatch queries with your Base Minter contract`, + href: '/contracts/baseMinter/query', + }, + { + title: 'Execute', + description: `Execute Base Minter contract actions`, + href: '/contracts/baseMinter/execute', + }, + { + title: 'Migrate', + description: `Migrate Base Minter contract`, + href: '/contracts/baseMinter/migrate', }, ] diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 345d265..399dcae 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -14,7 +14,8 @@ const routes = [ { text: 'My Collections', href: `/collections/myCollections/`, isChild: true }, { text: 'Collection Actions', href: `/collections/actions/`, isChild: true }, { text: 'Contract Dashboards', href: `/contracts/`, isChild: false }, - { text: 'Minter Contract', href: `/contracts/minter/`, isChild: true }, + { text: 'Base Minter Contract', href: `/contracts/baseMinter/`, isChild: true }, + { text: 'Vending Minter Contract', href: `/contracts/vendingMinter/`, isChild: true }, { text: 'SG721 Contract', href: `/contracts/sg721/`, isChild: true }, { text: 'Whitelist Contract', href: `/contracts/whitelist/`, isChild: true }, ] @@ -37,9 +38,9 @@ export const Sidebar = () => { { const wallet = useWallet() const [lastTx, setLastTx] = useState('') @@ -169,7 +169,7 @@ export const CollectionActions = ({ tokenId: tokenIdState.value, tokenIds: tokenIdListState.value, batchNumber: batchNumberState.value, - minterMessages, + vendingMinterMessages, sg721Messages, recipient: recipientState.value, recipients: airdropArray, diff --git a/components/collections/actions/actions.ts b/components/collections/actions/actions.ts index 86700f4..b664724 100644 --- a/components/collections/actions/actions.ts +++ b/components/collections/actions/actions.ts @@ -1,7 +1,7 @@ -import type { MinterInstance } from 'contracts/minter' -import { useMinterContract } from 'contracts/minter' import type { CollectionInfo, SG721Instance } from 'contracts/sg721' import { useSG721Contract } from 'contracts/sg721' +import type { VendingMinterInstance } from 'contracts/vendingMinter' +import { useVendingMinterContract } from 'contracts/vendingMinter' export type ActionType = typeof ACTION_TYPES[number] @@ -150,11 +150,11 @@ export interface DispatchExecuteProps { type Select = T -/** @see {@link MinterInstance} */ +/** @see {@link VendingMinterInstance} */ export type DispatchExecuteArgs = { minterContract: string sg721Contract: string - minterMessages?: MinterInstance + vendingMinterMessages?: VendingMinterInstance sg721Messages?: SG721Instance txSigner: string } & ( @@ -183,40 +183,40 @@ export type DispatchExecuteArgs = { ) export const dispatchExecute = async (args: DispatchExecuteArgs) => { - const { minterMessages, sg721Messages, txSigner } = args - if (!minterMessages || !sg721Messages) { + const { vendingMinterMessages, sg721Messages, txSigner } = args + if (!vendingMinterMessages || !sg721Messages) { throw new Error('Cannot execute actions') } switch (args.type) { case 'mint': { - return minterMessages.mint(txSigner) + return vendingMinterMessages.mint(txSigner) } case 'purge': { - return minterMessages.purge(txSigner) + return vendingMinterMessages.purge(txSigner) } case 'update_mint_price': { - return minterMessages.updateMintPrice(txSigner, args.price) + return vendingMinterMessages.updateMintPrice(txSigner, args.price) } case 'mint_to': { - return minterMessages.mintTo(txSigner, args.recipient) + return vendingMinterMessages.mintTo(txSigner, args.recipient) } case 'mint_for': { - return minterMessages.mintFor(txSigner, args.recipient, args.tokenId) + return vendingMinterMessages.mintFor(txSigner, args.recipient, args.tokenId) } case 'batch_mint': { - return minterMessages.batchMint(txSigner, args.recipient, args.batchNumber) + return vendingMinterMessages.batchMint(txSigner, args.recipient, args.batchNumber) } case 'set_whitelist': { - return minterMessages.setWhitelist(txSigner, args.whitelist) + return vendingMinterMessages.setWhitelist(txSigner, args.whitelist) } case 'update_start_time': { - return minterMessages.updateStartTime(txSigner, args.startTime) + return vendingMinterMessages.updateStartTime(txSigner, args.startTime) } case 'update_start_trading_time': { - return minterMessages.updateStartTradingTime(txSigner, args.startTime) + return vendingMinterMessages.updateStartTradingTime(txSigner, args.startTime) } case 'update_per_address_limit': { - return minterMessages.updatePerAddressLimit(txSigner, args.limit) + return vendingMinterMessages.updatePerAddressLimit(txSigner, args.limit) } case 'update_collection_info': { return sg721Messages.updateCollectionInfo(args.collectionInfo as CollectionInfo) @@ -225,10 +225,10 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { return sg721Messages.freezeCollectionInfo() } case 'shuffle': { - return minterMessages.shuffle(txSigner) + return vendingMinterMessages.shuffle(txSigner) } case 'withdraw': { - return minterMessages.withdraw(txSigner) + return vendingMinterMessages.withdraw(txSigner) } case 'transfer': { return sg721Messages.transferNft(args.recipient, args.tokenId.toString()) @@ -243,13 +243,13 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { return sg721Messages.batchBurn(args.tokenIds) } case 'batch_mint_for': { - return minterMessages.batchMintFor(txSigner, args.recipient, args.tokenIds) + return vendingMinterMessages.batchMintFor(txSigner, args.recipient, args.tokenIds) } case 'airdrop': { - return minterMessages.airdrop(txSigner, args.recipients) + return vendingMinterMessages.airdrop(txSigner, args.recipients) } case 'burn_remaining': { - return minterMessages.burnRemaining(txSigner) + return vendingMinterMessages.burnRemaining(txSigner) } default: { throw new Error('Unknown action') @@ -259,40 +259,40 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { export const previewExecutePayload = (args: DispatchExecuteArgs) => { // eslint-disable-next-line react-hooks/rules-of-hooks - const { messages: minterMessages } = useMinterContract() + const { messages: vendingMinterMessages } = useVendingMinterContract() // eslint-disable-next-line react-hooks/rules-of-hooks const { messages: sg721Messages } = useSG721Contract() const { minterContract, sg721Contract } = args switch (args.type) { case 'mint': { - return minterMessages(minterContract)?.mint() + return vendingMinterMessages(minterContract)?.mint() } case 'purge': { - return minterMessages(minterContract)?.purge() + return vendingMinterMessages(minterContract)?.purge() } case 'update_mint_price': { - return minterMessages(minterContract)?.updateMintPrice(args.price) + return vendingMinterMessages(minterContract)?.updateMintPrice(args.price) } case 'mint_to': { - return minterMessages(minterContract)?.mintTo(args.recipient) + return vendingMinterMessages(minterContract)?.mintTo(args.recipient) } case 'mint_for': { - return minterMessages(minterContract)?.mintFor(args.recipient, args.tokenId) + return vendingMinterMessages(minterContract)?.mintFor(args.recipient, args.tokenId) } case 'batch_mint': { - return minterMessages(minterContract)?.batchMint(args.recipient, args.batchNumber) + return vendingMinterMessages(minterContract)?.batchMint(args.recipient, args.batchNumber) } case 'set_whitelist': { - return minterMessages(minterContract)?.setWhitelist(args.whitelist) + return vendingMinterMessages(minterContract)?.setWhitelist(args.whitelist) } case 'update_start_time': { - return minterMessages(minterContract)?.updateStartTime(args.startTime) + return vendingMinterMessages(minterContract)?.updateStartTime(args.startTime) } case 'update_start_trading_time': { - return minterMessages(minterContract)?.updateStartTradingTime(args.startTime as string) + return vendingMinterMessages(minterContract)?.updateStartTradingTime(args.startTime as string) } case 'update_per_address_limit': { - return minterMessages(minterContract)?.updatePerAddressLimit(args.limit) + return vendingMinterMessages(minterContract)?.updatePerAddressLimit(args.limit) } case 'update_collection_info': { return sg721Messages(sg721Contract)?.updateCollectionInfo(args.collectionInfo as CollectionInfo) @@ -301,10 +301,10 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { return sg721Messages(sg721Contract)?.freezeCollectionInfo() } case 'shuffle': { - return minterMessages(minterContract)?.shuffle() + return vendingMinterMessages(minterContract)?.shuffle() } case 'withdraw': { - return minterMessages(minterContract)?.withdraw() + return vendingMinterMessages(minterContract)?.withdraw() } case 'transfer': { return sg721Messages(sg721Contract)?.transferNft(args.recipient, args.tokenId.toString()) @@ -319,13 +319,13 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { return sg721Messages(sg721Contract)?.batchBurn(args.tokenIds) } case 'batch_mint_for': { - return minterMessages(minterContract)?.batchMintFor(args.recipient, args.tokenIds) + return vendingMinterMessages(minterContract)?.batchMintFor(args.recipient, args.tokenIds) } case 'airdrop': { - return minterMessages(minterContract)?.airdrop(args.recipients) + return vendingMinterMessages(minterContract)?.airdrop(args.recipients) } case 'burn_remaining': { - return minterMessages(minterContract)?.burnRemaining() + return vendingMinterMessages(minterContract)?.burnRemaining() } default: { return {} diff --git a/components/collections/queries/Queries.tsx b/components/collections/queries/Queries.tsx index e9138e3..b9ca571 100644 --- a/components/collections/queries/Queries.tsx +++ b/components/collections/queries/Queries.tsx @@ -5,8 +5,8 @@ import { FormControl } from 'components/FormControl' import { AddressInput, TextInput } from 'components/forms/FormInput' import { useInputState } from 'components/forms/FormInput.hooks' import { JsonPreview } from 'components/JsonPreview' -import type { MinterInstance } from 'contracts/minter' import type { SG721Instance } from 'contracts/sg721' +import type { MinterInstance } from 'contracts/vendingMinter' import { toast } from 'react-hot-toast' import { useQuery } from 'react-query' diff --git a/components/collections/queries/query.ts b/components/collections/queries/query.ts index e9454db..ad1482b 100644 --- a/components/collections/queries/query.ts +++ b/components/collections/queries/query.ts @@ -1,5 +1,5 @@ -import type { MinterInstance } from 'contracts/minter' import type { SG721Instance } from 'contracts/sg721' +import type { VendingMinterInstance } from 'contracts/vendingMinter' export type QueryType = typeof QUERY_TYPES[number] @@ -59,7 +59,7 @@ export interface DispatchExecuteProps { type Select = T export type DispatchQueryArgs = { - minterMessages?: MinterInstance + vendingMinterMessages?: VendingMinterInstance sg721Messages?: SG721Instance } & ( | { type: undefined } @@ -72,8 +72,8 @@ export type DispatchQueryArgs = { ) export const dispatchQuery = async (args: DispatchQueryArgs) => { - const { minterMessages, sg721Messages } = args - if (!minterMessages || !sg721Messages) { + const { vendingMinterMessages, sg721Messages } = args + if (!vendingMinterMessages || !sg721Messages) { throw new Error('Cannot execute actions') } switch (args.type) { @@ -81,16 +81,16 @@ export const dispatchQuery = async (args: DispatchQueryArgs) => { return sg721Messages.collectionInfo() } case 'mint_price': { - return minterMessages.getMintPrice() + return vendingMinterMessages.getMintPrice() } case 'num_tokens': { - return minterMessages.getMintableNumTokens() + return vendingMinterMessages.getMintableNumTokens() } case 'tokens_minted_to_user': { - return minterMessages.getMintCount(args.address) + return vendingMinterMessages.getMintCount(args.address) } // case 'token_owners': { - // return minterMessages.updateStartTime(txSigner, args.startTime) + // return vendingMinterMessages.updateStartTime(txSigner, args.startTime) // } case 'token_info': { if (!args.tokenId) return diff --git a/components/contracts/minter/ExecuteCombobox.hooks.ts b/components/contracts/baseMinter/ExecuteCombobox.hooks.ts similarity index 74% rename from components/contracts/minter/ExecuteCombobox.hooks.ts rename to components/contracts/baseMinter/ExecuteCombobox.hooks.ts index 19830ab..2c9ee59 100644 --- a/components/contracts/minter/ExecuteCombobox.hooks.ts +++ b/components/contracts/baseMinter/ExecuteCombobox.hooks.ts @@ -1,4 +1,4 @@ -import type { ExecuteListItem } from 'contracts/minter/messages/execute' +import type { ExecuteListItem } from 'contracts/baseMinter/messages/execute' import { useState } from 'react' export const useExecuteComboboxState = () => { diff --git a/components/contracts/minter/ExecuteCombobox.tsx b/components/contracts/baseMinter/ExecuteCombobox.tsx similarity index 95% rename from components/contracts/minter/ExecuteCombobox.tsx rename to components/contracts/baseMinter/ExecuteCombobox.tsx index e327c6d..7f32de0 100644 --- a/components/contracts/minter/ExecuteCombobox.tsx +++ b/components/contracts/baseMinter/ExecuteCombobox.tsx @@ -1,8 +1,8 @@ import { Combobox, Transition } from '@headlessui/react' import clsx from 'clsx' import { FormControl } from 'components/FormControl' -import type { ExecuteListItem } from 'contracts/minter/messages/execute' -import { EXECUTE_LIST } from 'contracts/minter/messages/execute' +import type { ExecuteListItem } from 'contracts/baseMinter/messages/execute' +import { EXECUTE_LIST } from 'contracts/baseMinter/messages/execute' import { matchSorter } from 'match-sorter' import { Fragment, useState } from 'react' import { FaChevronDown, FaInfoCircle } from 'react-icons/fa' diff --git a/components/contracts/vendingMinter/ExecuteCombobox.hooks.ts b/components/contracts/vendingMinter/ExecuteCombobox.hooks.ts new file mode 100644 index 0000000..09ef184 --- /dev/null +++ b/components/contracts/vendingMinter/ExecuteCombobox.hooks.ts @@ -0,0 +1,7 @@ +import type { ExecuteListItem } from 'contracts/vendingMinter/messages/execute' +import { useState } from 'react' + +export const useExecuteComboboxState = () => { + const [value, setValue] = useState(null) + return { value, onChange: (item: ExecuteListItem) => setValue(item) } +} diff --git a/components/contracts/vendingMinter/ExecuteCombobox.tsx b/components/contracts/vendingMinter/ExecuteCombobox.tsx new file mode 100644 index 0000000..0f01538 --- /dev/null +++ b/components/contracts/vendingMinter/ExecuteCombobox.tsx @@ -0,0 +1,92 @@ +import { Combobox, Transition } from '@headlessui/react' +import clsx from 'clsx' +import { FormControl } from 'components/FormControl' +import type { ExecuteListItem } from 'contracts/vendingMinter/messages/execute' +import { EXECUTE_LIST } from 'contracts/vendingMinter/messages/execute' +import { matchSorter } from 'match-sorter' +import { Fragment, useState } from 'react' +import { FaChevronDown, FaInfoCircle } from 'react-icons/fa' + +export interface ExecuteComboboxProps { + value: ExecuteListItem | null + onChange: (item: ExecuteListItem) => void +} + +export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => { + const [search, setSearch] = useState('') + + const filtered = + search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] }) + + return ( + +
+ val?.name ?? ''} + id="message-type" + onChange={(event) => setSearch(event.target.value)} + placeholder="Select message type" + /> + + + {({ open }) => + + setSearch('')} as={Fragment}> + + {filtered.length < 1 && ( + + Message type not found. + + )} + {filtered.map((entry) => ( + + clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active }) + } + value={entry} + > + {entry.name} + {entry.description} + + ))} + + +
+ + {value && ( +
+
+ +
+ {value.description} +
+ )} +
+ ) +} diff --git a/contexts/contracts.tsx b/contexts/contracts.tsx index 5094c4a..b0fbd06 100644 --- a/contexts/contracts.tsx +++ b/contexts/contracts.tsx @@ -1,9 +1,13 @@ -import type { UseMinterContractProps } from 'contracts/minter' -import { useMinterContract } from 'contracts/minter' +import type { UseBaseFactoryContractProps } from 'contracts/baseFactory' +import { useBaseFactoryContract } from 'contracts/baseFactory' +import type { UseBaseMinterContractProps } from 'contracts/baseMinter' +import { useBaseMinterContract } from 'contracts/baseMinter' import type { UseSG721ContractProps } from 'contracts/sg721' import { useSG721Contract } from 'contracts/sg721' import type { UseVendingFactoryContractProps } from 'contracts/vendingFactory' import { useVendingFactoryContract } from 'contracts/vendingFactory' +import type { UseVendingMinterContractProps } from 'contracts/vendingMinter' +import { useVendingMinterContract } from 'contracts/vendingMinter' import type { UseWhiteListContractProps } from 'contracts/whitelist' import { useWhiteListContract } from 'contracts/whitelist' import type { ReactNode, VFC } from 'react' @@ -16,9 +20,11 @@ import create from 'zustand' */ export interface ContractsStore extends State { sg721: UseSG721ContractProps | null - minter: UseMinterContractProps | null + vendingMinter: UseVendingMinterContractProps | null + baseMinter: UseBaseMinterContractProps | null whitelist: UseWhiteListContractProps | null vendingFactory: UseVendingFactoryContractProps | null + baseFactory: UseBaseFactoryContractProps | null } /** @@ -26,9 +32,11 @@ export interface ContractsStore extends State { */ export const defaultValues: ContractsStore = { sg721: null, - minter: null, + vendingMinter: null, + baseMinter: null, whitelist: null, vendingFactory: null, + baseFactory: null, } /** @@ -53,18 +61,22 @@ export const ContractsProvider = ({ children }: { children: ReactNode }) => { const ContractsSubscription: VFC = () => { const sg721 = useSG721Contract() - const minter = useMinterContract() + const vendingMinter = useVendingMinterContract() + const baseMinter = useBaseMinterContract() const whitelist = useWhiteListContract() const vendingFactory = useVendingFactoryContract() + const baseFactory = useBaseFactoryContract() useEffect(() => { useContracts.setState({ sg721, - minter, + vendingMinter, + baseMinter, whitelist, vendingFactory, + baseFactory, }) - }, [sg721, minter, whitelist, vendingFactory]) + }, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory]) return null } diff --git a/contracts/baseFactory/contract.ts b/contracts/baseFactory/contract.ts new file mode 100644 index 0000000..20fd166 --- /dev/null +++ b/contracts/baseFactory/contract.ts @@ -0,0 +1,93 @@ +import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import type { Coin } from '@cosmjs/proto-signing' +import { coin } from '@cosmjs/proto-signing' +import type { logs } from '@cosmjs/stargate' +import { BASE_FACTORY_ADDRESS } from 'utils/constants' + +export interface CreateBaseMinterResponse { + readonly baseMinterAddress: string + readonly sg721Address: string + readonly transactionHash: string + readonly logs: readonly logs.Log[] +} + +export interface BaseFactoryInstance { + readonly contractAddress: string + + //Query + getParams: () => Promise + //Execute + createBaseMinter: ( + senderAddress: string, + msg: Record, + funds: Coin[], + ) => Promise +} + +export interface BaseFactoryMessages { + createBaseMinter: (msg: Record) => CreateBaseMinterMessage +} + +export interface CreateBaseMinterMessage { + sender: string + contract: string + msg: Record + funds: Coin[] +} + +export interface BaseFactoryContract { + use: (contractAddress: string) => BaseFactoryInstance + + messages: (contractAddress: string) => BaseFactoryMessages +} + +export const baseFactory = (client: SigningCosmWasmClient, txSigner: string): BaseFactoryContract => { + const use = (contractAddress: string): BaseFactoryInstance => { + //Query + const getParams = async (): Promise => { + const res = await client.queryContractSmart(contractAddress, { + params: {}, + }) + return res + } + + //Execute + const createBaseMinter = async ( + senderAddress: string, + msg: Record, + funds: Coin[], + ): Promise => { + const result = await client.execute(senderAddress, BASE_FACTORY_ADDRESS, msg, 'auto', '', funds) + + return { + baseMinterAddress: result.logs[0].events[5].attributes[0].value, + sg721Address: result.logs[0].events[5].attributes[2].value, + transactionHash: result.transactionHash, + logs: result.logs, + } + } + + return { + contractAddress, + getParams, + createBaseMinter, + } + } + + const messages = (contractAddress: string) => { + const createBaseMinter = (msg: Record): CreateBaseMinterMessage => { + return { + sender: txSigner, + contract: contractAddress, + msg, + funds: [coin('1000000000', 'ustars')], + } + } + + return { + createBaseMinter, + } + } + + return { use, messages } +} diff --git a/contracts/minter/index.ts b/contracts/baseFactory/index.ts similarity index 100% rename from contracts/minter/index.ts rename to contracts/baseFactory/index.ts diff --git a/contracts/baseFactory/messages/execute.ts b/contracts/baseFactory/messages/execute.ts new file mode 100644 index 0000000..374aba9 --- /dev/null +++ b/contracts/baseFactory/messages/execute.ts @@ -0,0 +1,28 @@ +import type { Coin } from '@cosmjs/proto-signing' + +import type { BaseFactoryInstance } from '../index' +import { useBaseFactoryContract } from '../index' + +/** @see {@link VendingFactoryInstance} */ +export interface DispatchExecuteArgs { + contract: string + messages?: BaseFactoryInstance + txSigner: string + msg: Record + funds: Coin[] +} + +export const dispatchExecute = async (args: DispatchExecuteArgs) => { + const { messages, txSigner } = args + if (!messages) { + throw new Error('cannot dispatch execute, messages is not defined') + } + return messages.createBaseMinter(txSigner, args.msg, args.funds) +} + +export const previewExecutePayload = (args: DispatchExecuteArgs) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { messages } = useBaseFactoryContract() + const { contract } = args + return messages(contract)?.createBaseMinter(args.msg) +} diff --git a/contracts/baseFactory/useContract.ts b/contracts/baseFactory/useContract.ts new file mode 100644 index 0000000..d03ff09 --- /dev/null +++ b/contracts/baseFactory/useContract.ts @@ -0,0 +1,57 @@ +import { useWallet } from 'contexts/wallet' +import { useCallback, useEffect, useState } from 'react' + +import type { BaseFactoryContract, BaseFactoryInstance, BaseFactoryMessages } from './contract' +import { baseFactory as initContract } from './contract' + +export interface UseBaseFactoryContractProps { + use: (customAddress: string) => BaseFactoryInstance | undefined + updateContractAddress: (contractAddress: string) => void + getContractAddress: () => string | undefined + messages: (contractAddress: string) => BaseFactoryMessages | undefined +} + +export function useBaseFactoryContract(): UseBaseFactoryContractProps { + const wallet = useWallet() + + const [address, setAddress] = useState('') + const [baseFactory, setBaseFactory] = useState() + + useEffect(() => { + setAddress(localStorage.getItem('contract_address') || '') + }, []) + + useEffect(() => { + const BaseFactoryBaseContract = initContract(wallet.getClient(), wallet.address) + setBaseFactory(BaseFactoryBaseContract) + }, [wallet]) + + const updateContractAddress = (contractAddress: string) => { + setAddress(contractAddress) + } + + const use = useCallback( + (customAddress = ''): BaseFactoryInstance | undefined => { + return baseFactory?.use(address || customAddress) + }, + [baseFactory, address], + ) + + const getContractAddress = (): string | undefined => { + return address + } + + const messages = useCallback( + (customAddress = ''): BaseFactoryMessages | undefined => { + return baseFactory?.messages(address || customAddress) + }, + [baseFactory, address], + ) + + return { + use, + updateContractAddress, + getContractAddress, + messages, + } +} diff --git a/contracts/baseMinter/contract.ts b/contracts/baseMinter/contract.ts new file mode 100644 index 0000000..7ebfcd4 --- /dev/null +++ b/contracts/baseMinter/contract.ts @@ -0,0 +1,243 @@ +import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import type { Coin } from '@cosmjs/proto-signing' +import { coin } from '@cosmjs/proto-signing' +import type { logs } from '@cosmjs/stargate' +import type { Timestamp } from '@stargazezone/types/contracts/minter/shared-types' +import toast from 'react-hot-toast' +import { BASE_FACTORY_ADDRESS } from 'utils/constants' + +export interface InstantiateResponse { + readonly contractAddress: string + readonly transactionHash: string + readonly logs: readonly logs.Log[] +} + +export interface MigrateResponse { + readonly transactionHash: string + readonly logs: readonly logs.Log[] +} + +export interface RoyaltyInfo { + payment_address: string + share: string +} + +export interface BaseMinterInstance { + readonly contractAddress: string + + //Query + getConfig: () => Promise + getStatus: () => Promise + + //Execute + mint: (senderAddress: string, tokenUri: string) => Promise + updateStartTradingTime: (senderAddress: string, time?: Timestamp) => Promise +} + +export interface BaseMinterMessages { + mint: (tokenUri: string) => MintMessage + updateStartTradingTime: (time: Timestamp) => UpdateStartTradingTimeMessage +} + +export interface MintMessage { + sender: string + contract: string + msg: { + mint: { + token_uri: string + } + } + funds: Coin[] +} + +export interface UpdateStartTradingTimeMessage { + sender: string + contract: string + msg: { + update_start_trading_time: string + } + funds: Coin[] +} + +export interface CustomMessage { + sender: string + contract: string + msg: Record[] + funds: Coin[] +} + +export interface MintPriceMessage { + public_price: { + denom: string + amount: string + } + airdrop_price: { + denom: string + amount: string + } + whitelist_price?: { + denom: string + amount: string + } + current_price: { + denom: string + amount: string + } +} + +export interface BaseMinterContract { + instantiate: ( + senderAddress: string, + codeId: number, + initMsg: Record, + label: string, + admin?: string, + funds?: Coin[], + ) => Promise + + migrate: ( + senderAddress: string, + contractAddress: string, + codeId: number, + migrateMsg: Record, + ) => Promise + + use: (contractAddress: string) => BaseMinterInstance + + messages: (contractAddress: string) => BaseMinterMessages +} + +export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): BaseMinterContract => { + const use = (contractAddress: string): BaseMinterInstance => { + //Query + const getFactoryParameters = async (): Promise => { + const res = await client.queryContractSmart(BASE_FACTORY_ADDRESS, { params: {} }) + return res + console.log(res) + } + + const getConfig = async (): Promise => { + const res = await client.queryContractSmart(contractAddress, { + config: {}, + }) + return res + } + + const getStatus = async (): Promise => { + const res = await client.queryContractSmart(contractAddress, { + status: {}, + }) + return res + } + + //Execute + const mint = async (senderAddress: string, tokenUri: string): Promise => { + //const factoryParameters = await baseFactory?.use(BASE_FACTORY_ADDRESS)?.getParams() + + const factoryParameters = await toast.promise(getFactoryParameters(), { + loading: 'Querying Factory Parameters...', + error: 'Querying Factory Parameters failed!', + success: 'Query successful! Minting...', + }) + console.log(factoryParameters.params.mint_fee_bps) + + const price = (await getConfig()).config.mint_price.amount + console.log(price) + console.log((Number(price) * Number(factoryParameters.params.mint_fee_bps)) / 100) + const res = await client.execute( + senderAddress, + contractAddress, + { + mint: { token_uri: tokenUri }, + }, + 'auto', + '', + [coin((Number(price) * Number(factoryParameters.params.mint_fee_bps)) / 100 / 100, 'ustars')], + ) + + return res.transactionHash + } + + const updateStartTradingTime = async (senderAddress: string, time?: Timestamp): Promise => { + const res = await client.execute( + senderAddress, + contractAddress, + { + update_start_trading_time: time || null, + }, + 'auto', + '', + ) + + return res.transactionHash + } + + return { + contractAddress, + getConfig, + getStatus, + mint, + updateStartTradingTime, + } + } + + const migrate = async ( + senderAddress: string, + contractAddress: string, + codeId: number, + migrateMsg: Record, + ): Promise => { + const result = await client.migrate(senderAddress, contractAddress, codeId, migrateMsg, 'auto') + return { + transactionHash: result.transactionHash, + logs: result.logs, + } + } + + const instantiate = async ( + senderAddress: string, + codeId: number, + initMsg: Record, + label: string, + ): Promise => { + const result = await client.instantiate(senderAddress, codeId, initMsg, label, 'auto', { + funds: [coin('1000000000', 'ustars')], + }) + return { + contractAddress: result.contractAddress, + transactionHash: result.transactionHash, + logs: result.logs, + } + } + + const messages = (contractAddress: string) => { + const mint = (tokenUri: string): MintMessage => { + return { + sender: txSigner, + contract: contractAddress, + msg: { + mint: { token_uri: tokenUri }, + }, + funds: [], + } + } + + const updateStartTradingTime = (startTime: string): UpdateStartTradingTimeMessage => { + return { + sender: txSigner, + contract: contractAddress, + msg: { + update_start_trading_time: startTime, + }, + funds: [], + } + } + + return { + mint, + updateStartTradingTime, + } + } + + return { use, instantiate, migrate, messages } +} diff --git a/contracts/baseMinter/index.ts b/contracts/baseMinter/index.ts new file mode 100644 index 0000000..6dc6461 --- /dev/null +++ b/contracts/baseMinter/index.ts @@ -0,0 +1,2 @@ +export * from './contract' +export * from './useContract' diff --git a/contracts/baseMinter/messages/execute.ts b/contracts/baseMinter/messages/execute.ts new file mode 100644 index 0000000..65fd2f0 --- /dev/null +++ b/contracts/baseMinter/messages/execute.ts @@ -0,0 +1,82 @@ +import type { BaseMinterInstance } from '../index' +import { useBaseMinterContract } from '../index' + +export type ExecuteType = typeof EXECUTE_TYPES[number] + +export const EXECUTE_TYPES = ['mint', 'update_start_trading_time'] as const + +export interface ExecuteListItem { + id: ExecuteType + name: string + description?: string +} + +export const EXECUTE_LIST: ExecuteListItem[] = [ + { + id: 'mint', + name: 'Mint', + description: `Mint new tokens for a given address`, + }, + { + id: 'update_start_trading_time', + name: 'Update Start Trading Time', + description: `Update start trading time for minting`, + }, +] + +export interface DispatchExecuteProps { + type: ExecuteType + [k: string]: unknown +} + +type Select = T + +/** @see {@link BaseMinterInstance} */ +export type DispatchExecuteArgs = { + contract: string + messages?: BaseMinterInstance + txSigner: string +} & ( + | { type: undefined } + | { type: Select<'mint'>; tokenUri: string } + | { type: Select<'update_start_trading_time'>; startTime?: string } +) + +export const dispatchExecute = async (args: DispatchExecuteArgs) => { + const { messages, txSigner } = args + if (!messages) { + throw new Error('cannot dispatch execute, messages is not defined') + } + switch (args.type) { + case 'mint': { + return messages.mint(txSigner, args.tokenUri) + } + case 'update_start_trading_time': { + return messages.updateStartTradingTime(txSigner, args.startTime) + } + default: { + throw new Error('unknown execute type') + } + } +} + +export const previewExecutePayload = (args: DispatchExecuteArgs) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { messages } = useBaseMinterContract() + const { contract } = args + switch (args.type) { + case 'mint': { + return messages(contract)?.mint(args.tokenUri) + } + case 'update_start_trading_time': { + return messages(contract)?.updateStartTradingTime(args.startTime as string) + } + default: { + return {} + } + } +} + +export const isEitherType = (type: unknown, arr: T[]): type is T => { + return arr.some((val) => type === val) +} diff --git a/contracts/baseMinter/messages/query.ts b/contracts/baseMinter/messages/query.ts new file mode 100644 index 0000000..6f92b26 --- /dev/null +++ b/contracts/baseMinter/messages/query.ts @@ -0,0 +1,37 @@ +import type { BaseMinterInstance } from '../contract' + +export type QueryType = typeof QUERY_TYPES[number] + +export const QUERY_TYPES = ['config', 'status'] as const + +export interface QueryListItem { + id: QueryType + name: string + description?: string +} + +export const QUERY_LIST: QueryListItem[] = [ + { id: 'config', name: 'Config', description: 'Query current contract config' }, + { id: 'status', name: 'Status', description: 'Query current contract status' }, +] + +export interface DispatchQueryProps { + address: string + messages: BaseMinterInstance | undefined + type: QueryType +} + +export const dispatchQuery = (props: DispatchQueryProps) => { + const { address, messages, type } = props + switch (type) { + case 'config': { + return messages?.getConfig() + } + case 'status': { + return messages?.getStatus() + } + default: { + throw new Error('unknown query type') + } + } +} diff --git a/contracts/baseMinter/useContract.ts b/contracts/baseMinter/useContract.ts new file mode 100644 index 0000000..35d54fe --- /dev/null +++ b/contracts/baseMinter/useContract.ts @@ -0,0 +1,102 @@ +import type { Coin } from '@cosmjs/proto-signing' +import type { logs } from '@cosmjs/stargate' +import { useWallet } from 'contexts/wallet' +import { useCallback, useEffect, useState } from 'react' + +import type { BaseMinterContract, BaseMinterInstance, BaseMinterMessages, MigrateResponse } from './contract' +import { baseMinter as initContract } from './contract' + +interface InstantiateResponse { + readonly contractAddress: string + readonly transactionHash: string + readonly logs: readonly logs.Log[] +} + +export interface UseBaseMinterContractProps { + instantiate: ( + codeId: number, + initMsg: Record, + label: string, + admin?: string, + funds?: Coin[], + ) => Promise + migrate: (contractAddress: string, codeId: number, migrateMsg: Record) => Promise + use: (customAddress: string) => BaseMinterInstance | undefined + updateContractAddress: (contractAddress: string) => void + getContractAddress: () => string | undefined + messages: (contractAddress: string) => BaseMinterMessages | undefined +} + +export function useBaseMinterContract(): UseBaseMinterContractProps { + const wallet = useWallet() + + const [address, setAddress] = useState('') + const [baseMinter, setBaseMinter] = useState() + + useEffect(() => { + setAddress(localStorage.getItem('contract_address') || '') + }, []) + + useEffect(() => { + const BaseMinterBaseContract = initContract(wallet.getClient(), wallet.address) + setBaseMinter(BaseMinterBaseContract) + }, [wallet]) + + const updateContractAddress = (contractAddress: string) => { + setAddress(contractAddress) + } + + const instantiate = useCallback( + (codeId: number, initMsg: Record, label: string, admin?: string): Promise => { + return new Promise((resolve, reject) => { + if (!baseMinter) { + reject(new Error('Contract is not initialized.')) + return + } + baseMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject) + }) + }, + [baseMinter, wallet], + ) + + const migrate = useCallback( + (contractAddress: string, codeId: number, migrateMsg: Record): Promise => { + return new Promise((resolve, reject) => { + if (!baseMinter) { + reject(new Error('Contract is not initialized.')) + return + } + console.log(wallet.address, contractAddress, codeId) + baseMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject) + }) + }, + [baseMinter, wallet], + ) + + const use = useCallback( + (customAddress = ''): BaseMinterInstance | undefined => { + return baseMinter?.use(address || customAddress) + }, + [baseMinter, address], + ) + + const getContractAddress = (): string | undefined => { + return address + } + + const messages = useCallback( + (customAddress = ''): BaseMinterMessages | undefined => { + return baseMinter?.messages(address || customAddress) + }, + [baseMinter, address], + ) + + return { + instantiate, + use, + updateContractAddress, + getContractAddress, + messages, + migrate, + } +} diff --git a/contracts/sg721/contract.ts b/contracts/sg721/contract.ts index 7183816..dbefd57 100644 --- a/contracts/sg721/contract.ts +++ b/contracts/sg721/contract.ts @@ -4,7 +4,7 @@ import type { Coin, logs } from '@cosmjs/stargate' import { coin } from '@cosmjs/stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' -import type { RoyaltyInfo } from '../minter/contract' +import type { RoyaltyInfo } from '../vendingMinter/contract' export interface InstantiateResponse { readonly contractAddress: string diff --git a/contracts/vendingFactory/contract.ts b/contracts/vendingFactory/contract.ts index a172157..6082804 100644 --- a/contracts/vendingFactory/contract.ts +++ b/contracts/vendingFactory/contract.ts @@ -4,8 +4,8 @@ import { coin } from '@cosmjs/proto-signing' import type { logs } from '@cosmjs/stargate' import { VENDING_FACTORY_ADDRESS } from 'utils/constants' -export interface CreateMinterResponse { - readonly minterAddress: string +export interface CreateVendingMinterResponse { + readonly vendingMinterAddress: string readonly sg721Address: string readonly transactionHash: string readonly logs: readonly logs.Log[] @@ -17,14 +17,18 @@ export interface VendingFactoryInstance { //Query //Execute - createMinter: (senderAddress: string, msg: Record, funds: Coin[]) => Promise + createVendingMinter: ( + senderAddress: string, + msg: Record, + funds: Coin[], + ) => Promise } export interface VendingFactoryMessages { - createMinter: (msg: Record) => CreateMinterMessage + createVendingMinter: (msg: Record) => CreateVendingMinterMessage } -export interface CreateMinterMessage { +export interface CreateVendingMinterMessage { sender: string contract: string msg: Record @@ -42,15 +46,15 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string): //Query //Execute - const createMinter = async ( + const createVendingMinter = async ( senderAddress: string, msg: Record, funds: Coin[], - ): Promise => { + ): Promise => { const result = await client.execute(senderAddress, VENDING_FACTORY_ADDRESS, msg, 'auto', '', funds) return { - minterAddress: result.logs[0].events[5].attributes[0].value, + vendingMinterAddress: result.logs[0].events[5].attributes[0].value, sg721Address: result.logs[0].events[5].attributes[2].value, transactionHash: result.transactionHash, logs: result.logs, @@ -59,12 +63,12 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string): return { contractAddress, - createMinter, + createVendingMinter, } } const messages = (contractAddress: string) => { - const createMinter = (msg: Record): CreateMinterMessage => { + const createVendingMinter = (msg: Record): CreateVendingMinterMessage => { return { sender: txSigner, contract: contractAddress, @@ -74,7 +78,7 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string): } return { - createMinter, + createVendingMinter, } } diff --git a/contracts/vendingFactory/messages/execute.ts b/contracts/vendingFactory/messages/execute.ts index fedf1f2..7939a7f 100644 --- a/contracts/vendingFactory/messages/execute.ts +++ b/contracts/vendingFactory/messages/execute.ts @@ -17,12 +17,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { if (!messages) { throw new Error('cannot dispatch execute, messages is not defined') } - return messages.createMinter(txSigner, args.msg, args.funds) + return messages.createVendingMinter(txSigner, args.msg, args.funds) } export const previewExecutePayload = (args: DispatchExecuteArgs) => { // eslint-disable-next-line react-hooks/rules-of-hooks const { messages } = useVendingFactoryContract() const { contract } = args - return messages(contract)?.createMinter(args.msg) + return messages(contract)?.createVendingMinter(args.msg) } diff --git a/contracts/minter/contract.ts b/contracts/vendingMinter/contract.ts similarity index 97% rename from contracts/minter/contract.ts rename to contracts/vendingMinter/contract.ts index ca9b9e5..e462cfb 100644 --- a/contracts/minter/contract.ts +++ b/contracts/vendingMinter/contract.ts @@ -22,7 +22,7 @@ export interface RoyaltyInfo { share: string } -export interface MinterInstance { +export interface VendingMinterInstance { readonly contractAddress: string //Query @@ -50,7 +50,7 @@ export interface MinterInstance { burnRemaining: (senderAddress: string) => Promise } -export interface MinterMessages { +export interface VendingMinterMessages { mint: () => MintMessage purge: () => PurgeMessage updateMintPrice: (price: string) => UpdateMintPriceMessage @@ -219,7 +219,7 @@ export interface MintPriceMessage { } } -export interface MinterContract { +export interface VendingMinterContract { instantiate: ( senderAddress: string, codeId: number, @@ -236,13 +236,13 @@ export interface MinterContract { migrateMsg: Record, ) => Promise - use: (contractAddress: string) => MinterInstance + use: (contractAddress: string) => VendingMinterInstance - messages: (contractAddress: string) => MinterMessages + messages: (contractAddress: string) => VendingMinterMessages } -export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterContract => { - const use = (contractAddress: string): MinterInstance => { +export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): VendingMinterContract => { + const use = (contractAddress: string): VendingMinterInstance => { //Query const getConfig = async (): Promise => { const res = await client.queryContractSmart(contractAddress, { diff --git a/contracts/vendingMinter/index.ts b/contracts/vendingMinter/index.ts new file mode 100644 index 0000000..6dc6461 --- /dev/null +++ b/contracts/vendingMinter/index.ts @@ -0,0 +1,2 @@ +export * from './contract' +export * from './useContract' diff --git a/contracts/minter/messages/execute.ts b/contracts/vendingMinter/messages/execute.ts similarity index 95% rename from contracts/minter/messages/execute.ts rename to contracts/vendingMinter/messages/execute.ts index f2f072f..058ae73 100644 --- a/contracts/minter/messages/execute.ts +++ b/contracts/vendingMinter/messages/execute.ts @@ -1,5 +1,5 @@ -import type { MinterInstance } from '../index' -import { useMinterContract } from '../index' +import type { VendingMinterInstance } from '../index' +import { useVendingMinterContract } from '../index' export type ExecuteType = typeof EXECUTE_TYPES[number] @@ -89,10 +89,10 @@ export interface DispatchExecuteProps { type Select = T -/** @see {@link MinterInstance} */ +/** @see {@link VendingMinterInstance} */ export type DispatchExecuteArgs = { contract: string - messages?: MinterInstance + messages?: VendingMinterInstance txSigner: string } & ( | { type: undefined } @@ -160,7 +160,7 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { export const previewExecutePayload = (args: DispatchExecuteArgs) => { // eslint-disable-next-line react-hooks/rules-of-hooks - const { messages } = useMinterContract() + const { messages } = useVendingMinterContract() const { contract } = args switch (args.type) { case 'mint': { diff --git a/contracts/minter/messages/query.ts b/contracts/vendingMinter/messages/query.ts similarity index 93% rename from contracts/minter/messages/query.ts rename to contracts/vendingMinter/messages/query.ts index 496ca5e..f5673ed 100644 --- a/contracts/minter/messages/query.ts +++ b/contracts/vendingMinter/messages/query.ts @@ -1,4 +1,4 @@ -import type { MinterInstance } from '../contract' +import type { VendingMinterInstance } from '../contract' export type QueryType = typeof QUERY_TYPES[number] @@ -24,7 +24,7 @@ export const QUERY_LIST: QueryListItem[] = [ export interface DispatchQueryProps { address: string - messages: MinterInstance | undefined + messages: VendingMinterInstance | undefined type: QueryType } diff --git a/contracts/minter/useContract.ts b/contracts/vendingMinter/useContract.ts similarity index 65% rename from contracts/minter/useContract.ts rename to contracts/vendingMinter/useContract.ts index 1121ea2..d118f04 100644 --- a/contracts/minter/useContract.ts +++ b/contracts/vendingMinter/useContract.ts @@ -3,8 +3,8 @@ import type { logs } from '@cosmjs/stargate' import { useWallet } from 'contexts/wallet' import { useCallback, useEffect, useState } from 'react' -import type { MigrateResponse, MinterContract, MinterInstance, MinterMessages } from './contract' -import { minter as initContract } from './contract' +import type { MigrateResponse, VendingMinterContract, VendingMinterInstance, VendingMinterMessages } from './contract' +import { vendingMinter as initContract } from './contract' /*export interface InstantiateResponse { /** The address of the newly instantiated contract *-/ @@ -24,7 +24,7 @@ interface InstantiateResponse { readonly logs: readonly logs.Log[] } -export interface UseMinterContractProps { +export interface UseVendingMinterContractProps { instantiate: ( codeId: number, initMsg: Record, @@ -33,25 +33,25 @@ export interface UseMinterContractProps { funds?: Coin[], ) => Promise migrate: (contractAddress: string, codeId: number, migrateMsg: Record) => Promise - use: (customAddress: string) => MinterInstance | undefined + use: (customAddress: string) => VendingMinterInstance | undefined updateContractAddress: (contractAddress: string) => void getContractAddress: () => string | undefined - messages: (contractAddress: string) => MinterMessages | undefined + messages: (contractAddress: string) => VendingMinterMessages | undefined } -export function useMinterContract(): UseMinterContractProps { +export function useVendingMinterContract(): UseVendingMinterContractProps { const wallet = useWallet() const [address, setAddress] = useState('') - const [minter, setMinter] = useState() + const [vendingMinter, setVendingMinter] = useState() useEffect(() => { setAddress(localStorage.getItem('contract_address') || '') }, []) useEffect(() => { - const MinterBaseContract = initContract(wallet.getClient(), wallet.address) - setMinter(MinterBaseContract) + const VendingMinterBaseContract = initContract(wallet.getClient(), wallet.address) + setVendingMinter(VendingMinterBaseContract) }, [wallet]) const updateContractAddress = (contractAddress: string) => { @@ -61,35 +61,35 @@ export function useMinterContract(): UseMinterContractProps { const instantiate = useCallback( (codeId: number, initMsg: Record, label: string, admin?: string): Promise => { return new Promise((resolve, reject) => { - if (!minter) { + if (!vendingMinter) { reject(new Error('Contract is not initialized.')) return } - minter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject) + vendingMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject) }) }, - [minter, wallet], + [vendingMinter, wallet], ) const migrate = useCallback( (contractAddress: string, codeId: number, migrateMsg: Record): Promise => { return new Promise((resolve, reject) => { - if (!minter) { + if (!vendingMinter) { reject(new Error('Contract is not initialized.')) return } console.log(wallet.address, contractAddress, codeId) - minter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject) + vendingMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject) }) }, - [minter, wallet], + [vendingMinter, wallet], ) const use = useCallback( - (customAddress = ''): MinterInstance | undefined => { - return minter?.use(address || customAddress) + (customAddress = ''): VendingMinterInstance | undefined => { + return vendingMinter?.use(address || customAddress) }, - [minter, address], + [vendingMinter, address], ) const getContractAddress = (): string | undefined => { @@ -97,10 +97,10 @@ export function useMinterContract(): UseMinterContractProps { } const messages = useCallback( - (customAddress = ''): MinterMessages | undefined => { - return minter?.messages(address || customAddress) + (customAddress = ''): VendingMinterMessages | undefined => { + return vendingMinter?.messages(address || customAddress) }, - [minter, address], + [vendingMinter, address], ) return { diff --git a/env.d.ts b/env.d.ts index 51d0974..bc8526b 100644 --- a/env.d.ts +++ b/env.d.ts @@ -18,6 +18,8 @@ declare namespace NodeJS { readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string readonly NEXT_PUBLIC_VENDING_MINTER_CODE_ID: string readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string + readonly NEXT_PUBLIC_BASE_FACTORY_ADDRESS: string + readonly NEXT_PUBLIC_BASE_MINTER_CODE_ID: string readonly NEXT_PUBLIC_PINATA_ENDPOINT_URL: string readonly NEXT_PUBLIC_API_URL: string diff --git a/pages/collections/actions.tsx b/pages/collections/actions.tsx index 8757da5..d6ce4b5 100644 --- a/pages/collections/actions.tsx +++ b/pages/collections/actions.tsx @@ -13,7 +13,7 @@ import { withMetadata } from 'utils/layout' import { links } from 'utils/links' const CollectionActionsPage: NextPage = () => { - const { minter: minterContract, sg721: sg721Contract } = useContracts() + const { vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts() const wallet = useWallet() const [action, setAction] = useState(false) @@ -32,9 +32,9 @@ const CollectionActionsPage: NextPage = () => { subtitle: 'Address of the Minter contract', }) - const minterMessages = useMemo( - () => minterContract?.use(minterContractState.value), - [minterContract, minterContractState.value], + const vendingMinterMessages = useMemo( + () => vendingMinterContract?.use(minterContractState.value), + [vendingMinterContract, minterContractState.value], ) const sg721Messages = useMemo( () => sg721Contract?.use(sg721ContractState.value), @@ -126,14 +126,14 @@ const CollectionActionsPage: NextPage = () => { {(action && ( )) || ( diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index f09b4c2..730e99f 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -49,7 +49,7 @@ import { getAssetType } from '../../utils/getAssetType' const CollectionCreationPage: NextPage = () => { const wallet = useWallet() const { - minter: minterContract, + vendingMinter: vendingMinterContract, whitelist: whitelistContract, vendingFactory: vendingFactoryContract, } = useContracts() @@ -69,7 +69,7 @@ const CollectionCreationPage: NextPage = () => { const [uploading, setUploading] = useState(false) const [creatingCollection, setCreatingCollection] = useState(false) const [readyToCreate, setReadyToCreate] = useState(false) - const [minterContractAddress, setMinterContractAddress] = useState(null) + const [vendingMinterContractAddress, setVendingMinterContractAddress] = useState(null) const [sg721ContractAddress, setSg721ContractAddress] = useState(null) const [whitelistContractAddress, setWhitelistContractAddress] = useState(null) const [baseTokenUri, setBaseTokenUri] = useState(null) @@ -102,7 +102,7 @@ const CollectionCreationPage: NextPage = () => { setCreatingCollection(true) setBaseTokenUri(null) setCoverImageUrl(null) - setMinterContractAddress(null) + setVendingMinterContractAddress(null) setSg721ContractAddress(null) setWhitelistContractAddress(null) setTransactionHash(null) @@ -176,7 +176,7 @@ const CollectionCreationPage: NextPage = () => { const instantiate = async (baseUri: string, coverImageUri: string, whitelist?: string) => { if (!wallet.initialized) throw new Error('Wallet not connected') - if (!minterContract) throw new Error('Contract not found') + if (!vendingMinterContract) throw new Error('Contract not found') let royaltyInfo = null if (royaltyDetails?.royaltyType === 'new') { @@ -229,7 +229,7 @@ const CollectionCreationPage: NextPage = () => { } const data = await dispatchExecute(payload) setTransactionHash(data.transactionHash) - setMinterContractAddress(data.minterAddress) + setVendingMinterContractAddress(data.vendingMinterAddress) setSg721ContractAddress(data.sg721Address) } @@ -399,8 +399,8 @@ const CollectionCreationPage: NextPage = () => { } } useEffect(() => { - if (minterContractAddress !== null) scrollRef.current?.scrollIntoView({ behavior: 'smooth' }) - }, [minterContractAddress]) + if (vendingMinterContractAddress !== null) scrollRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [vendingMinterContractAddress]) useEffect(() => { setBaseTokenUri(uploadDetails?.baseTokenURI as string) @@ -427,7 +427,7 @@ const CollectionCreationPage: NextPage = () => {

- +
Base Token URI:{' '} @@ -456,9 +456,9 @@ const CollectionCreationPage: NextPage = () => { - {minterContractAddress} + {vendingMinterContractAddress}
SG721 Contract Address:{' '} @@ -504,7 +504,7 @@ const CollectionCreationPage: NextPage = () => { View on Launchpad diff --git a/pages/collections/queries.tsx b/pages/collections/queries.tsx index ea9f5b0..40a2ebb 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 { vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts() const comboboxState = useQueryComboboxState() const type = comboboxState.value?.id @@ -57,19 +57,19 @@ 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 sg721Messages = useMemo(() => sg721Contract?.use(sg721ContractAddress), [sg721Contract, sg721ContractAddress]) const { data: response } = useQuery( - [sg721Messages, minterMessages, type, tokenId, address] as const, + [sg721Messages, vendingMinterMessages, type, tokenId, address] as const, async ({ queryKey }) => { - const [_sg721Messages, _minterMessages, _type, _tokenId, _address] = queryKey + const [_sg721Messages, _vendingMinterMessages, _type, _tokenId, _address] = queryKey const result = await dispatchQuery({ tokenId: _tokenId, - minterMessages: _minterMessages, + minterMessages: _vendingMinterMessages, 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/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 From c9d473441777537fc898f9581fe09f9ff45be2f8 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 9 Dec 2022 21:47:03 +0300 Subject: [PATCH 02/19] Implement Collection Actions changes for Base Minter contract --- components/collections/actions/Action.tsx | 22 ++++++- components/collections/actions/Combobox.tsx | 18 +++++- components/collections/actions/actions.ts | 70 +++++++++++++++++++-- components/collections/queries/Combobox.tsx | 17 ++++- components/collections/queries/Queries.tsx | 23 ++++--- components/collections/queries/query.ts | 55 +++++++++++++++- pages/collections/actions.tsx | 58 ++++++++++++++++- pages/collections/create.tsx | 2 +- pages/collections/queries.tsx | 14 +++-- 9 files changed, 249 insertions(+), 30 deletions(-) diff --git a/components/collections/actions/Action.tsx b/components/collections/actions/Action.tsx index 69d6b62..513d065 100644 --- a/components/collections/actions/Action.tsx +++ b/components/collections/actions/Action.tsx @@ -13,6 +13,7 @@ import { InputDateTime } from 'components/InputDateTime' import { JsonPreview } from 'components/JsonPreview' import { TransactionHash } from 'components/TransactionHash' import { useWallet } from 'contexts/wallet' +import type { BaseMinterInstance } from 'contracts/baseMinter' import type { SG721Instance } from 'contracts/sg721' import type { VendingMinterInstance } from 'contracts/vendingMinter' import type { FormEvent } from 'react' @@ -24,12 +25,15 @@ import type { AirdropAllocation } from 'utils/isValidAccountsFile' import type { CollectionInfo } from '../../../contracts/sg721/contract' import { TextInput } from '../../forms/FormInput' +import type { MinterType } from './Combobox' interface CollectionActionsProps { minterContractAddress: string sg721ContractAddress: string sg721Messages: SG721Instance | undefined vendingMinterMessages: VendingMinterInstance | undefined + baseMinterMessages: BaseMinterInstance | undefined + minterType: MinterType } type ExplicitContentType = true | false | undefined @@ -39,6 +43,8 @@ export const CollectionActions = ({ sg721Messages, minterContractAddress, vendingMinterMessages, + baseMinterMessages, + minterType, }: CollectionActionsProps) => { const wallet = useWallet() const [lastTx, setLastTx] = useState('') @@ -88,6 +94,14 @@ export const CollectionActions = ({ subtitle: 'Address of the recipient', }) + const tokenURIState = useInputState({ + id: 'token-uri', + name: 'tokenURI', + title: 'Token URI', + subtitle: 'URI for the token to be minted', + placeholder: 'ipfs://', + }) + const whitelistState = useInputState({ id: 'whitelist-address', name: 'whitelistAddress', @@ -138,6 +152,7 @@ export const CollectionActions = ({ placeholder: '8%', }) + const showTokenUriField = type === 'mint_token_uri' const showWhitelistField = type === 'set_whitelist' const showDateField = isEitherType(type, ['update_start_time', 'update_start_trading_time']) const showLimitField = type === 'update_per_address_limit' @@ -168,8 +183,10 @@ export const CollectionActions = ({ sg721Contract: sg721ContractAddress, tokenId: tokenIdState.value, tokenIds: tokenIdListState.value, + tokenUri: tokenURIState.value, batchNumber: batchNumberState.value, vendingMinterMessages, + baseMinterMessages, sg721Messages, recipient: recipientState.value, recipients: airdropArray, @@ -261,8 +278,9 @@ export const CollectionActions = ({
- + {showRecipientField && } + {showTokenUriField && } {showWhitelistField && } {showLimitField && } {showTokenIdField && } @@ -334,7 +352,7 @@ export const CollectionActions = ({ )} - + setTimestamp(date)} value={timestamp} /> diff --git a/components/collections/actions/Combobox.tsx b/components/collections/actions/Combobox.tsx index 2e347c6..4536808 100644 --- a/components/collections/actions/Combobox.tsx +++ b/components/collections/actions/Combobox.tsx @@ -2,19 +2,31 @@ import { Combobox, Transition } from '@headlessui/react' import clsx from 'clsx' import { FormControl } from 'components/FormControl' import { matchSorter } from 'match-sorter' -import { Fragment, useState } from 'react' +import { Fragment, useEffect, useState } from 'react' import { FaChevronDown, FaInfoCircle } from 'react-icons/fa' import type { ActionListItem } from './actions' -import { ACTION_LIST } from './actions' +import { BASE_ACTION_LIST, VENDING_ACTION_LIST } from './actions' + +export type MinterType = 'base' | 'vending' export interface ActionsComboboxProps { value: ActionListItem | null onChange: (item: ActionListItem) => void + minterType?: MinterType } -export const ActionsCombobox = ({ value, onChange }: ActionsComboboxProps) => { +export const ActionsCombobox = ({ value, onChange, minterType }: ActionsComboboxProps) => { const [search, setSearch] = useState('') + const [ACTION_LIST, SET_ACTION_LIST] = useState(VENDING_ACTION_LIST) + + useEffect(() => { + if (minterType === 'base') { + SET_ACTION_LIST(BASE_ACTION_LIST) + } else { + SET_ACTION_LIST(VENDING_ACTION_LIST) + } + }, [minterType]) const filtered = search === '' ? ACTION_LIST : matchSorter(ACTION_LIST, search, { keys: ['id', 'name', 'description'] }) diff --git a/components/collections/actions/actions.ts b/components/collections/actions/actions.ts index b664724..35d45df 100644 --- a/components/collections/actions/actions.ts +++ b/components/collections/actions/actions.ts @@ -1,12 +1,16 @@ +import { useBaseMinterContract } from 'contracts/baseMinter' import type { CollectionInfo, SG721Instance } from 'contracts/sg721' import { useSG721Contract } from 'contracts/sg721' import type { VendingMinterInstance } from 'contracts/vendingMinter' import { useVendingMinterContract } from 'contracts/vendingMinter' +import type { BaseMinterInstance } from '../../../contracts/baseMinter/contract' + export type ActionType = typeof ACTION_TYPES[number] export const ACTION_TYPES = [ 'mint', + 'mint_token_uri', 'purge', 'update_mint_price', 'mint_to', @@ -35,7 +39,55 @@ export interface ActionListItem { description?: string } -export const ACTION_LIST: ActionListItem[] = [ +export const BASE_ACTION_LIST: ActionListItem[] = [ + { + id: 'mint_token_uri', + name: 'Mint Token URI', + description: `Mint a token with the given token URI`, + }, + { + id: 'update_start_trading_time', + name: 'Update Trading Start Time', + description: `Update start time for trading`, + }, + { + id: 'update_collection_info', + name: 'Update Collection Info', + description: `Update Collection Info`, + }, + { + id: 'freeze_collection_info', + name: 'Freeze Collection Info', + description: `Freeze collection info to prevent further updates`, + }, + { + id: 'withdraw', + name: 'Withdraw Tokens', + description: `Withdraw tokens from the contract`, + }, + { + id: 'transfer', + name: 'Transfer Tokens', + description: `Transfer tokens from one address to another`, + }, + { + id: 'batch_transfer', + name: 'Batch Transfer Tokens', + description: `Transfer a list of tokens to a recipient`, + }, + { + id: 'burn', + name: 'Burn Token', + description: `Burn a specified token from the collection`, + }, + { + id: 'batch_burn', + name: 'Batch Burn Tokens', + description: `Burn a list of tokens from the collection`, + }, +] + +export const VENDING_ACTION_LIST: ActionListItem[] = [ { id: 'mint', name: 'Mint', @@ -150,16 +202,18 @@ export interface DispatchExecuteProps { type Select = T -/** @see {@link VendingMinterInstance} */ +/** @see {@link VendingMinterInstance}{@link BaseMinterInstance} */ export type DispatchExecuteArgs = { minterContract: string sg721Contract: string vendingMinterMessages?: VendingMinterInstance + baseMinterMessages?: BaseMinterInstance sg721Messages?: SG721Instance txSigner: string } & ( | { type: undefined } | { type: Select<'mint'> } + | { type: Select<'mint_token_uri'>; tokenUri: string } | { type: Select<'purge'> } | { type: Select<'update_mint_price'>; price: string } | { type: Select<'mint_to'>; recipient: string } @@ -183,14 +237,17 @@ export type DispatchExecuteArgs = { ) export const dispatchExecute = async (args: DispatchExecuteArgs) => { - const { vendingMinterMessages, sg721Messages, txSigner } = args - if (!vendingMinterMessages || !sg721Messages) { + const { vendingMinterMessages, baseMinterMessages, sg721Messages, txSigner } = args + if (!vendingMinterMessages || !baseMinterMessages || !sg721Messages) { throw new Error('Cannot execute actions') } switch (args.type) { case 'mint': { return vendingMinterMessages.mint(txSigner) } + case 'mint_token_uri': { + return baseMinterMessages.mint(txSigner, args.tokenUri) + } case 'purge': { return vendingMinterMessages.purge(txSigner) } @@ -262,11 +319,16 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { const { messages: vendingMinterMessages } = useVendingMinterContract() // eslint-disable-next-line react-hooks/rules-of-hooks const { messages: sg721Messages } = useSG721Contract() + // eslint-disable-next-line react-hooks/rules-of-hooks + const { messages: baseMinterMessages } = useBaseMinterContract() const { minterContract, sg721Contract } = args switch (args.type) { case 'mint': { return vendingMinterMessages(minterContract)?.mint() } + case 'mint_token_uri': { + return baseMinterMessages(minterContract)?.mint(args.tokenUri) + } case 'purge': { return vendingMinterMessages(minterContract)?.purge() } diff --git a/components/collections/queries/Combobox.tsx b/components/collections/queries/Combobox.tsx index 62485eb..d5ce10c 100644 --- a/components/collections/queries/Combobox.tsx +++ b/components/collections/queries/Combobox.tsx @@ -2,19 +2,30 @@ import { Combobox, Transition } from '@headlessui/react' import clsx from 'clsx' import { FormControl } from 'components/FormControl' import { matchSorter } from 'match-sorter' -import { Fragment, useState } from 'react' +import { Fragment, useEffect, useState } from 'react' import { FaChevronDown, FaInfoCircle } from 'react-icons/fa' +import type { MinterType } from '../actions/Combobox' import type { QueryListItem } from './query' -import { QUERY_LIST } from './query' +import { BASE_QUERY_LIST, VENDING_QUERY_LIST } from './query' export interface QueryComboboxProps { value: QueryListItem | null onChange: (item: QueryListItem) => void + minterType?: MinterType } -export const QueryCombobox = ({ value, onChange }: QueryComboboxProps) => { +export const QueryCombobox = ({ value, onChange, minterType }: QueryComboboxProps) => { const [search, setSearch] = useState('') + const [QUERY_LIST, SET_QUERY_LIST] = useState(VENDING_QUERY_LIST) + + useEffect(() => { + if (minterType === 'base') { + SET_QUERY_LIST(BASE_QUERY_LIST) + } else { + SET_QUERY_LIST(VENDING_QUERY_LIST) + } + }, [minterType]) const filtered = search === '' ? QUERY_LIST : matchSorter(QUERY_LIST, search, { keys: ['id', 'name', 'description'] }) diff --git a/components/collections/queries/Queries.tsx b/components/collections/queries/Queries.tsx index b9ca571..931e661 100644 --- a/components/collections/queries/Queries.tsx +++ b/components/collections/queries/Queries.tsx @@ -5,22 +5,29 @@ import { FormControl } from 'components/FormControl' import { AddressInput, TextInput } from 'components/forms/FormInput' import { useInputState } from 'components/forms/FormInput.hooks' import { JsonPreview } from 'components/JsonPreview' +import type { BaseMinterInstance } from 'contracts/baseMinter' import type { SG721Instance } from 'contracts/sg721' -import type { MinterInstance } from 'contracts/vendingMinter' +import type { VendingMinterInstance } from 'contracts/vendingMinter' import { toast } from 'react-hot-toast' import { useQuery } from 'react-query' +import type { MinterType } from '../actions/Combobox' + interface CollectionQueriesProps { minterContractAddress: string sg721ContractAddress: string sg721Messages: SG721Instance | undefined - minterMessages: MinterInstance | undefined + vendingMinterMessages: VendingMinterInstance | undefined + baseMinterMessages: BaseMinterInstance | undefined + minterType: MinterType } export const CollectionQueries = ({ sg721ContractAddress, sg721Messages, minterContractAddress, - minterMessages, + vendingMinterMessages, + baseMinterMessages, + minterType, }: CollectionQueriesProps) => { const comboboxState = useQueryComboboxState() const type = comboboxState.value?.id @@ -46,12 +53,14 @@ export const CollectionQueries = ({ const showAddressField = type === 'tokens_minted_to_user' 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, @@ -71,7 +80,7 @@ export const CollectionQueries = ({ return (
- + {showAddressField && } {showTokenIdField && }
diff --git a/components/collections/queries/query.ts b/components/collections/queries/query.ts index ad1482b..37b041b 100644 --- a/components/collections/queries/query.ts +++ b/components/collections/queries/query.ts @@ -1,3 +1,4 @@ +import type { BaseMinterInstance } from 'contracts/baseMinter' import type { SG721Instance } from 'contracts/sg721' import type { VendingMinterInstance } from 'contracts/vendingMinter' @@ -10,6 +11,8 @@ export const QUERY_TYPES = [ 'tokens_minted_to_user', // 'token_owners', 'token_info', + 'config', + 'status', ] as const export interface QueryListItem { @@ -18,7 +21,7 @@ export interface QueryListItem { description?: string } -export const QUERY_LIST: QueryListItem[] = [ +export const VENDING_QUERY_LIST: QueryListItem[] = [ { id: 'collection_info', name: 'Collection Info', @@ -49,6 +52,43 @@ export const QUERY_LIST: QueryListItem[] = [ name: 'Token Info', description: `Get information about a token in the collection.`, }, + { + id: 'config', + name: 'Minter Config', + description: `Query Minter Config`, + }, + { + id: 'status', + name: 'Minter Status', + description: `Query Minter Status`, + }, +] +export const BASE_QUERY_LIST: QueryListItem[] = [ + { + id: 'collection_info', + name: 'Collection Info', + description: `Get information about the collection.`, + }, + { + id: 'tokens_minted_to_user', + name: 'Tokens Minted to User', + description: `Get the number of tokens minted in the collection to a user.`, + }, + { + id: 'token_info', + name: 'Token Info', + description: `Get information about a token in the collection.`, + }, + { + id: 'config', + name: 'Minter Config', + description: `Query Minter Config`, + }, + { + id: 'status', + name: 'Minter Status', + description: `Query Minter Status`, + }, ] export interface DispatchExecuteProps { @@ -59,6 +99,7 @@ export interface DispatchExecuteProps { type Select = T export type DispatchQueryArgs = { + baseMinterMessages?: BaseMinterInstance vendingMinterMessages?: VendingMinterInstance sg721Messages?: SG721Instance } & ( @@ -69,11 +110,13 @@ export type DispatchQueryArgs = { | { type: Select<'tokens_minted_to_user'>; address: string } // | { type: Select<'token_owners'> } | { type: Select<'token_info'>; tokenId: string } + | { type: Select<'config'> } + | { type: Select<'status'> } ) export const dispatchQuery = async (args: DispatchQueryArgs) => { - const { vendingMinterMessages, sg721Messages } = args - if (!vendingMinterMessages || !sg721Messages) { + const { baseMinterMessages, vendingMinterMessages, sg721Messages } = args + if (!baseMinterMessages || !vendingMinterMessages || !sg721Messages) { throw new Error('Cannot execute actions') } switch (args.type) { @@ -96,6 +139,12 @@ export const dispatchQuery = async (args: DispatchQueryArgs) => { if (!args.tokenId) return return sg721Messages.allNftInfo(args.tokenId) } + case 'config': { + return baseMinterMessages.getConfig() + } + case 'status': { + return baseMinterMessages.getStatus() + } default: { throw new Error('Unknown action') } diff --git a/pages/collections/actions.tsx b/pages/collections/actions.tsx index d6ce4b5..0250853 100644 --- a/pages/collections/actions.tsx +++ b/pages/collections/actions.tsx @@ -1,3 +1,4 @@ +import { toUtf8 } from '@cosmjs/encoding' import { CollectionActions } from 'components/collections/actions/Action' import { CollectionQueries } from 'components/collections/queries/Queries' import { ContractPageHeader } from 'components/ContractPageHeader' @@ -9,14 +10,19 @@ import type { NextPage } from 'next' import { useRouter } from 'next/router' import { NextSeo } from 'next-seo' import { useEffect, useMemo, useState } from 'react' +import toast from 'react-hot-toast' +import { useDebounce } from 'utils/debounce' import { withMetadata } from 'utils/layout' import { links } from 'utils/links' +import type { MinterType } from '../../components/collections/actions/Combobox' + const CollectionActionsPage: NextPage = () => { - const { vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts() + const { baseMinter: baseMinterContract, vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts() const wallet = useWallet() const [action, setAction] = useState(false) + const [minterType, setMinterType] = useState('vending') const sg721ContractState = useInputState({ id: 'sg721-contract-address', @@ -32,10 +38,16 @@ const CollectionActionsPage: NextPage = () => { subtitle: 'Address of the Minter contract', }) + const debouncedMinterContractState = useDebounce(minterContractState.value, 300) + const vendingMinterMessages = useMemo( () => vendingMinterContract?.use(minterContractState.value), [vendingMinterContract, minterContractState.value], ) + const baseMinterMessages = useMemo( + () => baseMinterContract?.use(minterContractState.value), + [baseMinterContract, minterContractState.value], + ) const sg721Messages = useMemo( () => sg721Contract?.use(sg721ContractState.value), [sg721Contract, sg721ContractState.value], @@ -66,6 +78,41 @@ const CollectionActionsPage: NextPage = () => { if (initialSg721 && initialSg721.length > 0) sg721ContractState.onChange(initialSg721) }, []) + useEffect(() => { + async function getMinterContractType() { + if (wallet.client && debouncedMinterContractState.length > 0) { + const client = wallet.client + const data = await toast.promise( + client.queryContractRaw( + debouncedMinterContractState, + toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()), + ), + { + loading: 'Retrieving Minter type...', + error: 'Minter type retrieval failed.', + success: 'Minter type retrieved.', + }, + ) + const contract: string = JSON.parse(new TextDecoder().decode(data as Uint8Array)).contract + console.log(contract) + return contract + } + } + void getMinterContractType() + .then((contract) => { + if (contract?.includes('sg-base-minter')) { + setMinterType('base') + } else { + setMinterType('vending') + } + }) + .catch((err) => { + console.log(err) + setMinterType('vending') + console.log('Unable to retrieve contract version') + }) + }, [debouncedMinterContractState, wallet.address]) + return (
@@ -124,18 +171,23 @@ const CollectionActionsPage: NextPage = () => {
{(action && ( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition )) || ( )}
diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index 730e99f..aa9528d 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -456,7 +456,7 @@ const CollectionCreationPage: NextPage = () => { {vendingMinterContractAddress} diff --git a/pages/collections/queries.tsx b/pages/collections/queries.tsx index 40a2ebb..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 { vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts() + const { baseMinter: baseMinterContract, vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts() const comboboxState = useQueryComboboxState() const type = comboboxState.value?.id @@ -61,15 +61,21 @@ const CollectionQueriesPage: NextPage = () => { () => 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, vendingMinterMessages, type, tokenId, address] as const, + [sg721Messages, baseMinterMessages, vendingMinterMessages, type, tokenId, address] as const, async ({ queryKey }) => { - const [_sg721Messages, _vendingMinterMessages, _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: _vendingMinterMessages, + vendingMinterMessages: _vendingMinterMessages, + baseMinterMessages: _baseMinterMessages_, sg721Messages: _sg721Messages, address: _address, type: _type, From 6da7d8ff4eec9832fae74bdf8d7e6cd4143dd41e Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 9 Dec 2022 21:49:16 +0300 Subject: [PATCH 03/19] Bump Studio version to v0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1912e63..9252f0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.2.9", + "version": "0.3.0", "workspaces": [ "packages/*" ], From 1e6d6c4330c1125f477cc0455791852f928f0937 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 9 Dec 2022 22:18:08 +0300 Subject: [PATCH 04/19] Update .env.example --- .env.example | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 377c3be..d2bef2d 100644 --- a/.env.example +++ b/.env.example @@ -1,21 +1,16 @@ -APP_VERSION=0.2.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="" -NEXT_PUBLIC_BASE_MINTER_CODE_ID=275 +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 From f19c6348e152662bf7d8c2f9d4522d5e5fd02509 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 9 Dec 2022 22:58:07 +0300 Subject: [PATCH 05/19] Update mint() subtitle for baseMinter/execute --- contracts/baseMinter/messages/execute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/baseMinter/messages/execute.ts b/contracts/baseMinter/messages/execute.ts index 65fd2f0..dc29129 100644 --- a/contracts/baseMinter/messages/execute.ts +++ b/contracts/baseMinter/messages/execute.ts @@ -15,7 +15,7 @@ export const EXECUTE_LIST: ExecuteListItem[] = [ { id: 'mint', name: 'Mint', - description: `Mint new tokens for a given address`, + description: `Mint a token with the given token URI`, }, { id: 'update_start_trading_time', From 637294c9b6708a6677da91feec694f5fb9630fdc Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 9 Dec 2022 23:26:43 +0300 Subject: [PATCH 06/19] Update minter type retrieval dependencies --- pages/collections/actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/collections/actions.tsx b/pages/collections/actions.tsx index 0250853..cf529bf 100644 --- a/pages/collections/actions.tsx +++ b/pages/collections/actions.tsx @@ -111,7 +111,7 @@ const CollectionActionsPage: NextPage = () => { setMinterType('vending') console.log('Unable to retrieve contract version') }) - }, [debouncedMinterContractState, wallet.address]) + }, [debouncedMinterContractState, wallet.client]) return (
From 5c6c87eb9e86e5e08cc85947dd6367bd992490d2 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 13 Dec 2022 15:52:43 +0300 Subject: [PATCH 07/19] Isolate Base & Vending Minter creation UI --- components/Sidebar.tsx | 2 +- .../collections/creation/MinterDetails.tsx | 199 +++++++++++ .../collections/creation/UploadDetails.tsx | 2 +- next.config.js | 2 +- package.json | 3 +- pages/collections/create.tsx | 328 ++++++++++++++++-- yarn.lock | 30 +- 7 files changed, 521 insertions(+), 45 deletions(-) create mode 100644 components/collections/creation/MinterDetails.tsx diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 399dcae..b214988 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -46,7 +46,7 @@ export const Sidebar = () => { !router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1).includes(href) && isChild, }, { - 'text-plumbus': router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1).includes(href) && isChild, + 'text-stargaze': router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1).includes(href) && isChild, }, // active route styling // { 'text-gray-500 pointer-events-none': disabled }, // disabled route styling )} diff --git a/components/collections/creation/MinterDetails.tsx b/components/collections/creation/MinterDetails.tsx new file mode 100644 index 0000000..3805085 --- /dev/null +++ b/components/collections/creation/MinterDetails.tsx @@ -0,0 +1,199 @@ +/* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { toUtf8 } from '@cosmjs/encoding' +import axios from 'axios' +import { useInputState } from 'components/forms/FormInput.hooks' +import { useWallet } from 'contexts/wallet' +import React, { useCallback, useEffect, useState } from 'react' +import { toast } from 'react-hot-toast' +import { API_URL } from 'utils/constants' + +import { useDebounce } from '../../../utils/debounce' +import { TextInput } from '../../forms/FormInput' +import type { MinterType } from '../actions/Combobox' + +export type MinterAcquisitionMethod = 'existing' | 'new' + +export interface MinterInfo { + name: string + minter: string +} + +interface MinterDetailsProps { + onChange: (data: MinterDetailsDataProps) => void + minterType: MinterType +} + +export interface MinterDetailsDataProps { + minterAcquisitionMethod: MinterAcquisitionMethod + existingMinter: string | undefined +} + +export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => { + const wallet = useWallet() + + const [myBaseMinterContracts, setMyBaseMinterContracts] = useState([]) + const [minterAcquisitionMethod, setMinterAcquisitionMethod] = useState('existing') + + const existingMinterState = useInputState({ + id: 'existingMinter', + name: 'existingMinter', + title: 'Existing Base Minter Contract Address', + subtitle: '', + placeholder: 'stars1...', + }) + + const fetchMinterContracts = async (): Promise => { + const contracts: MinterInfo[] = await axios + .get(`${API_URL}/api/v1beta/collections/${wallet.address}`) + .then((response) => { + const collectionData = response.data + const minterContracts = collectionData.map((collection: any) => { + return { name: collection.name, minter: collection.minter } + }) + return minterContracts + }) + .catch(console.error) + console.log(contracts) + return contracts + } + + async function getMinterContractType(minterContractAddress: string) { + if (wallet.client && minterContractAddress.length > 0) { + const client = wallet.client + const data = await client.queryContractRaw( + minterContractAddress, + toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()), + ) + const contractType: string = JSON.parse(new TextDecoder().decode(data as Uint8Array)).contract + return contractType + } + } + + const filterBaseMinterContracts = async () => { + setMyBaseMinterContracts([]) + await fetchMinterContracts() + .then((minterContracts) => + minterContracts.map(async (minterContract: any) => { + await getMinterContractType(minterContract.minter) + .then((contractType) => { + if (contractType?.includes('sg-minter')) { + setMyBaseMinterContracts((prevState) => [...prevState, minterContract]) + } + }) + .catch((err) => { + console.log(err) + console.log('Unable to retrieve contract type') + }) + }), + ) + .catch((err) => { + console.log(err) + console.log('Unable to fetch base minter contracts') + }) + } + + const debouncedMyBaseMinterContracts = useDebounce(myBaseMinterContracts, 500) + + const renderMinterContracts = useCallback(() => { + return myBaseMinterContracts.map((minterContract, index) => { + return ( + + ) + }) + }, [debouncedMyBaseMinterContracts]) + + const debouncedWalletAddress = useDebounce(wallet.address, 500) + + const displayToast = async () => { + await toast.promise(filterBaseMinterContracts(), { + loading: 'Fetching Base Minter contracts...', + success: 'Base Minter contracts retrieved.', + error: 'Unable to retrieve Base Minter contracts.', + }) + } + + useEffect(() => { + if (debouncedWalletAddress && minterAcquisitionMethod === 'existing') { + void displayToast() + } + }, [debouncedWalletAddress, minterAcquisitionMethod]) + + useEffect(() => { + const data: MinterDetailsDataProps = { + minterAcquisitionMethod, + existingMinter: existingMinterState.value, + } + onChange(data) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [existingMinterState.value, minterAcquisitionMethod]) + + return ( +
+
+
+ { + setMinterAcquisitionMethod('new') + }} + type="radio" + value="New" + /> + +
+
+ { + setMinterAcquisitionMethod('existing') + }} + type="radio" + value="Existing" + /> + +
+
+ + {minterAcquisitionMethod === 'existing' && ( +
+
+ + +
+
+ )} +
+ ) +} diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index fbf417f..9ee9261 100644 --- a/components/collections/creation/UploadDetails.tsx +++ b/components/collections/creation/UploadDetails.tsx @@ -233,7 +233,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => { }, [uploadMethod]) return ( -
+
{ const [uploadDetails, setUploadDetails] = useState(null) const [collectionDetails, setCollectionDetails] = useState(null) + const [minterDetails, setMinterDetails] = useState(null) const [mintingDetails, setMintingDetails] = useState(null) const [whitelistDetails, setWhitelistDetails] = useState(null) const [royaltyDetails, setRoyaltyDetails] = useState(null) + const [minterType, setMinterType] = useState('vending') const [uploading, setUploading] = useState(false) const [creatingCollection, setCreatingCollection] = useState(false) - const [readyToCreate, setReadyToCreate] = useState(false) + const [readyToCreateVm, setReadyToCreateVm] = useState(false) + const [readyToCreateBm, setReadyToCreateBm] = useState(false) + const [readyToUploadAndMint, setReadyToUploadAndMint] = useState(false) const [vendingMinterContractAddress, setVendingMinterContractAddress] = useState(null) const [sg721ContractAddress, setSg721ContractAddress] = useState(null) const [whitelistContractAddress, setWhitelistContractAddress] = useState(null) @@ -76,20 +84,20 @@ const CollectionCreationPage: NextPage = () => { const [coverImageUrl, setCoverImageUrl] = useState(null) const [transactionHash, setTransactionHash] = useState(null) - const performChecks = () => { + const performVendingMinterChecks = () => { try { - setReadyToCreate(false) + setReadyToCreateVm(false) checkUploadDetails() checkCollectionDetails() checkMintingDetails() checkRoyaltyDetails() checkWhitelistDetails() .then(() => { - setReadyToCreate(true) + setReadyToCreateVm(true) }) .catch((err) => { toast.error(`Error in Whitelist Configuration: ${err.message}`, { style: { maxWidth: 'none' } }) - setReadyToCreate(false) + setReadyToCreateVm(false) }) } catch (error: any) { toast.error(error.message, { style: { maxWidth: 'none' } }) @@ -97,7 +105,157 @@ const CollectionCreationPage: NextPage = () => { } } - const createCollection = async () => { + const performBaseMinterChecks = () => { + try { + setReadyToCreateBm(false) + checkUploadDetails() + checkCollectionDetails() + checkMintingDetails() + checkRoyaltyDetails() + checkWhitelistDetails() + .then(() => { + setReadyToCreateBm(true) + }) + .catch((err) => { + toast.error(`Error in Whitelist Configuration: ${err.message}`, { style: { maxWidth: 'none' } }) + setReadyToCreateBm(false) + }) + } catch (error: any) { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setUploading(false) + } + } + + const performUploadAndMintChecks = () => { + try { + setReadyToUploadAndMint(false) + checkUploadDetails() + checkCollectionDetails() + checkMintingDetails() + checkRoyaltyDetails() + checkWhitelistDetails() + .then(() => { + setReadyToUploadAndMint(true) + }) + .catch((err) => { + toast.error(`Error in Whitelist Configuration: ${err.message}`, { style: { maxWidth: 'none' } }) + setReadyToUploadAndMint(false) + }) + } catch (error: any) { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setUploading(false) + } + } + + const createVendingMinterCollection = async () => { + try { + setCreatingCollection(true) + setBaseTokenUri(null) + setCoverImageUrl(null) + setVendingMinterContractAddress(null) + setSg721ContractAddress(null) + setWhitelistContractAddress(null) + setTransactionHash(null) + if (uploadDetails?.uploadMethod === 'new') { + setUploading(true) + + const baseUri = await uploadFiles() + //upload coverImageUri and append the file name + const coverImageUri = await upload( + collectionDetails?.imageFile as File[], + uploadDetails.uploadService, + 'cover', + uploadDetails.nftStorageApiKey as string, + uploadDetails.pinataApiKey as string, + uploadDetails.pinataSecretKey as string, + ) + + setUploading(false) + + setBaseTokenUri(baseUri) + setCoverImageUrl(coverImageUri) + + let whitelist: string | undefined + if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress + else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() + setWhitelistContractAddress(whitelist as string) + + await instantiate(baseUri, coverImageUri, whitelist) + } else { + setBaseTokenUri(uploadDetails?.baseTokenURI as string) + setCoverImageUrl(uploadDetails?.imageUrl as string) + + let whitelist: string | undefined + if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress + else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() + setWhitelistContractAddress(whitelist as string) + + await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist) + } + setCreatingCollection(false) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setCreatingCollection(false) + setUploading(false) + } + } + + const createBaseMinterCollection = async () => { + try { + setCreatingCollection(true) + setBaseTokenUri(null) + setCoverImageUrl(null) + setVendingMinterContractAddress(null) + setSg721ContractAddress(null) + setWhitelistContractAddress(null) + setTransactionHash(null) + if (uploadDetails?.uploadMethod === 'new') { + setUploading(true) + + const baseUri = await uploadFiles() + //upload coverImageUri and append the file name + const coverImageUri = await upload( + collectionDetails?.imageFile as File[], + uploadDetails.uploadService, + 'cover', + uploadDetails.nftStorageApiKey as string, + uploadDetails.pinataApiKey as string, + uploadDetails.pinataSecretKey as string, + ) + + setUploading(false) + + setBaseTokenUri(baseUri) + setCoverImageUrl(coverImageUri) + + let whitelist: string | undefined + if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress + else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() + setWhitelistContractAddress(whitelist as string) + + await instantiate(baseUri, coverImageUri, whitelist) + } else { + setBaseTokenUri(uploadDetails?.baseTokenURI as string) + setCoverImageUrl(uploadDetails?.imageUrl as string) + + let whitelist: string | undefined + if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress + else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() + setWhitelistContractAddress(whitelist as string) + + await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist) + } + setCreatingCollection(false) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setCreatingCollection(false) + setUploading(false) + } + } + + const uploadAndMint = async () => { try { setCreatingCollection(true) setBaseTokenUri(null) @@ -513,36 +671,142 @@ const CollectionCreationPage: NextPage = () => {
+
+
+
+ +
+
+ +
+
+
+ + {minterType === 'base' && ( +
+ +
+ )} +
-
- - -
-
- -
- -
- {readyToCreate && } + +
+ + + + + + +
+
+ + +
+ + +
+ + +
+
+ + + + + + + + +
- + + + + + + + + +
diff --git a/yarn.lock b/yarn.lock index 8034166..da8a445 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3704,6 +3704,11 @@ clsx@^1: resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +clsx@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" @@ -4922,10 +4927,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" @@ -6806,12 +6811,12 @@ react-hook-form@^7: resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.30.0.tgz" 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== +react-hot-toast@2.4.0: + 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" @@ -6862,6 +6867,13 @@ react-time-picker@^4.5.0: react-fit "^1.4.0" update-input-width "^1.2.2" +react-toastify@9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.1.tgz#9280caea4a13dc1739c350d90660a630807bf10b" + integrity sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw== + dependencies: + clsx "^1.1.1" + react-tracked@^1: version "1.7.9" resolved "https://registry.npmjs.org/react-tracked/-/react-tracked-1.7.9.tgz" From fe4da955662b87a8886291232ad3a4b9e9b294a3 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 13 Dec 2022 21:50:42 +0300 Subject: [PATCH 08/19] Implement 1/1 minting UI --- components/AssetsPreview.tsx | 42 ++-- components/ConfirmationModal.tsx | 2 +- .../collections/creation/MinterDetails.tsx | 8 +- .../collections/creation/UploadDetails.tsx | 64 +++++- pages/collections/create.tsx | 213 ++++++++++++------ 5 files changed, 230 insertions(+), 99 deletions(-) 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?
- +

Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a @@ -293,9 +303,35 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {

+ +
+ +
+
+
+
+ +
+

+ Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a + decentralized storage solution, such as IPFS.
You may head over to{' '} + + NFT.Storage + {' '} + or{' '} + + Pinata + {' '} + and upload your asset & metadata manually to get a URI for your token before minting. +

- +
+ +
+ +
+
@@ -390,8 +426,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => { 'before:absolute before:inset-0 before:hover:bg-white/5 before:transition', )} id="assetFiles" - multiple + multiple={minterType === 'vending'} onChange={selectAssets} + ref={assetFilesRef} type="file" />
@@ -418,8 +455,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => { 'before:absolute before:inset-0 before:hover:bg-white/5 before:transition', )} id="metadataFiles" - multiple + multiple={minterType === 'vending'} onChange={selectMetadata} + ref={metadataFilesRef} type="file" />
@@ -435,7 +473,11 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
0}> - +
diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index 07f3c8f..d6c31bb 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -26,8 +26,10 @@ import { Conditional } from 'components/Conditional' import { LoadingModal } from 'components/LoadingModal' import { useContracts } from 'contexts/contracts' import { useWallet } from 'contexts/wallet' -import type { DispatchExecuteArgs } from 'contracts/vendingFactory/messages/execute' -import { dispatchExecute } from 'contracts/vendingFactory/messages/execute' +import type { DispatchExecuteArgs as BaseFactoryDispatchExecuteArgs } from 'contracts/baseFactory/messages/execute' +import { dispatchExecute as baseFactoryDispatchExecute } from 'contracts/baseFactory/messages/execute' +import type { DispatchExecuteArgs as VendingFactoryDispatchExecuteArgs } from 'contracts/vendingFactory/messages/execute' +import { dispatchExecute as vendingFactoryDispatchExecute } from 'contracts/vendingFactory/messages/execute' import type { NextPage } from 'next' import { NextSeo } from 'next-seo' import { useEffect, useMemo, useRef, useState } from 'react' @@ -35,6 +37,7 @@ import { toast } from 'react-hot-toast' import { upload } from 'services/upload' import { compareFileArrays } from 'utils/compareFileArrays' import { + BASE_FACTORY_ADDRESS, BLOCK_EXPLORER_URL, NETWORK, SG721_CODE_ID, @@ -53,17 +56,24 @@ import { getAssetType } from '../../utils/getAssetType' const CollectionCreationPage: NextPage = () => { const wallet = useWallet() const { + baseMinter: baseMinterContract, vendingMinter: vendingMinterContract, whitelist: whitelistContract, vendingFactory: vendingFactoryContract, + baseFactory: baseFactoryContract, } = useContracts() const scrollRef = useRef(null) - const messages = useMemo( + const vendingFactoryMessages = useMemo( () => vendingFactoryContract?.use(VENDING_FACTORY_ADDRESS), [vendingFactoryContract, wallet.address], ) + const baseFactoryMessages = useMemo( + () => baseFactoryContract?.use(BASE_FACTORY_ADDRESS), + [baseFactoryContract, wallet.address], + ) + const [uploadDetails, setUploadDetails] = useState(null) const [collectionDetails, setCollectionDetails] = useState(null) const [minterDetails, setMinterDetails] = useState(null) @@ -109,9 +119,8 @@ const CollectionCreationPage: NextPage = () => { try { setReadyToCreateBm(false) checkUploadDetails() - checkCollectionDetails() - checkMintingDetails() checkRoyaltyDetails() + checkCollectionDetails() checkWhitelistDetails() .then(() => { setReadyToCreateBm(true) @@ -130,9 +139,6 @@ const CollectionCreationPage: NextPage = () => { try { setReadyToUploadAndMint(false) checkUploadDetails() - checkCollectionDetails() - checkMintingDetails() - checkRoyaltyDetails() checkWhitelistDetails() .then(() => { setReadyToUploadAndMint(true) @@ -147,6 +153,12 @@ const CollectionCreationPage: NextPage = () => { } } + const resetReadyFlags = () => { + setReadyToCreateVm(false) + setReadyToCreateBm(false) + setReadyToUploadAndMint(false) + } + const createVendingMinterCollection = async () => { try { setCreatingCollection(true) @@ -180,7 +192,7 @@ const CollectionCreationPage: NextPage = () => { else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() setWhitelistContractAddress(whitelist as string) - await instantiate(baseUri, coverImageUri, whitelist) + await instantiateVendingMinter(baseUri, coverImageUri, whitelist) } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setCoverImageUrl(uploadDetails?.imageUrl as string) @@ -190,7 +202,7 @@ const CollectionCreationPage: NextPage = () => { else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() setWhitelistContractAddress(whitelist as string) - await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist) + await instantiateVendingMinter(baseTokenUri as string, coverImageUrl as string, whitelist) } setCreatingCollection(false) // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -229,22 +241,12 @@ const CollectionCreationPage: NextPage = () => { setBaseTokenUri(baseUri) setCoverImageUrl(coverImageUri) - let whitelist: string | undefined - if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress - else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() - setWhitelistContractAddress(whitelist as string) - - await instantiate(baseUri, coverImageUri, whitelist) + await instantiateBaseMinter(baseUri, coverImageUri) } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setCoverImageUrl(uploadDetails?.imageUrl as string) - let whitelist: string | undefined - if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress - else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() - setWhitelistContractAddress(whitelist as string) - - await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist) + await instantiateBaseMinter(uploadDetails?.baseTokenURI as string, uploadDetails?.imageUrl as string) } setCreatingCollection(false) // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -257,48 +259,49 @@ const CollectionCreationPage: NextPage = () => { const uploadAndMint = async () => { try { + if (!wallet.initialized) throw new Error('Wallet not connected') + if (!baseMinterContract) throw new Error('Contract not found') setCreatingCollection(true) setBaseTokenUri(null) setCoverImageUrl(null) setVendingMinterContractAddress(null) setSg721ContractAddress(null) - setWhitelistContractAddress(null) setTransactionHash(null) + if (uploadDetails?.uploadMethod === 'new') { setUploading(true) - - const baseUri = await uploadFiles() - //upload coverImageUri and append the file name - const coverImageUri = await upload( - collectionDetails?.imageFile as File[], - uploadDetails.uploadService, - 'cover', - uploadDetails.nftStorageApiKey as string, - uploadDetails.pinataApiKey as string, - uploadDetails.pinataSecretKey as string, - ) - - setUploading(false) - - setBaseTokenUri(baseUri) - setCoverImageUrl(coverImageUri) - - let whitelist: string | undefined - if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress - else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() - setWhitelistContractAddress(whitelist as string) - - await instantiate(baseUri, coverImageUri, whitelist) + await uploadFiles() + .then(async (baseUri) => { + setUploading(false) + setBaseTokenUri(baseUri) + const result = await baseMinterContract + .use(minterDetails?.existingMinter as string) + ?.mint(wallet.address, `ipfs://${baseUri}`) + console.log(result) + return result + }) + .then((result) => { + toast.success(`Minted successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000 }) + }) + .catch((error) => { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setUploading(false) + setCreatingCollection(false) + }) } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) - setCoverImageUrl(uploadDetails?.imageUrl as string) - - let whitelist: string | undefined - if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress - else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist() - setWhitelistContractAddress(whitelist as string) - - await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist) + setUploading(false) + await baseMinterContract + .use(minterDetails?.existingMinter as string) + ?.mint(wallet.address, `ipfs://${uploadDetails?.baseTokenURI}`) + .then((result) => { + toast.success(`Minted successfully! Tx Hash: ${result}`, { style: { maxWidth: 'none' }, duration: 5000 }) + }) + .catch((error) => { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setUploading(false) + setCreatingCollection(false) + }) } setCreatingCollection(false) // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -332,9 +335,9 @@ const CollectionCreationPage: NextPage = () => { return data.contractAddress } - const instantiate = async (baseUri: string, coverImageUri: string, whitelist?: string) => { + const instantiateVendingMinter = async (baseUri: string, coverImageUri: string, whitelist?: string) => { if (!wallet.initialized) throw new Error('Wallet not connected') - if (!vendingMinterContract) throw new Error('Contract not found') + if (!vendingFactoryContract) throw new Error('Contract not found') let royaltyInfo = null if (royaltyDetails?.royaltyType === 'new') { @@ -378,19 +381,68 @@ const CollectionCreationPage: NextPage = () => { }, } - const payload: DispatchExecuteArgs = { + const payload: VendingFactoryDispatchExecuteArgs = { contract: VENDING_FACTORY_ADDRESS, - messages, + messages: vendingFactoryMessages, txSigner: wallet.address, msg, funds: [coin('1000000000', 'ustars')], } - const data = await dispatchExecute(payload) + const data = await vendingFactoryDispatchExecute(payload) setTransactionHash(data.transactionHash) setVendingMinterContractAddress(data.vendingMinterAddress) setSg721ContractAddress(data.sg721Address) } + const instantiateBaseMinter = async (baseUri: string, coverImageUri: string) => { + if (!wallet.initialized) throw new Error('Wallet not connected') + if (!baseFactoryContract) throw new Error('Contract not found') + + let royaltyInfo = null + if (royaltyDetails?.royaltyType === 'new') { + royaltyInfo = { + payment_address: royaltyDetails.paymentAddress, + share: (Number(royaltyDetails.share) / 100).toString(), + } + } + + const msg = { + create_minter: { + init_msg: null, + collection_params: { + code_id: SG721_CODE_ID, + name: collectionDetails?.name, + symbol: collectionDetails?.symbol, + info: { + creator: wallet.address, + description: collectionDetails?.description, + image: `${ + uploadDetails?.uploadMethod === 'new' + ? `ipfs://${coverImageUri}/${collectionDetails?.imageFile[0].name as string}` + : `${coverImageUri}` + }`, + external_link: collectionDetails?.externalLink, + explicit_content: collectionDetails?.explicit, + royalty_info: royaltyInfo, + start_trading_time: collectionDetails?.startTradingTime || null, + }, + }, + }, + } + + const payload: BaseFactoryDispatchExecuteArgs = { + contract: BASE_FACTORY_ADDRESS, + messages: baseFactoryMessages, + txSigner: wallet.address, + msg, + funds: [coin('1000000000', 'ustars')], + } + const data = await baseFactoryDispatchExecute(payload) + setTransactionHash(data.transactionHash) + setVendingMinterContractAddress(data.baseMinterAddress) + setSg721ContractAddress(data.sg721Address) + } + const uploadFiles = async (): Promise => { if (!uploadDetails) throw new Error('Please upload asset and metadata') return new Promise((resolve, reject) => { @@ -459,6 +511,9 @@ const CollectionCreationPage: NextPage = () => { if (!uploadDetails) { throw new Error('Please select assets and metadata') } + if (minterType === 'base' && uploadDetails.uploadMethod === 'new' && uploadDetails.assetFiles.length > 1) { + throw new Error('Base Minter can only mint one asset at a time. Please select only one asset.') + } if (uploadDetails.uploadMethod === 'new' && uploadDetails.assetFiles.length === 0) { throw new Error('Please select the assets') } @@ -478,6 +533,9 @@ const CollectionCreationPage: NextPage = () => { if (uploadDetails.uploadMethod === 'existing' && !uploadDetails.baseTokenURI?.includes('ipfs://')) { throw new Error('Please specify a valid base token URI') } + if (minterDetails?.minterAcquisitionMethod === 'existing' && !minterDetails.existingMinter) { + throw new Error('Please specify a valid Base Minter contract address') + } } const checkCollectionDetails = () => { @@ -565,12 +623,27 @@ const CollectionCreationPage: NextPage = () => { setCoverImageUrl(uploadDetails?.imageUrl as string) }, [uploadDetails?.baseTokenURI, uploadDetails?.imageUrl]) + useEffect(() => { + resetReadyFlags() + setVendingMinterContractAddress(null) + }, [minterType, minterDetails?.minterAcquisitionMethod]) + return (
- +
-

Create Collection

+

+ {minterType === 'base' && minterDetails?.minterAcquisitionMethod === 'existing' + ? 'Mint Token' + : 'Create Collection'} +

@@ -689,7 +762,10 @@ const CollectionCreationPage: NextPage = () => { > From 8993b1b9c01a199e739a217ba962f0904c9298fd Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 13 Dec 2022 21:58:40 +0300 Subject: [PATCH 09/19] Update checkUploadDetails() --- pages/collections/create.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index d6c31bb..baa0154 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -508,6 +508,7 @@ const CollectionCreationPage: NextPage = () => { } const checkUploadDetails = () => { + if (!wallet.initialized) throw new Error('Wallet not connected.') if (!uploadDetails) { throw new Error('Please select assets and metadata') } From 35309e220ae49b1caa09b6f8d1342acc57d3ec49 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 13 Dec 2022 22:21:05 +0300 Subject: [PATCH 10/19] Update react-hot-toast version --- package.json | 3 +-- utils/clipboard.ts | 2 +- yarn.lock | 14 +------------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 17c2a99..9252f0f 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,7 @@ "react-datetime-picker": "^3", "react-dom": "^18", "react-hook-form": "^7", - "react-hot-toast": "2.4.0", - "react-toastify": "9.1.1", + "react-hot-toast": "^2", "react-icons": "^4", "react-popper": "^2", "react-query": "^3", 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/yarn.lock b/yarn.lock index da8a445..159965f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3704,11 +3704,6 @@ clsx@^1: resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== -clsx@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" @@ -6811,7 +6806,7 @@ react-hook-form@^7: resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.30.0.tgz" integrity sha512-DzjiM6o2vtDGNMB9I4yCqW8J21P314SboNG1O0obROkbg7KVS0I7bMtwSdKyapnCPjHgnxc3L7E5PEdISeEUcQ== -react-hot-toast@2.4.0: +react-hot-toast@^2: 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== @@ -6867,13 +6862,6 @@ react-time-picker@^4.5.0: react-fit "^1.4.0" update-input-width "^1.2.2" -react-toastify@9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.1.tgz#9280caea4a13dc1739c350d90660a630807bf10b" - integrity sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw== - dependencies: - clsx "^1.1.1" - react-tracked@^1: version "1.7.9" resolved "https://registry.npmjs.org/react-tracked/-/react-tracked-1.7.9.tgz" From b881ffa5b0788618c32c6797591fe8f4012eb155 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Wed, 14 Dec 2022 10:16:27 +0300 Subject: [PATCH 11/19] Update colors for combobox options --- components/collections/actions/Action.tsx | 2 +- components/collections/actions/Combobox.tsx | 2 +- components/collections/queries/Combobox.tsx | 2 +- components/contracts/baseMinter/ExecuteCombobox.tsx | 2 +- components/contracts/sg721/ExecuteCombobox.tsx | 2 +- components/contracts/vendingMinter/ExecuteCombobox.tsx | 2 +- components/contracts/whitelist/ExecuteCombobox.tsx | 2 +- tailwind.config.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/collections/actions/Action.tsx b/components/collections/actions/Action.tsx index 513d065..322edd9 100644 --- a/components/collections/actions/Action.tsx +++ b/components/collections/actions/Action.tsx @@ -287,7 +287,7 @@ export const CollectionActions = ({ {showTokenIdListField && } {showNumberOfTokensField && } {showPriceField && } - {showDescriptionField && } + {showDescriptionField && } {showImageField && } {showExternalLinkField && } {showRoyaltyRelatedFields && ( diff --git a/components/collections/actions/Combobox.tsx b/components/collections/actions/Combobox.tsx index 4536808..3aa0814 100644 --- a/components/collections/actions/Combobox.tsx +++ b/components/collections/actions/Combobox.tsx @@ -80,7 +80,7 @@ export const ActionsCombobox = ({ value, onChange, minterType }: ActionsCombobox - clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active }) + clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active }) } value={entry} > diff --git a/components/collections/queries/Combobox.tsx b/components/collections/queries/Combobox.tsx index d5ce10c..57f05e6 100644 --- a/components/collections/queries/Combobox.tsx +++ b/components/collections/queries/Combobox.tsx @@ -78,7 +78,7 @@ export const QueryCombobox = ({ value, onChange, minterType }: QueryComboboxProp - clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active }) + clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active }) } value={entry} > diff --git a/components/contracts/baseMinter/ExecuteCombobox.tsx b/components/contracts/baseMinter/ExecuteCombobox.tsx index 7f32de0..9905148 100644 --- a/components/contracts/baseMinter/ExecuteCombobox.tsx +++ b/components/contracts/baseMinter/ExecuteCombobox.tsx @@ -67,7 +67,7 @@ export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => { - clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active }) + clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active }) } value={entry} > diff --git a/components/contracts/sg721/ExecuteCombobox.tsx b/components/contracts/sg721/ExecuteCombobox.tsx index aa67719..926a136 100644 --- a/components/contracts/sg721/ExecuteCombobox.tsx +++ b/components/contracts/sg721/ExecuteCombobox.tsx @@ -67,7 +67,7 @@ export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => { - clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active }) + clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active }) } value={entry} > diff --git a/components/contracts/vendingMinter/ExecuteCombobox.tsx b/components/contracts/vendingMinter/ExecuteCombobox.tsx index 0f01538..3b9a6d4 100644 --- a/components/contracts/vendingMinter/ExecuteCombobox.tsx +++ b/components/contracts/vendingMinter/ExecuteCombobox.tsx @@ -67,7 +67,7 @@ export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => { - clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active }) + clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active }) } value={entry} > diff --git a/components/contracts/whitelist/ExecuteCombobox.tsx b/components/contracts/whitelist/ExecuteCombobox.tsx index 7dce2a9..2db88e8 100644 --- a/components/contracts/whitelist/ExecuteCombobox.tsx +++ b/components/contracts/whitelist/ExecuteCombobox.tsx @@ -67,7 +67,7 @@ export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => { - clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active }) + clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active }) } value={entry} > 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' }, From 2edbea92455135f7fa8da066c919f3e1971703f6 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 15 Dec 2022 12:03:42 +0300 Subject: [PATCH 12/19] Update asset preview for the Base Minter option --- components/AssetsPreview.tsx | 49 ++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/components/AssetsPreview.tsx b/components/AssetsPreview.tsx index d0ba432..25f6a25 100644 --- a/components/AssetsPreview.tsx +++ b/components/AssetsPreview.tsx @@ -25,7 +25,11 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex, minter tempArray.push(
From eab5a4a36cf9919bd34b97f4da58710ddef271dd Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 15 Dec 2022 13:58:38 +0300 Subject: [PATCH 13/19] Update contract dashboard landing page --- .../collections/creation/UploadDetails.tsx | 44 ++++++++++--------- pages/collections/create.tsx | 6 ++- pages/contracts/index.tsx | 15 ++++++- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/components/collections/creation/UploadDetails.tsx b/components/collections/creation/UploadDetails.tsx index 97779dd..78cfd70 100644 --- a/components/collections/creation/UploadDetails.tsx +++ b/components/collections/creation/UploadDetails.tsx @@ -92,16 +92,18 @@ export const UploadDetails = ({ onChange, minterType, minterAcquisitionMethod }: setAssetFilesArray([]) setMetadataFilesArray([]) if (event.target.files === null) return - //sort the files - const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name)) - //check if the sorted file names are in numerical order - const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0]) - for (let i = 0; i < sortedFileNames.length; i++) { - if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) { - toast.error('The file names should be in numerical order starting from 1.') - //clear the input - event.target.value = '' - return + if (minterType === 'vending') { + //sort the files + const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name)) + //check if the sorted file names are in numerical order + const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0]) + for (let i = 0; i < sortedFileNames.length; i++) { + if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) { + toast.error('The file names should be in numerical order starting from 1.') + //clear the input + event.target.value = '' + return + } } } let loadedFileCount = 0 @@ -133,16 +135,18 @@ export const UploadDetails = ({ onChange, minterType, minterAcquisitionMethod }: event.target.value = '' return toast.error('The number of metadata files should be equal to the number of asset files.') } - //sort the files - const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name)) - //check if the sorted file names are in numerical order - const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0]) - for (let i = 0; i < sortedFileNames.length; i++) { - if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) { - toast.error('The file names should be in numerical order starting from 1.') - //clear the input - event.target.value = '' - return + if (minterType === 'vending') { + //sort the files + const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name)) + //check if the sorted file names are in numerical order + const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0]) + for (let i = 0; i < sortedFileNames.length; i++) { + if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) { + toast.error('The file names should be in numerical order starting from 1.') + //clear the input + event.target.value = '' + return + } } } let loadedFileCount = 0 diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index baa0154..e7c6753 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -276,7 +276,8 @@ const CollectionCreationPage: NextPage = () => { setBaseTokenUri(baseUri) const result = await baseMinterContract .use(minterDetails?.existingMinter as string) - ?.mint(wallet.address, `ipfs://${baseUri}`) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + ?.mint(wallet.address, `ipfs://${baseUri}/${uploadDetails?.metadataFiles[0].name.split('.')[0]}`) console.log(result) return result }) @@ -521,7 +522,8 @@ const CollectionCreationPage: NextPage = () => { if (uploadDetails.uploadMethod === 'new' && uploadDetails.metadataFiles.length === 0) { throw new Error('Please select the metadata files') } - if (uploadDetails.uploadMethod === 'new') compareFileArrays(uploadDetails.assetFiles, uploadDetails.metadataFiles) + if (uploadDetails.uploadMethod === 'new' && minterType === 'vending') + compareFileArrays(uploadDetails.assetFiles, uploadDetails.metadataFiles) if (uploadDetails.uploadMethod === 'new') { if (uploadDetails.uploadService === 'nft-storage') { if (uploadDetails.nftStorageApiKey === '') { diff --git a/pages/contracts/index.tsx b/pages/contracts/index.tsx index e83f52d..0991421 100644 --- a/pages/contracts/index.tsx +++ b/pages/contracts/index.tsx @@ -20,8 +20,19 @@ const HomePage: NextPage = () => {
- - Execute messages and run queries on Stargaze's minter contract. + + Execute messages and run queries on Stargaze's Base Minter contract. + + + Execute messages and run queries on Stargaze's Vending Minter contract. Execute messages and run queries on Stargaze's sg721 contract. From 2ee9f1e73d3b128aa550952ca9d62e4de3969d58 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 15 Dec 2022 14:59:05 +0300 Subject: [PATCH 14/19] Update Base Minter creation process --- components/collections/queries/Queries.tsx | 2 +- components/collections/queries/query.ts | 7 +++- pages/collections/create.tsx | 47 +++++++++++++++++++--- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/components/collections/queries/Queries.tsx b/components/collections/queries/Queries.tsx index 931e661..e44ffa1 100644 --- a/components/collections/queries/Queries.tsx +++ b/components/collections/queries/Queries.tsx @@ -50,7 +50,7 @@ export const CollectionQueries = ({ const address = addressState.value const showTokenIdField = type === 'token_info' - const showAddressField = type === 'tokens_minted_to_user' + const showAddressField = type === 'tokens_minted_to_user' || type === 'tokens' const { data: response } = useQuery( [sg721Messages, baseMinterMessages, vendingMinterMessages, type, tokenId, address] as const, diff --git a/components/collections/queries/query.ts b/components/collections/queries/query.ts index 37b041b..235ec8c 100644 --- a/components/collections/queries/query.ts +++ b/components/collections/queries/query.ts @@ -9,6 +9,7 @@ export const QUERY_TYPES = [ 'mint_price', 'num_tokens', 'tokens_minted_to_user', + 'tokens', // 'token_owners', 'token_info', 'config', @@ -70,7 +71,7 @@ export const BASE_QUERY_LIST: QueryListItem[] = [ description: `Get information about the collection.`, }, { - id: 'tokens_minted_to_user', + id: 'tokens', name: 'Tokens Minted to User', description: `Get the number of tokens minted in the collection to a user.`, }, @@ -108,6 +109,7 @@ export type DispatchQueryArgs = { | { type: Select<'mint_price'> } | { type: Select<'num_tokens'> } | { type: Select<'tokens_minted_to_user'>; address: string } + | { type: Select<'tokens'>; address: string } // | { type: Select<'token_owners'> } | { type: Select<'token_info'>; tokenId: string } | { type: Select<'config'> } @@ -132,6 +134,9 @@ export const dispatchQuery = async (args: DispatchQueryArgs) => { case 'tokens_minted_to_user': { return vendingMinterMessages.getMintCount(args.address) } + case 'tokens': { + return sg721Messages.tokens(args.address) + } // case 'token_owners': { // return vendingMinterMessages.updateStartTime(txSigner, args.startTime) // } diff --git a/pages/collections/create.tsx b/pages/collections/create.tsx index e7c6753..c98a8af 100644 --- a/pages/collections/create.tsx +++ b/pages/collections/create.tsx @@ -241,7 +241,10 @@ const CollectionCreationPage: NextPage = () => { setBaseTokenUri(baseUri) setCoverImageUrl(coverImageUri) - await instantiateBaseMinter(baseUri, coverImageUri) + await instantiateBaseMinter( + `ipfs://${baseUri}/${uploadDetails.metadataFiles[0].name.split('.')[0]}`, + coverImageUri, + ) } else { setBaseTokenUri(uploadDetails?.baseTokenURI as string) setCoverImageUrl(uploadDetails?.imageUrl as string) @@ -398,6 +401,7 @@ const CollectionCreationPage: NextPage = () => { const instantiateBaseMinter = async (baseUri: string, coverImageUri: string) => { if (!wallet.initialized) throw new Error('Wallet not connected') if (!baseFactoryContract) throw new Error('Contract not found') + if (!baseMinterContract) throw new Error('Contract not found') let royaltyInfo = null if (royaltyDetails?.royaltyType === 'new') { @@ -438,10 +442,37 @@ const CollectionCreationPage: NextPage = () => { msg, funds: [coin('1000000000', 'ustars')], } - const data = await baseFactoryDispatchExecute(payload) - setTransactionHash(data.transactionHash) - setVendingMinterContractAddress(data.baseMinterAddress) - setSg721ContractAddress(data.sg721Address) + await baseFactoryDispatchExecute(payload) + .then(async (data) => { + setTransactionHash(data.transactionHash) + setVendingMinterContractAddress(data.baseMinterAddress) + setSg721ContractAddress(data.sg721Address) + await toast + .promise( + baseMinterContract + .use(data.baseMinterAddress) + + ?.mint(wallet.address, baseUri) as Promise, + { + loading: 'Minting token...', + success: (result) => `Token minted successfully! Tx Hash: ${result}`, + error: (error) => `Failed to mint token: ${error.message}`, + }, + { style: { maxWidth: 'none' } }, + ) + .catch((error) => { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setUploading(false) + setCreatingCollection(false) + }) + setUploading(false) + setCreatingCollection(false) + }) + .catch((error) => { + toast.error(error.message, { style: { maxWidth: 'none' } }) + setUploading(false) + setCreatingCollection(false) + }) } const uploadFiles = async (): Promise => { @@ -690,7 +721,11 @@ const CollectionCreationPage: NextPage = () => { {vendingMinterContractAddress} From 285097870ef1485deb5879f80e588f37b13289d9 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 15 Dec 2022 15:00:04 +0300 Subject: [PATCH 15/19] Bump Studio version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9252f0f..22f78c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.3.0", + "version": "0.3.1", "workspaces": [ "packages/*" ], From 66e865277d5f1e8870bb8cc52fb05d82afa912c9 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 19 Dec 2022 15:01:00 +0300 Subject: [PATCH 16/19] Stargaze Names WL support on Collection Creation --- .env.example | 3 +- components/WhitelistUpload.tsx | 66 +++++++++++++++++++++++++++++++--- env.d.ts | 1 + utils/constants.ts | 1 + 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index d2bef2d..7a49465 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,11 @@ -APP_VERSION=0.3.0 +APP_VERSION=0.3.1 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_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr" NEXT_PUBLIC_BASE_MINTER_CODE_ID=613 NEXT_PUBLIC_WHITELIST_CODE_ID=277 diff --git a/components/WhitelistUpload.tsx b/components/WhitelistUpload.tsx index 22e96a5..317cd7a 100644 --- a/components/WhitelistUpload.tsx +++ b/components/WhitelistUpload.tsx @@ -1,10 +1,13 @@ /* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable no-misleading-character-class */ /* eslint-disable no-control-regex */ +import { toUtf8 } from '@cosmjs/encoding' import clsx from 'clsx' -import React from 'react' +import React, { useState } from 'react' import { toast } from 'react-hot-toast' +import { useWallet } from '../contexts/wallet' +import { SG721_NAME_ADDRESS } from '../utils/constants' import { isValidAddress } from '../utils/isValidAddress' interface WhitelistUploadProps { @@ -12,7 +15,43 @@ interface WhitelistUploadProps { } export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => { + const wallet = useWallet() + const [resolvedAddresses, setResolvedAddresses] = useState([]) + + const resolveAddresses = async (names: string[]) => { + await new Promise((resolve) => { + let i = 0 + names.map(async (name) => { + if (!wallet.client) throw new Error('Wallet not connected') + await wallet.client + .queryContractRaw( + SG721_NAME_ADDRESS, + toUtf8( + Buffer.from( + `0006${Buffer.from('tokens').toString('hex')}${Buffer.from(name).toString('hex')}`, + 'hex', + ).toString(), + ), + ) + .then((res) => { + const tokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri + if (tokenUri && isValidAddress(tokenUri)) resolvedAddresses.push(tokenUri) + else toast.error(`Resolved address is empty or invalid for the name: ${name}.stars`) + }) + .catch((e) => { + console.log(e) + toast.error(`Error resolving address for the name: ${name}.stars`) + }) + + i++ + if (i === names.length) resolve(resolvedAddresses) + }) + }) + return resolvedAddresses + } + const onFileChange = (event: React.ChangeEvent) => { + setResolvedAddresses([]) if (!event.target.files) return toast.error('Error opening file') if (event.target.files.length !== 1) { toast.error('No file selected') @@ -24,7 +63,7 @@ export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => { return onChange([]) } const reader = new FileReader() - reader.onload = (e: ProgressEvent) => { + reader.onload = async (e: ProgressEvent) => { const text = e.target?.result?.toString() let newline = '\n' if (text?.includes('\r')) newline = '\r' @@ -35,12 +74,29 @@ export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => { const regex = /[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u2020-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g const printableData = data?.map((item) => item.replace(regex, '')) + const names = printableData?.filter((address) => address !== '' && address.endsWith('.stars')) + const strippedNames = names?.map((name) => name.split('.')[0]) + console.log(names) + if (strippedNames?.length) { + await toast + .promise(resolveAddresses(strippedNames), { + loading: 'Resolving addresses...', + success: 'Address resolution successful!', + error: 'Address resolution failed!', + }) + .then((addresses) => { + console.log(addresses) + }) + .catch((error) => { + console.log(error) + }) + } return onChange([ ...new Set( - printableData?.filter( - (address) => address !== '' && isValidAddress(address) && address.startsWith('stars'), - ) || [], + printableData + ?.filter((address) => address !== '' && isValidAddress(address) && address.startsWith('stars')) + .concat(resolvedAddresses) || [], ), ]) } diff --git a/env.d.ts b/env.d.ts index bc8526b..3852406 100644 --- a/env.d.ts +++ b/env.d.ts @@ -19,6 +19,7 @@ declare namespace NodeJS { readonly NEXT_PUBLIC_VENDING_MINTER_CODE_ID: string readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string readonly NEXT_PUBLIC_BASE_FACTORY_ADDRESS: string + readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string readonly NEXT_PUBLIC_BASE_MINTER_CODE_ID: string readonly NEXT_PUBLIC_PINATA_ENDPOINT_URL: string diff --git a/utils/constants.ts b/utils/constants.ts index 38c4d3a..1c0896b 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -3,6 +3,7 @@ export const WHITELIST_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_CODE 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 SG721_NAME_ADDRESS = process.env.NEXT_PUBLIC_SG721_NAME_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 From 7a3b3b763fc1544c97c636b93be8d2ca4ce10987 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Mon, 19 Dec 2022 16:08:20 +0300 Subject: [PATCH 17/19] Stargaze Names support for Whitelist Execute Tab - Add/Remove member --- components/forms/AddressList.tsx | 44 +++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/components/forms/AddressList.tsx b/components/forms/AddressList.tsx index cbc342a..59dad9d 100644 --- a/components/forms/AddressList.tsx +++ b/components/forms/AddressList.tsx @@ -1,7 +1,12 @@ +import { toUtf8 } from '@cosmjs/encoding' import { FormControl } from 'components/FormControl' import { AddressInput } from 'components/forms/FormInput' +import { useWallet } from 'contexts/wallet' import { useEffect, useId, useMemo } from 'react' +import toast from 'react-hot-toast' import { FaMinus, FaPlus } from 'react-icons/fa' +import { SG721_NAME_ADDRESS } from 'utils/constants' +import { isValidAddress } from 'utils/isValidAddress' import { useInputState } from './FormInput.hooks' @@ -46,6 +51,7 @@ export interface AddressProps { } export function Address({ id, isLast, onAdd, onChange, onRemove }: AddressProps) { + const wallet = useWallet() const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast]) const htmlId = useId() @@ -56,10 +62,40 @@ export function Address({ id, isLast, onAdd, onChange, onRemove }: AddressProps) title: ``, }) + const resolveAddress = async (name: string) => { + if (!wallet.client) throw new Error('Wallet not connected') + await wallet.client + .queryContractRaw( + SG721_NAME_ADDRESS, + toUtf8( + Buffer.from( + `0006${Buffer.from('tokens').toString('hex')}${Buffer.from(name).toString('hex')}`, + 'hex', + ).toString(), + ), + ) + .then((res) => { + const tokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri + if (tokenUri && isValidAddress(tokenUri)) onChange(id, { address: tokenUri }) + else { + toast.error(`Resolved address is empty or invalid for the name: ${name}.stars`) + onChange(id, { address: '' }) + } + }) + .catch((err) => { + toast.error(`Error resolving address for the name: ${name}.stars`) + console.error(err) + onChange(id, { address: '' }) + }) + } useEffect(() => { - onChange(id, { - address: addressState.value, - }) + if (addressState.value.endsWith('.stars')) { + void resolveAddress(addressState.value.split('.')[0]) + } else { + onChange(id, { + address: addressState.value, + }) + } }, [addressState.value, id]) return ( @@ -67,7 +103,7 @@ export function Address({ id, isLast, onAdd, onChange, onRemove }: AddressProps)
-
-
+ + {/* To be removed */} + +
+ + + {/* /To be removed */} +
- -
-
- +
+
-

Base Minter

- Base Minter contract enables 1/1 minting - + +
-
+ {minterType === 'base' && (
diff --git a/pages/contracts/index.tsx b/pages/contracts/index.tsx index 0991421..685a190 100644 --- a/pages/contracts/index.tsx +++ b/pages/contracts/index.tsx @@ -1,8 +1,11 @@ +import { Conditional } from 'components/Conditional' import { HomeCard } from 'components/HomeCard' import type { NextPage } from 'next' // import Brand from 'public/brand/brand.svg' import { withMetadata } from 'utils/layout' +import { BASE_FACTORY_ADDRESS } from '../../utils/constants' + const HomePage: NextPage = () => { return (
@@ -20,13 +23,15 @@ const HomePage: NextPage = () => {
- - Execute messages and run queries on Stargaze's Base Minter contract. - + + + Execute messages and run queries on Stargaze's Base Minter contract. + +