From a8c25485544cd1633f4ded748b732d5ba991a375 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Fri, 9 Dec 2022 11:27:50 +0300 Subject: [PATCH] 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