Implement contract UIs (#2)
* Add instantiate page for minter * Add query page to minter contract * Add execute page for minter contract * Add contracts index page * Refaactor sg721 helper files * Add instantiate page * Add query page for sg721 * Add execute page for sg721 contract * Copy page templates for whitelist contracts * Add instantitate for whitelist contract * Add query page to whitelist contract * Add execute page for whitelist contract
This commit is contained in:
parent
3a9a523e01
commit
aa42f8763a
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -9,7 +9,7 @@
|
|||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"css.validate": false,
|
"css.validate": false,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
@ -17,3 +17,39 @@ export const sg721LinkTabs: LinkTabProps[] = [
|
|||||||
href: '/contracts/sg721/execute',
|
href: '/contracts/sg721/execute',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const minterLinkTabs: LinkTabProps[] = [
|
||||||
|
{
|
||||||
|
title: 'Instantiate',
|
||||||
|
description: `Initialize a new Minter contract`,
|
||||||
|
href: '/contracts/minter/instantiate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Query',
|
||||||
|
description: `Dispatch queries with your Minter contract`,
|
||||||
|
href: '/contracts/minter/query',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Execute',
|
||||||
|
description: `Execute Minter contract actions`,
|
||||||
|
href: '/contracts/minter/execute',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const whitelistLinkTabs: LinkTabProps[] = [
|
||||||
|
{
|
||||||
|
title: 'Instantiate',
|
||||||
|
description: `Initialize a new Whitelist contract`,
|
||||||
|
href: '/contracts/whitelist/instantiate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Query',
|
||||||
|
description: `Dispatch queries with your Whitelist contract`,
|
||||||
|
href: '/contracts/whitelist/query',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Execute',
|
||||||
|
description: `Execute Whitelist contract actions`,
|
||||||
|
href: '/contracts/whitelist/execute',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
@ -2,7 +2,7 @@ import clsx from 'clsx'
|
|||||||
import { Anchor } from 'components/Anchor'
|
import { Anchor } from 'components/Anchor'
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import BrandText from 'public/brand/brand-text.svg'
|
// import BrandText from 'public/brand/brand-text.svg'
|
||||||
import { footerLinks, links, socialsLinks } from 'utils/links'
|
import { footerLinks, links, socialsLinks } from 'utils/links'
|
||||||
|
|
||||||
import { SidebarLayout } from './SidebarLayout'
|
import { SidebarLayout } from './SidebarLayout'
|
||||||
@ -10,6 +10,7 @@ import { WalletLoader } from './WalletLoader'
|
|||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ text: 'Create Collection', href: `/collection/` },
|
{ text: 'Create Collection', href: `/collection/` },
|
||||||
|
{ text: 'Contract Dashboards', href: `/contracts/` },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
@ -23,7 +24,7 @@ export const Sidebar = () => {
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-1/2 h-16',
|
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-1/2 h-16',
|
||||||
'rounded border-2 border-white/20 border-dashed'
|
'rounded border-2 border-white/20 border-dashed',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Home{/* <BrandText className="text-plumbus hover:text-plumbus-light transition" /> */}
|
Home{/* <BrandText className="text-plumbus hover:text-plumbus-light transition" /> */}
|
||||||
|
7
components/contracts/minter/ExecuteCombobox.hooks.ts
Normal file
7
components/contracts/minter/ExecuteCombobox.hooks.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { ExecuteListItem } from 'contracts/minter/messages/execute'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export const useExecuteComboboxState = () => {
|
||||||
|
const [value, setValue] = useState<ExecuteListItem | null>(null)
|
||||||
|
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
|
||||||
|
}
|
92
components/contracts/minter/ExecuteCombobox.tsx
Normal file
92
components/contracts/minter/ExecuteCombobox.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
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 { 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 (
|
||||||
|
<Combobox
|
||||||
|
as={FormControl}
|
||||||
|
htmlId="message-type"
|
||||||
|
labelAs={Combobox.Label}
|
||||||
|
onChange={onChange}
|
||||||
|
subtitle="Contract execute message type"
|
||||||
|
title="Message Type"
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<Combobox.Input
|
||||||
|
className={clsx(
|
||||||
|
'w-full bg-white/10 rounded border-2 border-white/20 form-input',
|
||||||
|
'placeholder:text-white/50',
|
||||||
|
'focus:ring focus:ring-plumbus-20',
|
||||||
|
)}
|
||||||
|
displayValue={(val?: ExecuteListItem) => val?.name ?? ''}
|
||||||
|
id="message-type"
|
||||||
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
|
placeholder="Select message type"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Combobox.Button
|
||||||
|
className={clsx(
|
||||||
|
'flex absolute inset-y-0 right-0 items-center p-4',
|
||||||
|
'opacity-50 hover:opacity-100 active:opacity-100',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({ open }) => <FaChevronDown aria-hidden="true" className={clsx('w-4 h-4', { 'rotate-180': open })} />}
|
||||||
|
</Combobox.Button>
|
||||||
|
|
||||||
|
<Transition afterLeave={() => setSearch('')} as={Fragment}>
|
||||||
|
<Combobox.Options
|
||||||
|
className={clsx(
|
||||||
|
'overflow-auto absolute z-10 mt-2 w-full max-h-[30vh]',
|
||||||
|
'bg-stone-800/80 rounded shadow-lg backdrop-blur-sm',
|
||||||
|
'divide-y divide-stone-500/50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{filtered.length < 1 && (
|
||||||
|
<span className="flex flex-col justify-center items-center p-4 text-sm text-center text-white/50">
|
||||||
|
Message type not found.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{filtered.map((entry) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={entry.id}
|
||||||
|
className={({ active }) =>
|
||||||
|
clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active })
|
||||||
|
}
|
||||||
|
value={entry}
|
||||||
|
>
|
||||||
|
<span className="font-bold">{entry.name}</span>
|
||||||
|
<span className="max-w-md text-sm">{entry.description}</span>
|
||||||
|
</Combobox.Option>
|
||||||
|
))}
|
||||||
|
</Combobox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{value && (
|
||||||
|
<div className="flex space-x-2 text-white/50">
|
||||||
|
<div className="mt-1">
|
||||||
|
<FaInfoCircle className="w-3 h-3" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm">{value.description}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
)
|
||||||
|
}
|
7
components/contracts/sg721/ExecuteCombobox.hooks.ts
Normal file
7
components/contracts/sg721/ExecuteCombobox.hooks.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { ExecuteListItem } from 'contracts/sg721/messages/execute'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export const useExecuteComboboxState = () => {
|
||||||
|
const [value, setValue] = useState<ExecuteListItem | null>(null)
|
||||||
|
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
|
||||||
|
}
|
92
components/contracts/sg721/ExecuteCombobox.tsx
Normal file
92
components/contracts/sg721/ExecuteCombobox.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { Combobox, Transition } from '@headlessui/react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { FormControl } from 'components/FormControl'
|
||||||
|
import type { ExecuteListItem } from 'contracts/sg721/messages/execute'
|
||||||
|
import { EXECUTE_LIST } from 'contracts/sg721/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 (
|
||||||
|
<Combobox
|
||||||
|
as={FormControl}
|
||||||
|
htmlId="message-type"
|
||||||
|
labelAs={Combobox.Label}
|
||||||
|
onChange={onChange}
|
||||||
|
subtitle="Contract execute message type"
|
||||||
|
title="Message Type"
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<Combobox.Input
|
||||||
|
className={clsx(
|
||||||
|
'w-full bg-white/10 rounded border-2 border-white/20 form-input',
|
||||||
|
'placeholder:text-white/50',
|
||||||
|
'focus:ring focus:ring-plumbus-20',
|
||||||
|
)}
|
||||||
|
displayValue={(val?: ExecuteListItem) => val?.name ?? ''}
|
||||||
|
id="message-type"
|
||||||
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
|
placeholder="Select message type"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Combobox.Button
|
||||||
|
className={clsx(
|
||||||
|
'flex absolute inset-y-0 right-0 items-center p-4',
|
||||||
|
'opacity-50 hover:opacity-100 active:opacity-100',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({ open }) => <FaChevronDown aria-hidden="true" className={clsx('w-4 h-4', { 'rotate-180': open })} />}
|
||||||
|
</Combobox.Button>
|
||||||
|
|
||||||
|
<Transition afterLeave={() => setSearch('')} as={Fragment}>
|
||||||
|
<Combobox.Options
|
||||||
|
className={clsx(
|
||||||
|
'overflow-auto absolute z-10 mt-2 w-full max-h-[30vh]',
|
||||||
|
'bg-stone-800/80 rounded shadow-lg backdrop-blur-sm',
|
||||||
|
'divide-y divide-stone-500/50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{filtered.length < 1 && (
|
||||||
|
<span className="flex flex-col justify-center items-center p-4 text-sm text-center text-white/50">
|
||||||
|
Message type not found.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{filtered.map((entry) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={entry.id}
|
||||||
|
className={({ active }) =>
|
||||||
|
clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active })
|
||||||
|
}
|
||||||
|
value={entry}
|
||||||
|
>
|
||||||
|
<span className="font-bold">{entry.name}</span>
|
||||||
|
<span className="max-w-md text-sm">{entry.description}</span>
|
||||||
|
</Combobox.Option>
|
||||||
|
))}
|
||||||
|
</Combobox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{value && (
|
||||||
|
<div className="flex space-x-2 text-white/50">
|
||||||
|
<div className="mt-1">
|
||||||
|
<FaInfoCircle className="w-3 h-3" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm">{value.description}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
)
|
||||||
|
}
|
7
components/contracts/whitelist/ExecuteCombobox.hooks.ts
Normal file
7
components/contracts/whitelist/ExecuteCombobox.hooks.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { ExecuteListItem } from 'contracts/whitelist/messages/execute'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export const useExecuteComboboxState = () => {
|
||||||
|
const [value, setValue] = useState<ExecuteListItem | null>(null)
|
||||||
|
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
|
||||||
|
}
|
92
components/contracts/whitelist/ExecuteCombobox.tsx
Normal file
92
components/contracts/whitelist/ExecuteCombobox.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { Combobox, Transition } from '@headlessui/react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { FormControl } from 'components/FormControl'
|
||||||
|
import type { ExecuteListItem } from 'contracts/whitelist/messages/execute'
|
||||||
|
import { EXECUTE_LIST } from 'contracts/whitelist/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 (
|
||||||
|
<Combobox
|
||||||
|
as={FormControl}
|
||||||
|
htmlId="message-type"
|
||||||
|
labelAs={Combobox.Label}
|
||||||
|
onChange={onChange}
|
||||||
|
subtitle="Contract execute message type"
|
||||||
|
title="Message Type"
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<Combobox.Input
|
||||||
|
className={clsx(
|
||||||
|
'w-full bg-white/10 rounded border-2 border-white/20 form-input',
|
||||||
|
'placeholder:text-white/50',
|
||||||
|
'focus:ring focus:ring-plumbus-20',
|
||||||
|
)}
|
||||||
|
displayValue={(val?: ExecuteListItem) => val?.name ?? ''}
|
||||||
|
id="message-type"
|
||||||
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
|
placeholder="Select message type"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Combobox.Button
|
||||||
|
className={clsx(
|
||||||
|
'flex absolute inset-y-0 right-0 items-center p-4',
|
||||||
|
'opacity-50 hover:opacity-100 active:opacity-100',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({ open }) => <FaChevronDown aria-hidden="true" className={clsx('w-4 h-4', { 'rotate-180': open })} />}
|
||||||
|
</Combobox.Button>
|
||||||
|
|
||||||
|
<Transition afterLeave={() => setSearch('')} as={Fragment}>
|
||||||
|
<Combobox.Options
|
||||||
|
className={clsx(
|
||||||
|
'overflow-auto absolute z-10 mt-2 w-full max-h-[30vh]',
|
||||||
|
'bg-stone-800/80 rounded shadow-lg backdrop-blur-sm',
|
||||||
|
'divide-y divide-stone-500/50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{filtered.length < 1 && (
|
||||||
|
<span className="flex flex-col justify-center items-center p-4 text-sm text-center text-white/50">
|
||||||
|
Message type not found.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{filtered.map((entry) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={entry.id}
|
||||||
|
className={({ active }) =>
|
||||||
|
clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active })
|
||||||
|
}
|
||||||
|
value={entry}
|
||||||
|
>
|
||||||
|
<span className="font-bold">{entry.name}</span>
|
||||||
|
<span className="max-w-md text-sm">{entry.description}</span>
|
||||||
|
</Combobox.Option>
|
||||||
|
))}
|
||||||
|
</Combobox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{value && (
|
||||||
|
<div className="flex space-x-2 text-white/50">
|
||||||
|
<div className="mt-1">
|
||||||
|
<FaInfoCircle className="w-3 h-3" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm">{value.description}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
)
|
||||||
|
}
|
@ -11,6 +11,7 @@ interface BaseProps {
|
|||||||
name: string
|
name: string
|
||||||
title: string
|
title: string
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
|
isRequired?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type SlicedInputProps = Omit<ComponentPropsWithRef<'textarea'>, keyof BaseProps>
|
type SlicedInputProps = Omit<ComponentPropsWithRef<'textarea'>, keyof BaseProps>
|
||||||
@ -19,10 +20,10 @@ export type FormTextAreaProps = BaseProps & SlicedInputProps
|
|||||||
|
|
||||||
export const FormTextArea = forwardRef<HTMLTextAreaElement, FormTextAreaProps>(
|
export const FormTextArea = forwardRef<HTMLTextAreaElement, FormTextAreaProps>(
|
||||||
function FormTextArea(props, ref) {
|
function FormTextArea(props, ref) {
|
||||||
const { id, name, title, subtitle, ...rest } = props
|
const { id, name, title, subtitle, isRequired, ...rest } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl htmlId={id} subtitle={subtitle} title={title}>
|
<FormControl htmlId={id} isRequired={isRequired} subtitle={subtitle} title={title}>
|
||||||
<StyledTextArea id={id} name={name} ref={ref} {...rest} />
|
<StyledTextArea id={id} name={name} ref={ref} {...rest} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,7 @@ export const mainnetConfig: AppConfig = {
|
|||||||
|
|
||||||
export const testnetConfig: AppConfig = {
|
export const testnetConfig: AppConfig = {
|
||||||
chainId: 'elgafar-1',
|
chainId: 'elgafar-1',
|
||||||
chainName: 'elgafar-1',
|
chainName: 'elgarfar-1',
|
||||||
addressPrefix: 'stars',
|
addressPrefix: 'stars',
|
||||||
rpcUrl: 'https://rpc.elgafar-1.stargaze-apis.com/',
|
rpcUrl: 'https://rpc.elgafar-1.stargaze-apis.com/',
|
||||||
feeToken: 'ustars',
|
feeToken: 'ustars',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useMinterContract, UseMinterContractProps } from 'contracts/minter'
|
import type { UseMinterContractProps } from 'contracts/minter'
|
||||||
import { useSG721Contract, UseSG721ContractProps } from 'contracts/sg721'
|
import { useMinterContract } from 'contracts/minter'
|
||||||
import {
|
import type { UseSG721ContractProps } from 'contracts/sg721'
|
||||||
useWhiteListContract,
|
import { useSG721Contract } from 'contracts/sg721'
|
||||||
useWhiteListContractProps,
|
import type { UseWhiteListContractProps } from 'contracts/whitelist'
|
||||||
} from 'contracts/whitelist'
|
import { useWhiteListContract } from 'contracts/whitelist'
|
||||||
|
import type { ReactNode, VFC } from 'react'
|
||||||
import { Fragment, ReactNode, useEffect, VFC } from 'react'
|
import { Fragment, useEffect } from 'react'
|
||||||
import create, { State } from 'zustand'
|
import type { State } from 'zustand'
|
||||||
|
import create from 'zustand'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contracts store type definitions
|
* Contracts store type definitions
|
||||||
@ -15,7 +15,7 @@ import create, { State } from 'zustand'
|
|||||||
export interface ContractsStore extends State {
|
export interface ContractsStore extends State {
|
||||||
sg721: UseSG721ContractProps | null
|
sg721: UseSG721ContractProps | null
|
||||||
minter: UseMinterContractProps | null
|
minter: UseMinterContractProps | null
|
||||||
whitelist: useWhiteListContractProps | null
|
whitelist: UseWhiteListContractProps | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,35 +40,25 @@ export const useContracts = create<ContractsStore>(() => ({
|
|||||||
*/
|
*/
|
||||||
export const ContractsProvider = ({ children }: { children: ReactNode }) => {
|
export const ContractsProvider = ({ children }: { children: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
{children}
|
{children}
|
||||||
<ContractsSubscription />
|
<ContractsSubscription />
|
||||||
</Fragment>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Contracts store subscriptions (side effects)
|
|
||||||
*
|
|
||||||
* @todo refactor all contract logics to zustand store
|
|
||||||
*/
|
|
||||||
const ContractsSubscription: VFC = () => {
|
const ContractsSubscription: VFC = () => {
|
||||||
const sg721 = useSG721Contract()
|
const sg721 = useSG721Contract()
|
||||||
const minter = useMinterContract()
|
const minter = useMinterContract()
|
||||||
const whitelist = useWhiteListContract()
|
const whitelist = useWhiteListContract()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
useContracts.setState({
|
useContracts.setState({
|
||||||
sg721,
|
sg721,
|
||||||
minter,
|
minter,
|
||||||
whitelist,
|
whitelist,
|
||||||
})
|
})
|
||||||
}, [
|
}, [sg721, minter, whitelist])
|
||||||
sg721,
|
|
||||||
minter,
|
|
||||||
whitelist,
|
|
||||||
])
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
import { Coin } from '@cosmjs/proto-signing'
|
import type { Coin } from '@cosmjs/proto-signing'
|
||||||
import { logs } from '@cosmjs/stargate'
|
import { coin } from '@cosmjs/proto-signing'
|
||||||
import { Timestamp } from '@stargazezone/types/contracts/minter/shared-types'
|
import type { logs } from '@cosmjs/stargate'
|
||||||
|
import type { Timestamp } from '@stargazezone/types/contracts/minter/shared-types'
|
||||||
|
|
||||||
export interface InstantiateResponse {
|
export interface InstantiateResponse {
|
||||||
readonly contractAddress: string
|
readonly contractAddress: string
|
||||||
@ -9,7 +10,7 @@ export interface InstantiateResponse {
|
|||||||
readonly logs: readonly logs.Log[]
|
readonly logs: readonly logs.Log[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoyalityInfo = {
|
export interface RoyalityInfo {
|
||||||
payment_address: string
|
payment_address: string
|
||||||
share: string
|
share: string
|
||||||
}
|
}
|
||||||
@ -25,22 +26,108 @@ export interface MinterInstance {
|
|||||||
getMintCount: (address: string) => Promise<any>
|
getMintCount: (address: string) => Promise<any>
|
||||||
|
|
||||||
//Execute
|
//Execute
|
||||||
mint: (senderAddress: string) => Promise<string>
|
mint: (senderAddress: string, price: string) => Promise<string>
|
||||||
setWhitelist: (senderAddress: string, whitelist: string) => Promise<string>
|
setWhitelist: (senderAddress: string, whitelist: string) => Promise<string>
|
||||||
updateStartTime: (senderAddress: string, time: Timestamp) => Promise<string>
|
updateStartTime: (senderAddress: string, time: Timestamp) => Promise<string>
|
||||||
updatePerAddressLimit: (
|
updatePerAddressLimit: (senderAddress: string, perAddressLimit: number) => Promise<string>
|
||||||
senderAddress: string,
|
|
||||||
per_address_limit: number
|
|
||||||
) => Promise<string>
|
|
||||||
mintTo: (senderAddress: string, recipient: string) => Promise<string>
|
mintTo: (senderAddress: string, recipient: string) => Promise<string>
|
||||||
mintFor: (
|
mintFor: (senderAddress: string, recipient: string, tokenId: number) => Promise<string>
|
||||||
senderAddress: string,
|
shuffle: (senderAddress: string) => Promise<string>
|
||||||
token_id: number,
|
|
||||||
recipient: string
|
|
||||||
) => Promise<string>
|
|
||||||
withdraw: (senderAddress: string) => Promise<string>
|
withdraw: (senderAddress: string) => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MinterMessages {
|
||||||
|
mint: (contractAddress: string, price: string) => MintMessage
|
||||||
|
setWhitelist: (contractAddress: string, whitelist: string) => SetWhitelistMessage
|
||||||
|
updateStartTime: (contractAddress: string, time: Timestamp) => UpdateStarTimeMessage
|
||||||
|
updatePerAddressLimit: (contractAddress: string, perAddressLimit: number) => UpdatePerAddressLimitMessage
|
||||||
|
mintTo: (contractAddress: string, recipient: string) => MintToMessage
|
||||||
|
mintFor: (contractAddress: string, recipient: string, tokenId: number) => MintForMessage
|
||||||
|
shuffle: (contractAddress: string) => ShuffleMessage
|
||||||
|
withdraw: (contractAddress: string) => WithdrawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MintMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
mint: Record<string, never>
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetWhitelistMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
set_whitelist: {
|
||||||
|
whitelist: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateStarTimeMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
update_start_time: string
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePerAddressLimitMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
update_per_address_limit: {
|
||||||
|
per_address_limit: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MintToMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
mint_to: {
|
||||||
|
recipient: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MintForMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
mint_for: {
|
||||||
|
recipient: string
|
||||||
|
token_id: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShuffleMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
shuffle: Record<string, never>
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithdrawMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
withdraw: Record<string, never>
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface MinterContract {
|
export interface MinterContract {
|
||||||
instantiate: (
|
instantiate: (
|
||||||
senderAddress: string,
|
senderAddress: string,
|
||||||
@ -48,13 +135,15 @@ export interface MinterContract {
|
|||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
admin?: string,
|
admin?: string,
|
||||||
funds?: Coin[]
|
funds?: Coin[],
|
||||||
) => Promise<InstantiateResponse>
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
use: (contractAddress: string) => MinterInstance
|
use: (contractAddress: string) => MinterInstance
|
||||||
|
|
||||||
|
messages: () => MinterMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterContract => {
|
||||||
const use = (contractAddress: string): MinterInstance => {
|
const use = (contractAddress: string): MinterInstance => {
|
||||||
//Query
|
//Query
|
||||||
const getConfig = async (): Promise<any> => {
|
const getConfig = async (): Promise<any> => {
|
||||||
@ -93,7 +182,7 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Execute
|
//Execute
|
||||||
const mint = async (senderAddress: string): Promise<string> => {
|
const mint = async (senderAddress: string, price: string): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
@ -101,16 +190,14 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
mint: {},
|
mint: {},
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
|
[coin(price, 'ustars')],
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const setWhitelist = async (
|
const setWhitelist = async (senderAddress: string, whitelist: string): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
whitelist: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
@ -118,16 +205,13 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
set_whitelist: { whitelist },
|
set_whitelist: { whitelist },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateStartTime = async (
|
const updateStartTime = async (senderAddress: string, time: Timestamp): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
time: Timestamp
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
@ -135,33 +219,27 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
update_start_time: { time },
|
update_start_time: { time },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePerAddressLimit = async (
|
const updatePerAddressLimit = async (senderAddress: string, perAddressLimit: number): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
per_address_limit: number
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
update_per_address_limit: { per_address_limit },
|
update_per_address_limit: { per_address_limit: perAddressLimit },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const mintTo = async (
|
const mintTo = async (senderAddress: string, recipient: string): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
recipient: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
@ -169,25 +247,35 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
mint_to: { recipient },
|
mint_to: { recipient },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const mintFor = async (
|
const mintFor = async (senderAddress: string, recipient: string, tokenId: number): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
token_id: number,
|
|
||||||
recipient: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
mint_for: { token_id, recipient },
|
mint_for: { token_id: tokenId, recipient },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const shuffle = async (senderAddress: string): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
shuffle: {},
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
@ -201,7 +289,7 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
withdraw: {},
|
withdraw: {},
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
@ -220,6 +308,7 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
updatePerAddressLimit,
|
updatePerAddressLimit,
|
||||||
mintTo,
|
mintTo,
|
||||||
mintFor,
|
mintFor,
|
||||||
|
shuffle,
|
||||||
withdraw,
|
withdraw,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,21 +318,10 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
codeId: number,
|
codeId: number,
|
||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
admin?: string,
|
|
||||||
funds?: Coin[]
|
|
||||||
): Promise<InstantiateResponse> => {
|
): Promise<InstantiateResponse> => {
|
||||||
console.log(funds)
|
const result = await client.instantiate(senderAddress, codeId, initMsg, label, 'auto', {
|
||||||
const result = await client.instantiate(
|
funds: [coin('1000000000', 'ustars')],
|
||||||
senderAddress,
|
})
|
||||||
codeId,
|
|
||||||
initMsg,
|
|
||||||
label,
|
|
||||||
'auto',
|
|
||||||
{
|
|
||||||
funds,
|
|
||||||
admin,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contractAddress: result.contractAddress,
|
contractAddress: result.contractAddress,
|
||||||
@ -252,5 +330,115 @@ export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { use, instantiate }
|
const messages = () => {
|
||||||
|
const mint = (contractAddress: string, price: string): MintMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
mint: {},
|
||||||
|
},
|
||||||
|
funds: [coin(price, 'ustars')],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setWhitelist = (contractAddress: string, whitelist: string): SetWhitelistMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
set_whitelist: {
|
||||||
|
whitelist,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStartTime = (contractAddress: string, startTime: string): UpdateStarTimeMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
update_start_time: startTime,
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePerAddressLimit = (contractAddress: string, limit: number): UpdatePerAddressLimitMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
update_per_address_limit: {
|
||||||
|
per_address_limit: limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mintTo = (contractAddress: string, recipient: string): MintToMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
mint_to: {
|
||||||
|
recipient,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mintFor = (contractAddress: string, recipient: string, tokenId: number): MintForMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
mint_for: {
|
||||||
|
recipient,
|
||||||
|
token_id: tokenId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shuffle = (contractAddress: string): ShuffleMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
shuffle: {},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const withdraw = (contractAddress: string): WithdrawMessage => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
withdraw: {},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mint,
|
||||||
|
setWhitelist,
|
||||||
|
updateStartTime,
|
||||||
|
updatePerAddressLimit,
|
||||||
|
mintTo,
|
||||||
|
mintFor,
|
||||||
|
shuffle,
|
||||||
|
withdraw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { use, instantiate, messages }
|
||||||
}
|
}
|
||||||
|
158
contracts/minter/messages/execute.ts
Normal file
158
contracts/minter/messages/execute.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import type { MinterInstance } from '../index'
|
||||||
|
import { useMinterContract } from '../index'
|
||||||
|
|
||||||
|
export type ExecuteType = typeof EXECUTE_TYPES[number]
|
||||||
|
|
||||||
|
export const EXECUTE_TYPES = [
|
||||||
|
'mint',
|
||||||
|
'set_whitelist',
|
||||||
|
'update_start_time',
|
||||||
|
'update_per_address_limit',
|
||||||
|
'mint_to',
|
||||||
|
'mint_for',
|
||||||
|
'shuffle',
|
||||||
|
'withdraw',
|
||||||
|
] 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: 'set_whitelist',
|
||||||
|
name: 'Set Whitelist',
|
||||||
|
description: `Set whitelist contract address`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'update_start_time',
|
||||||
|
name: 'Update Start Time',
|
||||||
|
description: `Update start time for minting`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'update_per_address_limit',
|
||||||
|
name: 'Update Per Address Limit',
|
||||||
|
description: `Update token per address limit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mint_to',
|
||||||
|
name: 'Mint To',
|
||||||
|
description: `Mint tokens to a given address`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mint_for',
|
||||||
|
name: 'Mint For',
|
||||||
|
description: `Mint tokens for a given address with a given token ID`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'shuffle',
|
||||||
|
name: 'Shuffle',
|
||||||
|
description: `Shuffle the token IDs`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface DispatchExecuteProps {
|
||||||
|
type: ExecuteType
|
||||||
|
[k: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type Select<T extends ExecuteType> = T
|
||||||
|
|
||||||
|
/** @see {@link MinterInstance} */
|
||||||
|
export type DispatchExecuteArgs = {
|
||||||
|
contract: string
|
||||||
|
messages?: MinterInstance
|
||||||
|
txSigner: string
|
||||||
|
} & (
|
||||||
|
| { type: undefined }
|
||||||
|
| { type: Select<'mint'>; price: string }
|
||||||
|
| { type: Select<'set_whitelist'>; whitelist: string }
|
||||||
|
| { type: Select<'update_start_time'>; startTime: string }
|
||||||
|
| { type: Select<'update_per_address_limit'>; limit: number }
|
||||||
|
| { type: Select<'mint_to'>; recipient: string }
|
||||||
|
| { type: Select<'mint_for'>; recipient: string; tokenId: number }
|
||||||
|
| { type: Select<'shuffle'> }
|
||||||
|
| { type: Select<'withdraw'> }
|
||||||
|
)
|
||||||
|
|
||||||
|
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.price === '' ? '0' : args.price)
|
||||||
|
}
|
||||||
|
case 'set_whitelist': {
|
||||||
|
return messages.setWhitelist(txSigner, args.whitelist)
|
||||||
|
}
|
||||||
|
case 'update_start_time': {
|
||||||
|
return messages.updateStartTime(txSigner, args.startTime)
|
||||||
|
}
|
||||||
|
case 'update_per_address_limit': {
|
||||||
|
return messages.updatePerAddressLimit(txSigner, args.limit)
|
||||||
|
}
|
||||||
|
case 'mint_to': {
|
||||||
|
return messages.mintTo(txSigner, args.recipient)
|
||||||
|
}
|
||||||
|
case 'mint_for': {
|
||||||
|
return messages.mintFor(txSigner, args.recipient, args.tokenId)
|
||||||
|
}
|
||||||
|
case 'shuffle': {
|
||||||
|
return messages.shuffle(txSigner)
|
||||||
|
}
|
||||||
|
case 'withdraw': {
|
||||||
|
return messages.withdraw(txSigner)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error('unknown execute type')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const { messages } = useMinterContract()
|
||||||
|
const { contract } = args
|
||||||
|
switch (args.type) {
|
||||||
|
case 'mint': {
|
||||||
|
return messages()?.mint(contract, args.price === '' ? '0' : args.price)
|
||||||
|
}
|
||||||
|
case 'set_whitelist': {
|
||||||
|
return messages()?.setWhitelist(contract, args.whitelist)
|
||||||
|
}
|
||||||
|
case 'update_start_time': {
|
||||||
|
return messages()?.updateStartTime(contract, args.startTime)
|
||||||
|
}
|
||||||
|
case 'update_per_address_limit': {
|
||||||
|
return messages()?.updatePerAddressLimit(contract, args.limit)
|
||||||
|
}
|
||||||
|
case 'mint_to': {
|
||||||
|
return messages()?.mintTo(contract, args.recipient)
|
||||||
|
}
|
||||||
|
case 'mint_for': {
|
||||||
|
return messages()?.mintFor(contract, args.recipient, args.tokenId)
|
||||||
|
}
|
||||||
|
case 'shuffle': {
|
||||||
|
return messages()?.shuffle(contract)
|
||||||
|
}
|
||||||
|
case 'withdraw': {
|
||||||
|
return messages()?.withdraw(contract)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
|
||||||
|
return arr.some((val) => type === val)
|
||||||
|
}
|
53
contracts/minter/messages/query.ts
Normal file
53
contracts/minter/messages/query.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import type { MinterInstance } from '../contract'
|
||||||
|
|
||||||
|
export type QueryType = typeof QUERY_TYPES[number]
|
||||||
|
|
||||||
|
export const QUERY_TYPES = ['config', 'mintable_num_tokens', 'start_time', 'mint_price', 'mint_count'] as const
|
||||||
|
|
||||||
|
export interface QueryListItem {
|
||||||
|
id: QueryType
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QUERY_LIST: QueryListItem[] = [
|
||||||
|
{ id: 'config', name: 'Config', description: 'View current config' },
|
||||||
|
{ id: 'mintable_num_tokens', name: 'Total Mintable Tokens', description: 'View the total amount of mintable tokens' },
|
||||||
|
{ id: 'start_time', name: 'Start Time', description: 'View the start time for minting' },
|
||||||
|
{ id: 'mint_price', name: 'Mint Price', description: 'View the mint price' },
|
||||||
|
{
|
||||||
|
id: 'mint_count',
|
||||||
|
name: 'Total Minted Count',
|
||||||
|
description: 'View the total amount of minted tokens for an address',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface DispatchQueryProps {
|
||||||
|
address: string
|
||||||
|
messages: MinterInstance | undefined
|
||||||
|
type: QueryType
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||||
|
const { address, messages, type } = props
|
||||||
|
switch (type) {
|
||||||
|
case 'config': {
|
||||||
|
return messages?.getConfig()
|
||||||
|
}
|
||||||
|
case 'mintable_num_tokens': {
|
||||||
|
return messages?.getMintableNumTokens()
|
||||||
|
}
|
||||||
|
case 'start_time': {
|
||||||
|
return messages?.getStartTime()
|
||||||
|
}
|
||||||
|
case 'mint_price': {
|
||||||
|
return messages?.getMintPrice()
|
||||||
|
}
|
||||||
|
case 'mint_count': {
|
||||||
|
return messages?.getMintCount(address)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error('unknown query type')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,10 @@
|
|||||||
import { Coin } from '@cosmjs/proto-signing'
|
import type { Coin } from '@cosmjs/proto-signing'
|
||||||
import { logs } from '@cosmjs/stargate'
|
import type { logs } from '@cosmjs/stargate'
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import {
|
import type { MinterContract, MinterInstance, MinterMessages } from './contract'
|
||||||
minter as initContract,
|
import { minter as initContract } from './contract'
|
||||||
MinterContract,
|
|
||||||
MinterInstance,
|
|
||||||
} from './contract'
|
|
||||||
|
|
||||||
/*export interface InstantiateResponse {
|
/*export interface InstantiateResponse {
|
||||||
/** The address of the newly instantiated contract *-/
|
/** The address of the newly instantiated contract *-/
|
||||||
@ -33,11 +30,12 @@ export interface UseMinterContractProps {
|
|||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
admin?: string,
|
admin?: string,
|
||||||
funds?: Coin[]
|
funds?: Coin[],
|
||||||
) => Promise<InstantiateResponse>
|
) => Promise<InstantiateResponse>
|
||||||
use: (customAddress: string) => MinterInstance | undefined
|
use: (customAddress: string) => MinterInstance | undefined
|
||||||
updateContractAddress: (contractAddress: string) => void
|
updateContractAddress: (contractAddress: string) => void
|
||||||
getContractAddress: () => string | undefined
|
getContractAddress: () => string | undefined
|
||||||
|
messages: () => MinterMessages | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMinterContract(): UseMinterContractProps {
|
export function useMinterContract(): UseMinterContractProps {
|
||||||
@ -52,13 +50,9 @@ export function useMinterContract(): UseMinterContractProps {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (wallet.initialized) {
|
if (wallet.initialized) {
|
||||||
const getMinterBaseInstance = async (): Promise<void> => {
|
const MinterBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||||
const MinterBaseContract = initContract(wallet.getClient())
|
|
||||||
setMinter(MinterBaseContract)
|
setMinter(MinterBaseContract)
|
||||||
}
|
}
|
||||||
|
|
||||||
getMinterBaseInstance()
|
|
||||||
}
|
|
||||||
}, [wallet])
|
}, [wallet])
|
||||||
|
|
||||||
const updateContractAddress = (contractAddress: string) => {
|
const updateContractAddress = (contractAddress: string) => {
|
||||||
@ -66,33 +60,38 @@ export function useMinterContract(): UseMinterContractProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const instantiate = useCallback(
|
const instantiate = useCallback(
|
||||||
(codeId, initMsg, label, admin?, funds?): Promise<InstantiateResponse> => {
|
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!minter) return reject('Contract is not initialized.')
|
if (!minter) {
|
||||||
minter
|
reject(new Error('Contract is not initialized.'))
|
||||||
.instantiate(wallet.address, codeId, initMsg, label, admin, funds)
|
return
|
||||||
.then(resolve)
|
}
|
||||||
.catch(reject)
|
minter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[minter, wallet]
|
[minter, wallet],
|
||||||
)
|
)
|
||||||
|
|
||||||
const use = useCallback(
|
const use = useCallback(
|
||||||
(customAddress = ''): MinterInstance | undefined => {
|
(customAddress = ''): MinterInstance | undefined => {
|
||||||
return minter?.use(address || customAddress)
|
return minter?.use(address || customAddress)
|
||||||
},
|
},
|
||||||
[minter, address]
|
[minter, address],
|
||||||
)
|
)
|
||||||
|
|
||||||
const getContractAddress = (): string | undefined => {
|
const getContractAddress = (): string | undefined => {
|
||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messages = useCallback((): MinterMessages | undefined => {
|
||||||
|
return minter?.messages()
|
||||||
|
}, [minter])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instantiate,
|
instantiate,
|
||||||
use,
|
use,
|
||||||
updateContractAddress,
|
updateContractAddress,
|
||||||
getContractAddress,
|
getContractAddress,
|
||||||
|
messages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,111 +1,181 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
import { Coin } from '@cosmjs/stargate'
|
import { toBase64, toUtf8 } from '@cosmjs/encoding'
|
||||||
|
import type { Coin } from '@cosmjs/stargate'
|
||||||
|
import { coin } from '@cosmjs/stargate'
|
||||||
|
|
||||||
export interface InstantiateResponse {
|
export interface InstantiateResponse {
|
||||||
readonly contractAddress: string
|
readonly contractAddress: string
|
||||||
readonly transactionHash: string
|
readonly transactionHash: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Expiration =
|
export type Expiration = { at_height: number } | { at_time: string } | { never: Record<string, never> }
|
||||||
| { at_height: number }
|
|
||||||
| { at_time: string }
|
|
||||||
| { never: {} }
|
|
||||||
|
|
||||||
export interface SG721Instance {
|
export interface SG721Instance {
|
||||||
readonly contractAddress: string
|
readonly contractAddress: string
|
||||||
|
|
||||||
// queries
|
// queries
|
||||||
getOwnerOf: (
|
ownerOf: (tokenId: string, includeExpired?: boolean | null) => Promise<any>
|
||||||
token_id: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
getApproval: (
|
approval: (tokenId: string, spender: string, includeExpired?: boolean | null) => Promise<any>
|
||||||
token_id: string,
|
|
||||||
spender: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
getApprovals: (
|
approvals: (tokenId: string, includeExpired?: boolean | null) => Promise<any>
|
||||||
token_id: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
getAllOperators: (
|
allOperators: (
|
||||||
owner: string,
|
owner: string,
|
||||||
include_expired: boolean | null,
|
includeExpired?: boolean | null,
|
||||||
start_after: string | null,
|
startAfter?: string | null,
|
||||||
limit: number | null
|
limit?: number | null,
|
||||||
) => Promise<any>
|
) => Promise<any>
|
||||||
|
|
||||||
getNumTokens: () => Promise<any>
|
numTokens: () => Promise<any>
|
||||||
|
|
||||||
getContractInfo: () => Promise<any>
|
contractInfo: () => Promise<any>
|
||||||
|
|
||||||
getNftInfo: (token_id: string) => Promise<any>
|
nftInfo: (tokenId: string) => Promise<any>
|
||||||
|
|
||||||
getAllNftInfo: (
|
allNftInfo: (tokenId: string, includeExpired?: boolean | null) => Promise<any>
|
||||||
token_id: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
getTokens: (
|
tokens: (owner: string, startAfter?: string | null, limit?: number | null) => Promise<any>
|
||||||
owner: string,
|
|
||||||
start_after: string | null,
|
|
||||||
limit: number | null
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
getAllTokens: (
|
allTokens: (startAfter?: string | null, limit?: number | null) => Promise<any>
|
||||||
start_after: string | null,
|
|
||||||
limit: number | null
|
|
||||||
) => Promise<any>
|
|
||||||
|
|
||||||
getMinter: () => Promise<any>
|
minter: () => Promise<any>
|
||||||
|
|
||||||
getCollectionInfo: () => Promise<any>
|
collectionInfo: () => Promise<any>
|
||||||
|
|
||||||
//Execute
|
//Execute
|
||||||
transferNft: (
|
transferNft: (recipient: string, tokenId: string) => Promise<string>
|
||||||
senderAddress: string,
|
|
||||||
recipient: string,
|
|
||||||
token_id: string
|
|
||||||
) => Promise<string>
|
|
||||||
/// Send is a base message to transfer a token to a contract and trigger an action
|
/// Send is a base message to transfer a token to a contract and trigger an action
|
||||||
/// on the receiving contract.
|
/// on the receiving contract.
|
||||||
sendNft: (
|
sendNft: (
|
||||||
senderAddress: string,
|
|
||||||
contract: string,
|
contract: string,
|
||||||
token_id: string,
|
tokenId: string,
|
||||||
msg: string //Binary
|
msg: Record<string, unknown>, //Binary
|
||||||
) => Promise<string>
|
) => Promise<string>
|
||||||
/// Allows operator to transfer / send the token from the owner's account.
|
/// Allows operator to transfer / send the token from the owner's account.
|
||||||
/// If expiration is set, then this allowance has a time/height limit
|
/// If expiration is set, then this allowance has a time/height limit
|
||||||
approve: (
|
approve: (spender: string, tokenId: string, expires?: Expiration) => Promise<string>
|
||||||
senderAddress: string,
|
|
||||||
spender: string,
|
|
||||||
token_id: string,
|
|
||||||
expires: Expiration | null
|
|
||||||
) => Promise<string>
|
|
||||||
/// Remove previously granted Approval
|
/// Remove previously granted Approval
|
||||||
revoke: (
|
revoke: (spender: string, tokenId: string) => Promise<string>
|
||||||
senderAddress: string,
|
|
||||||
spender: string,
|
|
||||||
token_id: string
|
|
||||||
) => Promise<string>
|
|
||||||
/// Allows operator to transfer / send any token from the owner's account.
|
/// Allows operator to transfer / send any token from the owner's account.
|
||||||
/// If expiration is set, then this allowance has a time/height limit
|
/// If expiration is set, then this allowance has a time/height limit
|
||||||
approveAll: (
|
approveAll: (operator: string, expires?: Expiration) => Promise<string>
|
||||||
senderAddress: string,
|
|
||||||
operator: string,
|
|
||||||
expires: Expiration | null
|
|
||||||
) => Promise<string>
|
|
||||||
/// Remove previously granted ApproveAll permission
|
/// Remove previously granted ApproveAll permission
|
||||||
revokeAll: (senderAddress: string, operator: string) => Promise<string>
|
revokeAll: (operator: string) => Promise<string>
|
||||||
/// Mint a new NFT, can only be called by the contract minter
|
/// Mint a new NFT, can only be called by the contract minter
|
||||||
mint: (senderAddress: string, msg: string) => Promise<string> //MintMsg<T>
|
mint: (tokenId: string, owner: string, tokenURI?: string) => Promise<string> //MintMsg<T>
|
||||||
|
|
||||||
/// Burn an NFT the sender has access to
|
/// Burn an NFT the sender has access to
|
||||||
burn: (senderAddress: string, token_id: string) => Promise<string>
|
burn: (tokenId: string) => Promise<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Sg721Messages {
|
||||||
|
transferNft: (recipient: string, tokenId: string) => TransferNFTMessage
|
||||||
|
sendNft: (contract: string, tokenId: string, msg: Record<string, unknown>) => SendNFTMessage
|
||||||
|
approve: (recipient: string, tokenId: string, expires?: Expiration) => ApproveMessage
|
||||||
|
revoke: (recipient: string, tokenId: string) => RevokeMessage
|
||||||
|
approveAll: (operator: string, expires?: Expiration) => ApproveAllMessage
|
||||||
|
revokeAll: (operator: string) => RevokeAllMessage
|
||||||
|
mint: (tokenId: string, owner: string, tokenURI?: string) => MintMessage
|
||||||
|
burn: (tokenId: string) => BurnMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransferNFTMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
transfer_nft: {
|
||||||
|
recipient: string
|
||||||
|
token_id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendNFTMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
send_nft: {
|
||||||
|
contract: string
|
||||||
|
token_id: string
|
||||||
|
msg: Record<string, unknown>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApproveMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
approve: {
|
||||||
|
spender: string
|
||||||
|
token_id: string
|
||||||
|
expires?: Expiration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RevokeMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
|
||||||
|
msg: {
|
||||||
|
revoke: {
|
||||||
|
spender: string
|
||||||
|
token_id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApproveAllMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
approve_all: {
|
||||||
|
operator: string
|
||||||
|
expires?: Expiration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RevokeAllMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
revoke_all: {
|
||||||
|
operator: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MintMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
mint: {
|
||||||
|
token_id: string
|
||||||
|
owner: string
|
||||||
|
token_uri?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BurnMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
burn: {
|
||||||
|
token_id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SG721Contract {
|
export interface SG721Contract {
|
||||||
@ -114,121 +184,103 @@ export interface SG721Contract {
|
|||||||
codeId: number,
|
codeId: number,
|
||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
funds: Coin[],
|
admin?: string,
|
||||||
admin?: string
|
|
||||||
) => Promise<InstantiateResponse>
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
use: (contractAddress: string) => SG721Instance
|
use: (contractAddress: string) => SG721Instance
|
||||||
|
|
||||||
|
messages: (contractAddress: string) => Sg721Messages
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SG721 = (client: SigningCosmWasmClient): SG721Contract => {
|
export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Contract => {
|
||||||
const use = (contractAddress: string): SG721Instance => {
|
const use = (contractAddress: string): SG721Instance => {
|
||||||
const encode = (str: string): string =>
|
const jsonToBinary = (json: Record<string, unknown>): string => {
|
||||||
Buffer.from(str, 'binary').toString('base64')
|
return toBase64(toUtf8(JSON.stringify(json)))
|
||||||
|
}
|
||||||
|
|
||||||
const getOwnerOf = async (
|
const ownerOf = async (tokenId: string, includeExpired?: boolean | null): Promise<any> => {
|
||||||
token_id: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
): Promise<any> => {
|
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
owner_of: { token_id, include_expired },
|
owner_of: { token_id: tokenId, include_expired: includeExpired },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getApproval = async (
|
const approval = async (tokenId: string, spender: string, includeExpired?: boolean | null): Promise<any> => {
|
||||||
token_id: string,
|
|
||||||
spender: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
): Promise<any> => {
|
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
approval: { token_id, spender, include_expired },
|
approval: { token_id: tokenId, spender, include_expired: includeExpired },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getApprovals = async (
|
const approvals = async (tokenId: string, includeExpired?: boolean | null): Promise<any> => {
|
||||||
token_id: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
): Promise<any> => {
|
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
approvals: { token_id, include_expired },
|
approvals: { token_id: tokenId, include_expired: includeExpired },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllOperators = async (
|
const allOperators = async (
|
||||||
owner: string,
|
owner: string,
|
||||||
include_expired: boolean | null,
|
includeExpired?: boolean | null,
|
||||||
start_after: string | null,
|
startAfter?: string | null,
|
||||||
limit: number | null
|
limit?: number | null,
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
all_operators: { owner, include_expired, start_after, limit },
|
all_operators: { owner, include_expired: includeExpired, start_after: startAfter, limit },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNumTokens = async (): Promise<any> => {
|
const numTokens = async (): Promise<any> => {
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
num_tokens: {},
|
num_tokens: {},
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getContractInfo = async (): Promise<any> => {
|
const contractInfo = async (): Promise<any> => {
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
contract_info: {},
|
contract_info: {},
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNftInfo = async (token_id: string): Promise<any> => {
|
const nftInfo = async (tokenId: string): Promise<any> => {
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
nft_info: { token_id },
|
nft_info: { token_id: tokenId },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllNftInfo = async (
|
const allNftInfo = async (tokenId: string, includeExpired?: boolean | null): Promise<any> => {
|
||||||
token_id: string,
|
|
||||||
include_expired: boolean | null
|
|
||||||
): Promise<any> => {
|
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
all_nft_info: { token_id, include_expired },
|
all_nft_info: { token_id: tokenId, include_expired: includeExpired },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTokens = async (
|
const tokens = async (owner: string, startAfter?: string | null, limit?: number | null): Promise<any> => {
|
||||||
owner: string,
|
|
||||||
start_after: string | null,
|
|
||||||
limit: number | null
|
|
||||||
): Promise<any> => {
|
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
tokens: { owner, start_after, limit },
|
tokens: { owner, start_after: startAfter, limit },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllTokens = async (
|
const allTokens = async (startAfter?: string | null, limit?: number | null): Promise<any> => {
|
||||||
start_after: string | null,
|
|
||||||
limit: number | null
|
|
||||||
): Promise<any> => {
|
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
all_tokens: { start_after, limit },
|
all_tokens: { start_after: startAfter, limit },
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMinter = async (): Promise<any> => {
|
const minter = async (): Promise<any> => {
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
minter: {},
|
minter: {},
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCollectionInfo = async (): Promise<any> => {
|
const collectionInfo = async (): Promise<any> => {
|
||||||
const res = await client.queryContractSmart(contractAddress, {
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
collection_info: {},
|
collection_info: {},
|
||||||
})
|
})
|
||||||
@ -236,144 +288,121 @@ export const SG721 = (client: SigningCosmWasmClient): SG721Contract => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Execute
|
//Execute
|
||||||
const transferNft = async (
|
const transferNft = async (recipient: string, tokenId: string): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
recipient: string,
|
|
||||||
token_id: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
transfer_nft: { recipient, token_id },
|
transfer_nft: { recipient, token_id: tokenId },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendNft = async (
|
const sendNft = async (
|
||||||
senderAddress: string,
|
|
||||||
contract: string,
|
contract: string,
|
||||||
token_id: string,
|
tokenId: string,
|
||||||
msg: string //Binary
|
msg: Record<string, unknown>, //Binary
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
send_nft: { contract, token_id, msg: encode(msg) },
|
send_nft: { contract, token_id: tokenId, msg: jsonToBinary(msg) },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const approve = async (
|
const approve = async (spender: string, tokenId: string, expires?: Expiration): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
spender: string,
|
|
||||||
token_id: string,
|
|
||||||
expires: Expiration | null
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
approve: { spender, token_id, expires },
|
approve: { spender, token_id: tokenId, expires },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const revoke = async (
|
const revoke = async (spender: string, tokenId: string): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
spender: string,
|
|
||||||
token_id: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
revoke: { spender, token_id },
|
revoke: { spender, token_id: tokenId },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const approveAll = async (
|
const approveAll = async (operator: string, expires?: Expiration): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
operator: string,
|
|
||||||
expires: Expiration | null
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
approve_all: { operator, expires },
|
approve_all: { operator, expires },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const revokeAll = async (
|
const revokeAll = async (operator: string): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
operator: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
revoke_all: { operator },
|
revoke_all: { operator },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const mint = async (
|
const mint = async (tokenId: string, owner: string, tokenURI?: string): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
msg: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
mint: { msg },
|
mint: {
|
||||||
|
token_id: tokenId,
|
||||||
|
owner,
|
||||||
|
token_uri: tokenURI,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const burn = async (
|
const burn = async (tokenId: string): Promise<string> => {
|
||||||
senderAddress: string,
|
|
||||||
token_id: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{
|
{
|
||||||
burn: { token_id },
|
burn: { token_id: tokenId },
|
||||||
},
|
},
|
||||||
'auto',
|
'auto',
|
||||||
''
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
@ -381,18 +410,18 @@ export const SG721 = (client: SigningCosmWasmClient): SG721Contract => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
contractAddress,
|
contractAddress,
|
||||||
getOwnerOf,
|
ownerOf,
|
||||||
getApproval,
|
approval,
|
||||||
getApprovals,
|
approvals,
|
||||||
getAllOperators,
|
allOperators,
|
||||||
getNumTokens,
|
numTokens,
|
||||||
getContractInfo,
|
contractInfo,
|
||||||
getNftInfo,
|
nftInfo,
|
||||||
getAllNftInfo,
|
allNftInfo,
|
||||||
getTokens,
|
tokens,
|
||||||
getAllTokens,
|
allTokens,
|
||||||
getMinter,
|
minter,
|
||||||
getCollectionInfo,
|
collectionInfo,
|
||||||
transferNft,
|
transferNft,
|
||||||
sendNft,
|
sendNft,
|
||||||
approve,
|
approve,
|
||||||
@ -409,26 +438,144 @@ export const SG721 = (client: SigningCosmWasmClient): SG721Contract => {
|
|||||||
codeId: number,
|
codeId: number,
|
||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
funds: Coin[],
|
admin?: string,
|
||||||
admin?: string
|
|
||||||
): Promise<InstantiateResponse> => {
|
): Promise<InstantiateResponse> => {
|
||||||
const result = await client.instantiate(
|
const result = await client.instantiate(senderAddress, codeId, initMsg, label, 'auto', {
|
||||||
senderAddress,
|
funds: [coin('1000000000', 'ustars')],
|
||||||
codeId,
|
|
||||||
initMsg,
|
|
||||||
label,
|
|
||||||
'auto',
|
|
||||||
{
|
|
||||||
funds,
|
|
||||||
memo: '',
|
memo: '',
|
||||||
admin,
|
admin,
|
||||||
}
|
})
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
contractAddress: result.contractAddress,
|
contractAddress: result.contractAddress,
|
||||||
transactionHash: result.transactionHash,
|
transactionHash: result.transactionHash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { use, instantiate }
|
const messages = (contractAddress: string) => {
|
||||||
|
const transferNft = (recipient: string, tokenId: string) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
transfer_nft: {
|
||||||
|
recipient,
|
||||||
|
token_id: tokenId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendNft = (contract: string, tokenId: string, msg: Record<string, unknown>) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
send_nft: {
|
||||||
|
contract,
|
||||||
|
token_id: tokenId,
|
||||||
|
msg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const approve = (spender: string, tokenId: string, expires?: Expiration) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
approve: {
|
||||||
|
spender,
|
||||||
|
token_id: tokenId,
|
||||||
|
expires,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const revoke = (spender: string, tokenId: string) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
revoke: {
|
||||||
|
spender,
|
||||||
|
token_id: tokenId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const approveAll = (operator: string, expires?: Expiration) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
approve_all: {
|
||||||
|
operator,
|
||||||
|
expires,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const revokeAll = (operator: string) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
revoke_all: {
|
||||||
|
operator,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mint = (tokenId: string, owner: string, tokenURI?: string) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
mint: {
|
||||||
|
token_id: tokenId,
|
||||||
|
owner,
|
||||||
|
token_uri: tokenURI,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const burn = (tokenId: string) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
burn: {
|
||||||
|
token_id: tokenId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
transferNft,
|
||||||
|
sendNft,
|
||||||
|
approve,
|
||||||
|
revoke,
|
||||||
|
approveAll,
|
||||||
|
revokeAll,
|
||||||
|
mint,
|
||||||
|
burn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { use, instantiate, messages }
|
||||||
}
|
}
|
||||||
|
162
contracts/sg721/messages/execute.ts
Normal file
162
contracts/sg721/messages/execute.ts
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import type { Expiration, SG721Instance } from '../index'
|
||||||
|
import { useSG721Contract } from '../index'
|
||||||
|
|
||||||
|
export type ExecuteType = typeof EXECUTE_TYPES[number]
|
||||||
|
|
||||||
|
export const EXECUTE_TYPES = [
|
||||||
|
'transfer_nft',
|
||||||
|
'send_nft',
|
||||||
|
'approve',
|
||||||
|
'revoke',
|
||||||
|
'approve_all',
|
||||||
|
'revoke_all',
|
||||||
|
'mint',
|
||||||
|
'burn',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export interface ExecuteListItem {
|
||||||
|
id: ExecuteType
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||||
|
{
|
||||||
|
id: 'transfer_nft',
|
||||||
|
name: 'Transfer NFT',
|
||||||
|
description: `Transfer a token to an address`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'send_nft',
|
||||||
|
name: 'Send NFT',
|
||||||
|
description: `Send a token to a contract and execute a message afterwards`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'approve',
|
||||||
|
name: 'Approve',
|
||||||
|
description: `Allow an operator to transfer/send a given token from the owner's account`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'revoke',
|
||||||
|
name: 'Revoke',
|
||||||
|
description: `Remove permissions of an operator from the owner's account`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'approve_all',
|
||||||
|
name: 'Approve All',
|
||||||
|
description: `Allow an operator to transfer/send all tokens from owner's account`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'revoke_all',
|
||||||
|
name: 'Revoke All',
|
||||||
|
description: `Remove permissions of an operator from the owner's account`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mint',
|
||||||
|
name: 'Mint',
|
||||||
|
description: `Mint a new token to owner's account`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'burn',
|
||||||
|
name: 'Burn',
|
||||||
|
description: `Burn a token transaction sender has access to`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface DispatchExecuteProps {
|
||||||
|
type: ExecuteType
|
||||||
|
[k: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type Select<T extends ExecuteType> = T
|
||||||
|
|
||||||
|
/** @see {@link SG721Instance} */
|
||||||
|
export type DispatchExecuteArgs = {
|
||||||
|
contract: string
|
||||||
|
messages?: SG721Instance
|
||||||
|
} & (
|
||||||
|
| { type: undefined }
|
||||||
|
| { type: Select<'transfer_nft'>; recipient: string; tokenId: string }
|
||||||
|
| { type: Select<'send_nft'>; recipient: string; tokenId: string; msg: Record<string, unknown> }
|
||||||
|
| { type: Select<'approve'>; recipient: string; tokenId: string; expiration?: Expiration }
|
||||||
|
| { type: Select<'revoke'>; recipient: string; tokenId: string }
|
||||||
|
| { type: Select<'approve_all'>; operator: string; expiration?: Expiration }
|
||||||
|
| { type: Select<'revoke_all'>; operator: string }
|
||||||
|
| { type: Select<'mint'>; recipient: string; tokenId: string; tokenURI?: string }
|
||||||
|
| { type: Select<'burn'>; tokenId: string }
|
||||||
|
)
|
||||||
|
|
||||||
|
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||||
|
const { messages } = args
|
||||||
|
if (!messages) {
|
||||||
|
throw new Error('cannot dispatch execute, messages is not defined')
|
||||||
|
}
|
||||||
|
switch (args.type) {
|
||||||
|
case 'transfer_nft': {
|
||||||
|
return messages.transferNft(args.recipient, args.tokenId)
|
||||||
|
}
|
||||||
|
case 'send_nft': {
|
||||||
|
return messages.sendNft(args.recipient, args.tokenId, args.msg)
|
||||||
|
}
|
||||||
|
case 'approve': {
|
||||||
|
return messages.approve(args.recipient, args.tokenId, args.expiration)
|
||||||
|
}
|
||||||
|
case 'revoke': {
|
||||||
|
return messages.revoke(args.recipient, args.tokenId)
|
||||||
|
}
|
||||||
|
case 'approve_all': {
|
||||||
|
return messages.approveAll(args.operator, args.expiration)
|
||||||
|
}
|
||||||
|
case 'revoke_all': {
|
||||||
|
return messages.revokeAll(args.operator)
|
||||||
|
}
|
||||||
|
case 'mint': {
|
||||||
|
return messages.mint(args.recipient, args.tokenId, args.tokenURI)
|
||||||
|
}
|
||||||
|
case 'burn': {
|
||||||
|
return messages.burn(args.tokenId)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error('unknown execute type')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const { messages } = useSG721Contract()
|
||||||
|
const { contract } = args
|
||||||
|
switch (args.type) {
|
||||||
|
case 'transfer_nft': {
|
||||||
|
return messages(contract)?.transferNft(args.recipient, args.tokenId)
|
||||||
|
}
|
||||||
|
case 'send_nft': {
|
||||||
|
return messages(contract)?.sendNft(args.recipient, args.tokenId, args.msg)
|
||||||
|
}
|
||||||
|
case 'approve': {
|
||||||
|
return messages(contract)?.approve(args.recipient, args.tokenId, args.expiration)
|
||||||
|
}
|
||||||
|
case 'revoke': {
|
||||||
|
return messages(contract)?.revoke(args.recipient, args.tokenId)
|
||||||
|
}
|
||||||
|
case 'approve_all': {
|
||||||
|
return messages(contract)?.approveAll(args.operator, args.expiration)
|
||||||
|
}
|
||||||
|
case 'revoke_all': {
|
||||||
|
return messages(contract)?.revokeAll(args.operator)
|
||||||
|
}
|
||||||
|
case 'mint': {
|
||||||
|
return messages(contract)?.mint(args.recipient, args.tokenId, args.tokenURI)
|
||||||
|
}
|
||||||
|
case 'burn': {
|
||||||
|
return messages(contract)?.burn(args.tokenId)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
|
||||||
|
return arr.some((val) => type === val)
|
||||||
|
}
|
95
contracts/sg721/messages/query.ts
Normal file
95
contracts/sg721/messages/query.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import type { SG721Instance } from '../contract'
|
||||||
|
|
||||||
|
export type QueryType = typeof QUERY_TYPES[number]
|
||||||
|
|
||||||
|
export const QUERY_TYPES = [
|
||||||
|
'owner_of',
|
||||||
|
'approval',
|
||||||
|
'approvals',
|
||||||
|
'all_operators',
|
||||||
|
'num_tokens',
|
||||||
|
'contract_info',
|
||||||
|
'nft_info',
|
||||||
|
'all_nft_info',
|
||||||
|
'tokens',
|
||||||
|
'all_tokens',
|
||||||
|
'minter',
|
||||||
|
'collection_info',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export interface QueryListItem {
|
||||||
|
id: QueryType
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QUERY_LIST: QueryListItem[] = [
|
||||||
|
{ id: 'owner_of', name: 'Owner Of', description: 'View current owner of given token' },
|
||||||
|
{ id: 'approval', name: 'Approval', description: 'View address that has access to given token' },
|
||||||
|
{ id: 'approvals', name: 'Approvals', description: 'View all approvals of a given token' },
|
||||||
|
{
|
||||||
|
id: 'all_operators',
|
||||||
|
name: 'All Operators',
|
||||||
|
description: "List all the operators that has access all of the owner's tokens",
|
||||||
|
},
|
||||||
|
{ id: 'num_tokens', name: 'Number of Tokens', description: 'View total number of tokens minted' },
|
||||||
|
{ id: 'contract_info', name: 'Contract Info', description: 'View top-level metadata of contract' },
|
||||||
|
{ id: 'nft_info', name: 'NFT Info', description: 'View metadata of a given token' },
|
||||||
|
{ id: 'all_nft_info', name: 'All NFT Info', description: 'View metadata and owner info of a given token' },
|
||||||
|
{ id: 'tokens', name: 'Tokens', description: 'View all the tokens owned by given address' },
|
||||||
|
{ id: 'all_tokens', name: 'All Tokens', description: 'List all the tokens controlled by the contract' },
|
||||||
|
{ id: 'minter', name: 'Minter', description: 'View current minter of the contract' },
|
||||||
|
{ id: 'collection_info', name: 'Collection Info', description: 'View metadata of a given collection' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface DispatchQueryProps {
|
||||||
|
messages: SG721Instance | undefined
|
||||||
|
type: QueryType
|
||||||
|
tokenId: string
|
||||||
|
address: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||||
|
const { tokenId, messages, type, address } = props
|
||||||
|
switch (type) {
|
||||||
|
case 'owner_of': {
|
||||||
|
return messages?.ownerOf(tokenId)
|
||||||
|
}
|
||||||
|
case 'approval': {
|
||||||
|
return messages?.approval(tokenId, address)
|
||||||
|
}
|
||||||
|
case 'approvals': {
|
||||||
|
return messages?.approvals(tokenId)
|
||||||
|
}
|
||||||
|
case 'all_operators': {
|
||||||
|
return messages?.allOperators(address)
|
||||||
|
}
|
||||||
|
case 'num_tokens': {
|
||||||
|
return messages?.numTokens()
|
||||||
|
}
|
||||||
|
case 'contract_info': {
|
||||||
|
return messages?.contractInfo()
|
||||||
|
}
|
||||||
|
case 'nft_info': {
|
||||||
|
return messages?.nftInfo(tokenId)
|
||||||
|
}
|
||||||
|
case 'all_nft_info': {
|
||||||
|
return messages?.allNftInfo(tokenId, null)
|
||||||
|
}
|
||||||
|
case 'tokens': {
|
||||||
|
return messages?.tokens(address)
|
||||||
|
}
|
||||||
|
case 'all_tokens': {
|
||||||
|
return messages?.allTokens()
|
||||||
|
}
|
||||||
|
case 'minter': {
|
||||||
|
return messages?.minter()
|
||||||
|
}
|
||||||
|
case 'collection_info': {
|
||||||
|
return messages?.collectionInfo()
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error('unknown query type')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
|
import type { Coin } from '@cosmjs/proto-signing'
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
import { Coin } from 'cosmwasm'
|
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { SG721 as initContract, SG721Contract, SG721Instance } from './contract'
|
import type { SG721Contract, SG721Instance, Sg721Messages } from './contract'
|
||||||
|
import { SG721 as initContract } from './contract'
|
||||||
|
|
||||||
interface InstantiateResponse {
|
interface InstantiateResponse {
|
||||||
readonly contractAddress: string
|
readonly contractAddress: string
|
||||||
@ -14,11 +15,12 @@ export interface UseSG721ContractProps {
|
|||||||
codeId: number,
|
codeId: number,
|
||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
funds: Coin[],
|
admin?: string,
|
||||||
admin?: string
|
funds?: Coin[],
|
||||||
) => Promise<InstantiateResponse>
|
) => Promise<InstantiateResponse>
|
||||||
use: (customAddress: string) => SG721Instance | undefined
|
use: (customAddress: string) => SG721Instance | undefined
|
||||||
updateContractAddress: (contractAddress: string) => void
|
updateContractAddress: (contractAddress: string) => void
|
||||||
|
messages: (contractAddress: string) => Sg721Messages | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSG721Contract(): UseSG721ContractProps {
|
export function useSG721Contract(): UseSG721ContractProps {
|
||||||
@ -33,12 +35,8 @@ export function useSG721Contract(): UseSG721ContractProps {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (wallet.initialized) {
|
if (wallet.initialized) {
|
||||||
const getSG721Instance = async (): Promise<void> => {
|
const contract = initContract(wallet.getClient(), wallet.address)
|
||||||
const SG721Contract = initContract(wallet.getClient())
|
setSG721(contract)
|
||||||
setSG721(SG721Contract)
|
|
||||||
}
|
|
||||||
|
|
||||||
getSG721Instance()
|
|
||||||
}
|
}
|
||||||
}, [wallet])
|
}, [wallet])
|
||||||
|
|
||||||
@ -47,27 +45,33 @@ export function useSG721Contract(): UseSG721ContractProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const instantiate = useCallback(
|
const instantiate = useCallback(
|
||||||
(codeId, initMsg, label, admin?): Promise<InstantiateResponse> => {
|
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!SG721) return reject('Contract is not initialized.')
|
if (!SG721) {
|
||||||
SG721.instantiate(wallet.address, codeId, initMsg, label, admin)
|
reject(new Error('Contract is not initialized.'))
|
||||||
.then(resolve)
|
return
|
||||||
.catch(reject)
|
}
|
||||||
|
SG721.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[SG721, wallet]
|
[SG721, wallet],
|
||||||
)
|
)
|
||||||
|
|
||||||
const use = useCallback(
|
const use = useCallback(
|
||||||
(customAddress = ''): SG721Instance | undefined => {
|
(customAddress = ''): SG721Instance | undefined => {
|
||||||
return SG721?.use(address || customAddress)
|
return SG721?.use(address || customAddress)
|
||||||
},
|
},
|
||||||
[SG721, address]
|
[SG721, address],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const messages = useCallback((): Sg721Messages | undefined => {
|
||||||
|
return SG721?.messages(address)
|
||||||
|
}, [SG721, address])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instantiate,
|
instantiate,
|
||||||
use,
|
use,
|
||||||
updateContractAddress,
|
updateContractAddress,
|
||||||
|
messages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
import { Coin } from '@cosmjs/proto-signing'
|
import type { Coin } from '@cosmjs/proto-signing'
|
||||||
|
import { coin } from '@cosmjs/proto-signing'
|
||||||
type Expiration = { at_height: number } | { at_time: string } | { never: {} }
|
|
||||||
|
|
||||||
export interface InstantiateResponse {
|
export interface InstantiateResponse {
|
||||||
readonly contractAddress: string
|
readonly contractAddress: string
|
||||||
@ -23,39 +22,98 @@ export interface WhiteListInstance {
|
|||||||
hasStarted: () => Promise<boolean>
|
hasStarted: () => Promise<boolean>
|
||||||
hasEnded: () => Promise<boolean>
|
hasEnded: () => Promise<boolean>
|
||||||
isActive: () => Promise<boolean>
|
isActive: () => Promise<boolean>
|
||||||
members: (limit: number, startAfter?: string) => Promise<string[]>
|
members: (startAfter?: string, limit?: number) => Promise<string[]>
|
||||||
hasMember: (member: string) => Promise<boolean>
|
hasMember: (member: string) => Promise<boolean>
|
||||||
config: () => Promise<ConfigResponse>
|
config: () => Promise<ConfigResponse>
|
||||||
|
|
||||||
//Execute
|
//Execute
|
||||||
updateStartTime: (startTime: string) => Promise<string>
|
updateStartTime: (startTime: string) => Promise<string>
|
||||||
updateEndTime: (endTime: string) => Promise<string>
|
updateEndTime: (endTime: string) => Promise<string>
|
||||||
addMembers: (to_add: string[]) => Promise<string>
|
addMembers: (memberList: string[]) => Promise<string>
|
||||||
removeMembers: (to_remove: string[]) => Promise<string>
|
removeMembers: (memberList: string[]) => Promise<string>
|
||||||
updatePerAddressLimit: (limit: number) => Promise<string>
|
updatePerAddressLimit: (limit: number) => Promise<string>
|
||||||
increaseMemberLimit: (limit: number) => Promise<string>
|
increaseMemberLimit: (limit: number) => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WhitelistMessages {
|
||||||
|
updateStartTime: (startTime: string) => UpdateStartTimeMessage
|
||||||
|
updateEndTime: (endTime: string) => UpdateEndTimeMessage
|
||||||
|
addMembers: (memberList: string[]) => AddMembersMessage
|
||||||
|
removeMembers: (memberList: string[]) => RemoveMembersMessage
|
||||||
|
updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
|
||||||
|
increaseMemberLimit: (limit: number) => IncreaseMemberLimitMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateStartTimeMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
update_start_time: string
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateEndTimeMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
update_end_time: string
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddMembersMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
add_members: { to_add: string[] }
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveMembersMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
remove_members: { to_remove: string[] }
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePerAddressLimitMessage {
|
||||||
|
sender: string
|
||||||
|
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
update_per_address_limit: number
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IncreaseMemberLimitMessage {
|
||||||
|
sender: string
|
||||||
|
contract: string
|
||||||
|
msg: {
|
||||||
|
increase_member_limit: number
|
||||||
|
}
|
||||||
|
funds: Coin[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface WhiteListContract {
|
export interface WhiteListContract {
|
||||||
instantiate: (
|
instantiate: (
|
||||||
senderAddress: string,
|
|
||||||
codeId: number,
|
codeId: number,
|
||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
admin?: string,
|
admin?: string,
|
||||||
funds?: Coin[]
|
|
||||||
) => Promise<InstantiateResponse>
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
use: (contractAddress: string) => WhiteListInstance
|
use: (contractAddress: string) => WhiteListInstance
|
||||||
|
|
||||||
|
messages: (contractAddress: string) => WhitelistMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WhiteList = (
|
export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): WhiteListContract => {
|
||||||
client: SigningCosmWasmClient,
|
|
||||||
senderAddress: string
|
|
||||||
): WhiteListContract => {
|
|
||||||
const use = (contractAddress: string): WhiteListInstance => {
|
const use = (contractAddress: string): WhiteListInstance => {
|
||||||
console.log(client, 'client')
|
|
||||||
console.log(senderAddress, 'senderAddress')
|
|
||||||
///QUERY START
|
///QUERY START
|
||||||
const hasStarted = async (): Promise<boolean> => {
|
const hasStarted = async (): Promise<boolean> => {
|
||||||
return client.queryContractSmart(contractAddress, { has_started: {} })
|
return client.queryContractSmart(contractAddress, { has_started: {} })
|
||||||
@ -69,10 +127,7 @@ export const WhiteList = (
|
|||||||
return client.queryContractSmart(contractAddress, { is_active: {} })
|
return client.queryContractSmart(contractAddress, { is_active: {} })
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = async (
|
const members = async (startAfter?: string, limit?: number): Promise<string[]> => {
|
||||||
limit: number,
|
|
||||||
startAfter?: string
|
|
||||||
): Promise<string[]> => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
return client.queryContractSmart(contractAddress, {
|
||||||
members: { limit, start_after: startAfter },
|
members: { limit, start_after: startAfter },
|
||||||
})
|
})
|
||||||
@ -92,63 +147,50 @@ export const WhiteList = (
|
|||||||
/// QUERY END
|
/// QUERY END
|
||||||
/// EXECUTE START
|
/// EXECUTE START
|
||||||
const updateStartTime = async (startTime: string): Promise<string> => {
|
const updateStartTime = async (startTime: string): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(txSigner, contractAddress, { update_start_time: startTime }, 'auto')
|
||||||
senderAddress,
|
|
||||||
contractAddress,
|
|
||||||
{ update_start_time: startTime },
|
|
||||||
'auto',
|
|
||||||
'memo'
|
|
||||||
)
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateEndTime = async (endTime: string): Promise<string> => {
|
const updateEndTime = async (endTime: string): Promise<string> => {
|
||||||
|
const res = await client.execute(txSigner, contractAddress, { update_end_time: endTime }, 'auto')
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMembers = async (memberList: string[]): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{ update_end_time: endTime },
|
{
|
||||||
'auto'
|
add_members: {
|
||||||
|
to_add: memberList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
)
|
)
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const addMembers = async (to_add: string[]): Promise<string> => {
|
const removeMembers = async (memberList: string[]): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
txSigner,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
{ add_members: to_add },
|
{
|
||||||
'auto'
|
remove_members: {
|
||||||
)
|
to_remove: memberList,
|
||||||
return res.transactionHash
|
},
|
||||||
}
|
},
|
||||||
|
'auto',
|
||||||
const removeMembers = async (to_remove: string[]): Promise<string> => {
|
|
||||||
const res = await client.execute(
|
|
||||||
senderAddress,
|
|
||||||
contractAddress,
|
|
||||||
{ remove_members: to_remove },
|
|
||||||
'auto'
|
|
||||||
)
|
)
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePerAddressLimit = async (limit: number): Promise<string> => {
|
const updatePerAddressLimit = async (limit: number): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(txSigner, contractAddress, { update_per_address_limit: limit }, 'auto')
|
||||||
senderAddress,
|
|
||||||
contractAddress,
|
|
||||||
{ update_per_address_limit: limit },
|
|
||||||
'auto'
|
|
||||||
)
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
const increaseMemberLimit = async (limit: number): Promise<string> => {
|
const increaseMemberLimit = async (limit: number): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(txSigner, contractAddress, { increase_member_limit: limit }, 'auto')
|
||||||
senderAddress,
|
|
||||||
contractAddress,
|
|
||||||
{ increase_member_limit: limit },
|
|
||||||
'auto'
|
|
||||||
)
|
|
||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
/// EXECUTE END
|
/// EXECUTE END
|
||||||
@ -171,25 +213,15 @@ export const WhiteList = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const instantiate = async (
|
const instantiate = async (
|
||||||
senderAddress: string,
|
|
||||||
codeId: number,
|
codeId: number,
|
||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
admin?: string,
|
admin?: string,
|
||||||
funds?: Coin[]
|
|
||||||
): Promise<InstantiateResponse> => {
|
): Promise<InstantiateResponse> => {
|
||||||
console.log('Funds:' + funds)
|
const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
|
||||||
const result = await client.instantiate(
|
funds: [coin('100000000', 'ustars')],
|
||||||
senderAddress,
|
|
||||||
codeId,
|
|
||||||
initMsg,
|
|
||||||
label,
|
|
||||||
'auto',
|
|
||||||
{
|
|
||||||
funds,
|
|
||||||
admin,
|
admin,
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contractAddress: result.contractAddress,
|
contractAddress: result.contractAddress,
|
||||||
@ -197,5 +229,82 @@ export const WhiteList = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { use, instantiate }
|
const messages = (contractAddress: string) => {
|
||||||
|
const updateStartTime = (startTime: string) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
update_start_time: startTime,
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateEndTime = (endTime: string) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
update_end_time: endTime,
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMembers = (memberList: string[]) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
add_members: { to_add: memberList },
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeMembers = (memberList: string[]) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
remove_members: { to_remove: memberList },
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePerAddressLimit = (limit: number) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
update_per_address_limit: limit,
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const increaseMemberLimit = (limit: number) => {
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: {
|
||||||
|
increase_member_limit: limit,
|
||||||
|
},
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateStartTime,
|
||||||
|
updateEndTime,
|
||||||
|
addMembers,
|
||||||
|
removeMembers,
|
||||||
|
updatePerAddressLimit,
|
||||||
|
increaseMemberLimit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { use, instantiate, messages }
|
||||||
}
|
}
|
||||||
|
136
contracts/whitelist/messages/execute.ts
Normal file
136
contracts/whitelist/messages/execute.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import type { WhiteListInstance } from '../index'
|
||||||
|
import { useWhiteListContract } from '../index'
|
||||||
|
|
||||||
|
export type ExecuteType = typeof EXECUTE_TYPES[number]
|
||||||
|
|
||||||
|
export const EXECUTE_TYPES = [
|
||||||
|
'update_start_time',
|
||||||
|
'update_end_time',
|
||||||
|
'add_members',
|
||||||
|
'remove_members',
|
||||||
|
'update_per_address_limit',
|
||||||
|
'increase_member_limit',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export interface ExecuteListItem {
|
||||||
|
id: ExecuteType
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||||
|
{
|
||||||
|
id: 'update_start_time',
|
||||||
|
name: 'Update Start Time',
|
||||||
|
description: `Update the start time of the whitelist`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'update_end_time',
|
||||||
|
name: 'Update End Time',
|
||||||
|
description: `Update the end time of the whitelist`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'add_members',
|
||||||
|
name: 'Add Members',
|
||||||
|
description: `Add members to the whitelist`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'remove_members',
|
||||||
|
name: 'Remove Members',
|
||||||
|
description: `Remove members from the whitelist`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'update_per_address_limit',
|
||||||
|
name: 'Update Per Address Limit',
|
||||||
|
description: `Update tokens per address limit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'increase_member_limit',
|
||||||
|
name: 'Increase Member Limit',
|
||||||
|
description: `Increase the member limit of the whitelist`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface DispatchExecuteProps {
|
||||||
|
type: ExecuteType
|
||||||
|
[k: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type Select<T extends ExecuteType> = T
|
||||||
|
|
||||||
|
/** @see {@link WhiteListInstance} */
|
||||||
|
export type DispatchExecuteArgs = {
|
||||||
|
contract: string
|
||||||
|
messages?: WhiteListInstance
|
||||||
|
} & (
|
||||||
|
| { type: undefined }
|
||||||
|
| { type: Select<'update_start_time'>; timestamp: string }
|
||||||
|
| { type: Select<'update_end_time'>; timestamp: string }
|
||||||
|
| { type: Select<'add_members'>; members: string[] }
|
||||||
|
| { type: Select<'remove_members'>; members: string[] }
|
||||||
|
| { type: Select<'update_per_address_limit'>; limit: number }
|
||||||
|
| { type: Select<'increase_member_limit'>; limit: number }
|
||||||
|
)
|
||||||
|
|
||||||
|
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||||
|
const { messages } = args
|
||||||
|
if (!messages) {
|
||||||
|
throw new Error('cannot dispatch execute, messages is not defined')
|
||||||
|
}
|
||||||
|
switch (args.type) {
|
||||||
|
case 'update_start_time': {
|
||||||
|
return messages.updateStartTime(args.timestamp)
|
||||||
|
}
|
||||||
|
case 'update_end_time': {
|
||||||
|
return messages.updateEndTime(args.timestamp)
|
||||||
|
}
|
||||||
|
case 'add_members': {
|
||||||
|
return messages.addMembers(args.members)
|
||||||
|
}
|
||||||
|
case 'remove_members': {
|
||||||
|
return messages.removeMembers(args.members)
|
||||||
|
}
|
||||||
|
case 'update_per_address_limit': {
|
||||||
|
return messages.updatePerAddressLimit(args.limit)
|
||||||
|
}
|
||||||
|
case 'increase_member_limit': {
|
||||||
|
return messages.increaseMemberLimit(args.limit)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error('unknown execute type')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const { messages } = useWhiteListContract()
|
||||||
|
const { contract } = args
|
||||||
|
switch (args.type) {
|
||||||
|
case 'update_start_time': {
|
||||||
|
return messages(contract)?.updateStartTime(args.timestamp)
|
||||||
|
}
|
||||||
|
case 'update_end_time': {
|
||||||
|
return messages(contract)?.updateEndTime(args.timestamp)
|
||||||
|
}
|
||||||
|
case 'add_members': {
|
||||||
|
return messages(contract)?.addMembers(args.members)
|
||||||
|
}
|
||||||
|
case 'remove_members': {
|
||||||
|
return messages(contract)?.removeMembers(args.members)
|
||||||
|
}
|
||||||
|
case 'update_per_address_limit': {
|
||||||
|
return messages(contract)?.updatePerAddressLimit(args.limit)
|
||||||
|
}
|
||||||
|
case 'increase_member_limit': {
|
||||||
|
return messages(contract)?.increaseMemberLimit(args.limit)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
|
||||||
|
return arr.some((val) => type === val)
|
||||||
|
}
|
47
contracts/whitelist/messages/query.ts
Normal file
47
contracts/whitelist/messages/query.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import type { WhiteListInstance } from '../contract'
|
||||||
|
|
||||||
|
export type QueryType = typeof QUERY_TYPES[number]
|
||||||
|
|
||||||
|
export const QUERY_TYPES = ['has_started', 'has_ended', 'is_active', 'members', 'has_member', 'config'] as const
|
||||||
|
|
||||||
|
export interface QueryListItem {
|
||||||
|
id: QueryType
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QUERY_LIST: QueryListItem[] = [
|
||||||
|
{ id: 'has_started', name: 'Has Started', description: 'Check if the whitelist minting has started' },
|
||||||
|
{ id: 'has_ended', name: 'Has Ended', description: 'Check if the whitelist minting has ended' },
|
||||||
|
{ id: 'is_active', name: 'Is Active', description: 'Check if the whitelist minting is active' },
|
||||||
|
{ id: 'members', name: 'Members', description: 'View the whitelist members' },
|
||||||
|
{ id: 'has_member', name: 'Has Member', description: 'Check if a member is in the whitelist' },
|
||||||
|
{ id: 'config', name: 'Config', description: 'View the whitelist configuration' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface DispatchQueryProps {
|
||||||
|
messages: WhiteListInstance | undefined
|
||||||
|
type: QueryType
|
||||||
|
address: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||||
|
const { messages, type, address } = props
|
||||||
|
switch (type) {
|
||||||
|
case 'has_started':
|
||||||
|
return messages?.hasStarted()
|
||||||
|
case 'has_ended':
|
||||||
|
return messages?.hasEnded()
|
||||||
|
case 'is_active':
|
||||||
|
return messages?.isActive()
|
||||||
|
case 'members':
|
||||||
|
return messages?.members()
|
||||||
|
case 'has_member':
|
||||||
|
return messages?.hasMember(address)
|
||||||
|
case 'config':
|
||||||
|
return messages?.config()
|
||||||
|
default: {
|
||||||
|
throw new Error('unknown query type')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,29 @@
|
|||||||
import { Coin } from '@cosmjs/proto-signing'
|
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { WhiteList } from './contract'
|
import type { InstantiateResponse, WhiteListContract, WhiteListInstance, WhitelistMessages } from './contract'
|
||||||
import {
|
import { WhiteList as initContract } from './contract'
|
||||||
InstantiateResponse,
|
|
||||||
WhiteList as initContract,
|
|
||||||
WhiteListContract,
|
|
||||||
WhiteListInstance,
|
|
||||||
} from './contract'
|
|
||||||
|
|
||||||
export interface useWhiteListContractProps {
|
export interface UseWhiteListContractProps {
|
||||||
instantiate: (
|
instantiate: (
|
||||||
codeId: number,
|
codeId: number,
|
||||||
initMsg: Record<string, unknown>,
|
initMsg: Record<string, unknown>,
|
||||||
label: string,
|
label: string,
|
||||||
admin?: string,
|
admin?: string,
|
||||||
funds?: Coin[]
|
|
||||||
) => Promise<InstantiateResponse>
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
use: (customAddress: string) => WhiteListInstance | undefined
|
use: (customAddress?: string) => WhiteListInstance | undefined
|
||||||
|
|
||||||
updateContractAddress: (contractAddress: string) => void
|
updateContractAddress: (contractAddress: string) => void
|
||||||
|
|
||||||
|
messages: (contractAddress: string) => WhitelistMessages | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWhiteListContract(): useWhiteListContractProps {
|
export function useWhiteListContract(): UseWhiteListContractProps {
|
||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
|
|
||||||
const [address, setAddress] = useState<string>('')
|
const [address, setAddress] = useState<string>('')
|
||||||
const [WhiteList, setWhiteList] = useState<WhiteListContract>()
|
const [whiteList, setWhiteList] = useState<WhiteListContract>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAddress(localStorage.getItem('contract_address') || '')
|
setAddress(localStorage.getItem('contract_address') || '')
|
||||||
@ -35,14 +31,10 @@ export function useWhiteListContract(): useWhiteListContractProps {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (wallet.initialized) {
|
if (wallet.initialized) {
|
||||||
const getWhiteListInstance = async (): Promise<void> => {
|
|
||||||
const client = wallet.getClient()
|
const client = wallet.getClient()
|
||||||
const whiteListContract = initContract(client, wallet.address)
|
const whiteListContract = initContract(client, wallet.address)
|
||||||
setWhiteList(whiteListContract)
|
setWhiteList(whiteListContract)
|
||||||
}
|
}
|
||||||
|
|
||||||
getWhiteListInstance()
|
|
||||||
}
|
|
||||||
}, [wallet])
|
}, [wallet])
|
||||||
|
|
||||||
const updateContractAddress = (contractAddress: string) => {
|
const updateContractAddress = (contractAddress: string) => {
|
||||||
@ -50,34 +42,33 @@ export function useWhiteListContract(): useWhiteListContractProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const instantiate = useCallback(
|
const instantiate = useCallback(
|
||||||
(codeId, initMsg, label, admin?, funds?): Promise<InstantiateResponse> => {
|
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!WhiteList) return reject('Contract is not initialized.')
|
if (!whiteList) {
|
||||||
WhiteList.instantiate(
|
reject(new Error('Contract is not initialized.'))
|
||||||
wallet.address,
|
return
|
||||||
codeId,
|
}
|
||||||
initMsg,
|
whiteList.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||||
label,
|
|
||||||
admin,
|
|
||||||
funds
|
|
||||||
)
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[WhiteList, wallet]
|
[whiteList],
|
||||||
)
|
)
|
||||||
|
|
||||||
const use = useCallback(
|
const use = useCallback(
|
||||||
(customAddress = ''): WhiteListInstance | undefined => {
|
(customAddress = ''): WhiteListInstance | undefined => {
|
||||||
return WhiteList?.use(address || customAddress)
|
return whiteList?.use(address || customAddress)
|
||||||
},
|
},
|
||||||
[WhiteList]
|
[whiteList, address],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const messages = useCallback((): WhitelistMessages | undefined => {
|
||||||
|
return whiteList?.messages(address)
|
||||||
|
}, [whiteList, address])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instantiate,
|
instantiate,
|
||||||
use,
|
use,
|
||||||
updateContractAddress,
|
updateContractAddress,
|
||||||
|
messages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
env.d.ts
vendored
8
env.d.ts
vendored
@ -15,17 +15,13 @@ declare namespace NodeJS {
|
|||||||
readonly APP_VERSION: string
|
readonly APP_VERSION: string
|
||||||
|
|
||||||
readonly NEXT_PUBLIC_SG721_CODE_ID: string
|
readonly NEXT_PUBLIC_SG721_CODE_ID: string
|
||||||
|
readonly NEXT_PUBLIC_MINTER_CODE_ID: string
|
||||||
|
readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string
|
||||||
|
|
||||||
readonly NEXT_PUBLIC_API_URL: string
|
readonly NEXT_PUBLIC_API_URL: string
|
||||||
readonly NEXT_PUBLIC_BLOCK_EXPLORER_URL: string
|
readonly NEXT_PUBLIC_BLOCK_EXPLORER_URL: string
|
||||||
readonly NEXT_PUBLIC_NETWORK: string
|
readonly NEXT_PUBLIC_NETWORK: string
|
||||||
readonly NEXT_PUBLIC_WEBSITE_URL: string
|
readonly NEXT_PUBLIC_WEBSITE_URL: string
|
||||||
|
|
||||||
readonly NEXT_PUBLIC_S3_BUCKET: string
|
|
||||||
readonly NEXT_PUBLIC_S3_ENDPOINT: string
|
|
||||||
readonly NEXT_PUBLIC_S3_KEY: string
|
|
||||||
readonly NEXT_PUBLIC_S3_REGION: string
|
|
||||||
readonly NEXT_PUBLIC_S3_SECRET: string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
pages/contracts/index.tsx
Normal file
41
pages/contracts/index.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { HomeCard } from 'components/HomeCard'
|
||||||
|
import type { NextPage } from 'next'
|
||||||
|
// import Brand from 'public/brand/brand.svg'
|
||||||
|
import { withMetadata } from 'utils/layout'
|
||||||
|
|
||||||
|
const HomePage: NextPage = () => {
|
||||||
|
return (
|
||||||
|
<section className="px-8 pt-4 pb-16 mx-auto space-y-8 max-w-4xl">
|
||||||
|
<div className="flex justify-center items-center py-8 max-w-xl">
|
||||||
|
{/* <Brand className="w-full text-plumbus" /> */}
|
||||||
|
</div>
|
||||||
|
<h1 className="font-heading text-4xl font-bold">Smart Contracts</h1>
|
||||||
|
<p className="text-xl">
|
||||||
|
Here you can invoke and query different smart contracts and see the results.
|
||||||
|
<br />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div className="grid gap-8 md:grid-cols-2">
|
||||||
|
<HomeCard className="p-4 -m-4 hover:bg-gray-500/10 rounded" link="/contracts/minter" title="Minter contract">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
</HomeCard>
|
||||||
|
<HomeCard className="p-4 -m-4 hover:bg-gray-500/10 rounded" link="/contracts/sg721" title="Sg721 Contract">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
</HomeCard>
|
||||||
|
<HomeCard
|
||||||
|
className="p-4 -m-4 hover:bg-gray-500/10 rounded"
|
||||||
|
link="/contracts/whitelist"
|
||||||
|
title="Whitelist Contract"
|
||||||
|
>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
</HomeCard>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(HomePage, { center: false })
|
166
pages/contracts/minter/execute.tsx
Normal file
166
pages/contracts/minter/execute.tsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
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 { 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 { 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 { NextPage } from 'next'
|
||||||
|
import { NextSeo } from 'next-seo'
|
||||||
|
import type { FormEvent } from 'react'
|
||||||
|
import { 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 MinterExecutePage: NextPage = () => {
|
||||||
|
const { minter: contract } = useContracts()
|
||||||
|
const wallet = useWallet()
|
||||||
|
const [lastTx, setLastTx] = useState('')
|
||||||
|
|
||||||
|
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||||
|
|
||||||
|
const comboboxState = useExecuteComboboxState()
|
||||||
|
const type = comboboxState.value?.id
|
||||||
|
|
||||||
|
const limitState = useNumberInputState({
|
||||||
|
id: 'per-address-limi',
|
||||||
|
name: 'perAddressLimit',
|
||||||
|
title: 'Per Address Limit',
|
||||||
|
subtitle: 'Enter the per address limit',
|
||||||
|
})
|
||||||
|
|
||||||
|
const tokenIdState = useNumberInputState({
|
||||||
|
id: 'token-id',
|
||||||
|
name: 'tokenId',
|
||||||
|
title: 'Token ID',
|
||||||
|
subtitle: 'Enter the token ID',
|
||||||
|
})
|
||||||
|
|
||||||
|
const priceState = useNumberInputState({
|
||||||
|
id: 'price',
|
||||||
|
name: 'price',
|
||||||
|
title: 'Price',
|
||||||
|
subtitle: 'Enter the token price',
|
||||||
|
})
|
||||||
|
|
||||||
|
const contractState = useInputState({
|
||||||
|
id: 'contract-address',
|
||||||
|
name: 'contract-address',
|
||||||
|
title: 'Minter Address',
|
||||||
|
subtitle: 'Address of the Minter contract',
|
||||||
|
})
|
||||||
|
|
||||||
|
const recipientState = useInputState({
|
||||||
|
id: 'recipient-address',
|
||||||
|
name: 'recipient',
|
||||||
|
title: 'Recipient Address',
|
||||||
|
subtitle: 'Address of the recipient',
|
||||||
|
})
|
||||||
|
|
||||||
|
const whitelistState = useInputState({
|
||||||
|
id: 'whitelist-address',
|
||||||
|
name: 'whitelistAddress',
|
||||||
|
title: 'Whitelist Address',
|
||||||
|
subtitle: 'Address of the whitelist contract',
|
||||||
|
})
|
||||||
|
|
||||||
|
const showWhitelistField = type === 'set_whitelist'
|
||||||
|
const showDateField = type === 'update_start_time'
|
||||||
|
const showLimitField = type === 'update_per_address_limit'
|
||||||
|
const showTokenIdField = type === 'mint_for'
|
||||||
|
const showRecipientField = isEitherType(type, ['mint_to', 'mint_for'])
|
||||||
|
const showPriceField = type === 'mint'
|
||||||
|
|
||||||
|
const messages = useMemo(() => contract?.use(contractState.value), [contract, wallet.address, contractState.value])
|
||||||
|
const payload: DispatchExecuteArgs = {
|
||||||
|
whitelist: whitelistState.value,
|
||||||
|
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||||
|
limit: limitState.value,
|
||||||
|
contract: contractState.value,
|
||||||
|
tokenId: tokenIdState.value,
|
||||||
|
messages,
|
||||||
|
recipient: recipientState.value,
|
||||||
|
txSigner: wallet.address,
|
||||||
|
price: priceState.value ? (Number(priceState.value) * 1_000_000).toString() : '0',
|
||||||
|
type,
|
||||||
|
}
|
||||||
|
const { isLoading, mutate } = useMutation(
|
||||||
|
async (event: FormEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!type) {
|
||||||
|
throw new Error('Please select message type!')
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="py-6 px-12 space-y-4">
|
||||||
|
<NextSeo title="Execute Minter Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Minter contract facilitates primary market vending machine style minting."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Minter Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={2} data={minterLinkTabs} />
|
||||||
|
|
||||||
|
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<AddressInput {...contractState} />
|
||||||
|
<ExecuteCombobox {...comboboxState} />
|
||||||
|
{showRecipientField && <AddressInput {...recipientState} />}
|
||||||
|
{showWhitelistField && <AddressInput {...whitelistState} />}
|
||||||
|
{showLimitField && <NumberInput {...limitState} />}
|
||||||
|
{showTokenIdField && <NumberInput {...tokenIdState} />}
|
||||||
|
{showPriceField && <NumberInput {...priceState} />}
|
||||||
|
{/* TODO: Fix address execute message */}
|
||||||
|
<Conditional test={showDateField}>
|
||||||
|
<FormControl htmlId="start-date" subtitle="Start time for the minting" title="Start Time">
|
||||||
|
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||||
|
</FormControl>
|
||||||
|
</Conditional>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="relative">
|
||||||
|
<Button className="absolute top-0 right-0" isLoading={isLoading} rightIcon={<FaArrowRight />} type="submit">
|
||||||
|
Execute
|
||||||
|
</Button>
|
||||||
|
<FormControl subtitle="View execution transaction hash" title="Transaction Hash">
|
||||||
|
<TransactionHash hash={lastTx} />
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<FormControl subtitle="View current message to be sent" title="Payload Preview">
|
||||||
|
<JsonPreview content={previewExecutePayload(payload)} isCopyable />
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(MinterExecutePage, { center: false })
|
1
pages/contracts/minter/index.tsx
Normal file
1
pages/contracts/minter/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './instantiate'
|
279
pages/contracts/minter/instantiate.tsx
Normal file
279
pages/contracts/minter/instantiate.tsx
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
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 { minterLinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { useContracts } from 'contexts/contracts'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import type { InstantiateResponse } from 'contracts/minter'
|
||||||
|
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 { MINTER_CODE_ID } from 'utils/constants'
|
||||||
|
import { withMetadata } from 'utils/layout'
|
||||||
|
import { links } from 'utils/links'
|
||||||
|
|
||||||
|
const MinterInstantiatePage: NextPage = () => {
|
||||||
|
const wallet = useWallet()
|
||||||
|
const contract = useContracts().minter
|
||||||
|
|
||||||
|
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||||
|
|
||||||
|
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 minterState = useInputState({
|
||||||
|
id: 'minter-address',
|
||||||
|
name: 'minterAddress',
|
||||||
|
title: 'Minter Address',
|
||||||
|
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||||
|
subtitle: 'Address that has the permissions to mint on sg721 contract',
|
||||||
|
})
|
||||||
|
|
||||||
|
const codeIdState = useNumberInputState({
|
||||||
|
id: 'code-id',
|
||||||
|
name: 'code-id',
|
||||||
|
title: 'Code ID',
|
||||||
|
subtitle: 'Code ID for the sg721 contract',
|
||||||
|
placeholder: '1',
|
||||||
|
})
|
||||||
|
|
||||||
|
const creatorState = useInputState({
|
||||||
|
id: 'creator-address',
|
||||||
|
name: 'creatorAddress',
|
||||||
|
title: 'Creator Address',
|
||||||
|
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||||
|
subtitle: 'Address of the collection creator',
|
||||||
|
})
|
||||||
|
|
||||||
|
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',
|
||||||
|
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 = useNumberInputState({
|
||||||
|
id: 'royalty-share',
|
||||||
|
name: 'royaltyShare',
|
||||||
|
title: 'Share Percentage',
|
||||||
|
subtitle: 'Percentage of royalties to be paid',
|
||||||
|
placeholder: '8',
|
||||||
|
})
|
||||||
|
|
||||||
|
const unitPriceState = useNumberInputState({
|
||||||
|
id: 'unit-price',
|
||||||
|
name: 'unitPrice',
|
||||||
|
title: 'Unit Price',
|
||||||
|
subtitle: 'Price of each tokens in collection',
|
||||||
|
placeholder: '500',
|
||||||
|
})
|
||||||
|
|
||||||
|
const baseTokenUriState = useInputState({
|
||||||
|
id: 'base-token-uri',
|
||||||
|
name: 'baseTokenUri',
|
||||||
|
title: 'Base Token URI',
|
||||||
|
subtitle: 'IPFS uri for the tokens',
|
||||||
|
placeholder: 'ipfs://bafybe....',
|
||||||
|
})
|
||||||
|
|
||||||
|
const tokenNumberState = useNumberInputState({
|
||||||
|
id: 'token-number',
|
||||||
|
name: 'tokenNumber',
|
||||||
|
title: 'Token Amount',
|
||||||
|
subtitle: 'Number of tokens in collection',
|
||||||
|
placeholder: '1000',
|
||||||
|
})
|
||||||
|
|
||||||
|
const perAddressLimitState = useNumberInputState({
|
||||||
|
id: 'per-address-limit',
|
||||||
|
name: 'perAddressLimit',
|
||||||
|
title: 'Per Address Limit',
|
||||||
|
subtitle: 'Limit of tokens per address',
|
||||||
|
placeholder: '5',
|
||||||
|
})
|
||||||
|
|
||||||
|
const whitelistAddressState = useInputState({
|
||||||
|
id: 'whitelist-address',
|
||||||
|
name: 'whitelistAddress',
|
||||||
|
title: 'Whitelist Address',
|
||||||
|
subtitle: 'Address to whitelist contract',
|
||||||
|
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data, isLoading, mutate } = useMutation(
|
||||||
|
async (event: FormEvent): Promise<InstantiateResponse | null> => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!contract) {
|
||||||
|
throw new Error('Smart contract connection failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
let royaltyInfo = null
|
||||||
|
if (royaltyPaymentAddressState.value && royaltyShareState.value) {
|
||||||
|
royaltyInfo = {
|
||||||
|
paymentAddress: royaltyPaymentAddressState.value,
|
||||||
|
share: royaltyShareState.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenNumberState.value < 1 || tokenNumberState.value > 10000) {
|
||||||
|
throw new Error('Token amount must be between 1 and 10000')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perAddressLimitState.value < 1 || perAddressLimitState.value > 50) {
|
||||||
|
throw new Error('Per address limit must be between 1 and 50')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(unitPriceState.value) < 500) {
|
||||||
|
throw new Error('Unit price must be greater than 500 STARS')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startDate) {
|
||||||
|
throw new Error('Start date is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = {
|
||||||
|
base_token_uri: baseTokenUriState.value,
|
||||||
|
num_tokens: tokenNumberState.value,
|
||||||
|
sg721_code_id: codeIdState.value,
|
||||||
|
sg721_instantiate_msg: {
|
||||||
|
name: nameState.value,
|
||||||
|
symbol: symbolState.value,
|
||||||
|
minter: minterState.value,
|
||||||
|
collection_info: {
|
||||||
|
creator: creatorState.value,
|
||||||
|
description: descriptionState.value,
|
||||||
|
image: imageState.value,
|
||||||
|
external_link: externalLinkState.value || null,
|
||||||
|
royalty_info: royaltyInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
per_address_limit: perAddressLimitState.value,
|
||||||
|
unit_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
|
||||||
|
whitelist_address: whitelistAddressState.value || null,
|
||||||
|
start_time: (startDate.getTime() * 1_000_000).toString(),
|
||||||
|
}
|
||||||
|
return toast.promise(contract.instantiate(MINTER_CODE_ID, msg, 'Stargaze Minter Contract', wallet.address), {
|
||||||
|
loading: 'Instantiating contract...',
|
||||||
|
error: 'Instantiation failed!',
|
||||||
|
success: 'Instantiation success!',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(String(error))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const txHash = data?.transactionHash
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||||
|
<NextSeo title="Instantiate Minter Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Minter contract facilitates primary market vending machine style minting."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Minter Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={0} data={minterLinkTabs} />
|
||||||
|
|
||||||
|
<Conditional test={Boolean(data)}>
|
||||||
|
<Alert type="info">
|
||||||
|
<b>Instantiate success!</b> Here is the transaction result containing the contract address and the transaction
|
||||||
|
hash.
|
||||||
|
</Alert>
|
||||||
|
<JsonPreview content={data} title="Transaction Result" />
|
||||||
|
<br />
|
||||||
|
</Conditional>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about your sg721 contract" title="SG721 Contract Details">
|
||||||
|
<NumberInput isRequired {...codeIdState} />
|
||||||
|
<TextInput isRequired {...nameState} />
|
||||||
|
<TextInput isRequired {...symbolState} />
|
||||||
|
<TextInput isRequired {...minterState} />
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about your collection" title="Collection Details">
|
||||||
|
<TextInput isRequired {...creatorState} />
|
||||||
|
<FormTextArea isRequired {...descriptionState} />
|
||||||
|
<TextInput isRequired {...imageState} />
|
||||||
|
<TextInput {...externalLinkState} />
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about royalty" title="Royalty Details">
|
||||||
|
<TextInput {...royaltyPaymentAddressState} />
|
||||||
|
<NumberInput {...royaltyShareState} />
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
|
||||||
|
<NumberInput isRequired {...unitPriceState} />
|
||||||
|
<TextInput isRequired {...baseTokenUriState} />
|
||||||
|
<NumberInput isRequired {...tokenNumberState} />
|
||||||
|
<NumberInput isRequired {...perAddressLimitState} />
|
||||||
|
<FormControl htmlId="start-date" isRequired subtitle="Start time for the minting" title="Start Time">
|
||||||
|
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
||||||
|
</FormControl>
|
||||||
|
<TextInput {...whitelistAddressState} />
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<div className="flex items-center p-4">
|
||||||
|
<div className="flex-grow" />
|
||||||
|
<Button isLoading={isLoading} isWide rightIcon={<FaAsterisk />} type="submit">
|
||||||
|
Instantiate Contract
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(MinterInstantiatePage, { center: false })
|
120
pages/contracts/minter/query.tsx
Normal file
120
pages/contracts/minter/query.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import { Conditional } from 'components/Conditional'
|
||||||
|
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 { minterLinkTabs } 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 { 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 MinterQueryPage: NextPage = () => {
|
||||||
|
const { minter: contract } = useContracts()
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
|
const contractState = useInputState({
|
||||||
|
id: 'contract-address',
|
||||||
|
name: 'contract-address',
|
||||||
|
title: 'Minter Address',
|
||||||
|
subtitle: 'Address of the 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<QueryType>('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)
|
||||||
|
},
|
||||||
|
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 (
|
||||||
|
<section className="py-6 px-12 space-y-4">
|
||||||
|
<NextSeo title="Query Minter Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Minter contract facilitates primary market vending machine style minting."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Minter Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={1} data={minterLinkTabs} />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 p-4 space-x-8">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<AddressInput {...contractState} />
|
||||||
|
<FormControl htmlId="contract-query-type" subtitle="Type of query to be dispatched" title="Query Type">
|
||||||
|
<select
|
||||||
|
className={clsx(
|
||||||
|
'bg-white/10 rounded border-2 border-white/20 form-select',
|
||||||
|
'placeholder:text-white/50',
|
||||||
|
'focus:ring focus:ring-plumbus-20',
|
||||||
|
)}
|
||||||
|
id="contract-query-type"
|
||||||
|
name="query-type"
|
||||||
|
onChange={(e) => setType(e.target.value as QueryType)}
|
||||||
|
>
|
||||||
|
{QUERY_LIST.map(({ id, name }) => (
|
||||||
|
<option key={`query-${id}`} value={id}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</FormControl>
|
||||||
|
<Conditional test={type === 'mint_count'}>
|
||||||
|
<AddressInput {...addressState} />
|
||||||
|
</Conditional>
|
||||||
|
</div>
|
||||||
|
<JsonPreview content={contractAddress ? { type, response } : null} title="Query Response" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(MinterQueryPage, { center: false })
|
156
pages/contracts/sg721/execute.tsx
Normal file
156
pages/contracts/sg721/execute.tsx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import { Button } from 'components/Button'
|
||||||
|
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||||
|
import { ExecuteCombobox } from 'components/contracts/sg721/ExecuteCombobox'
|
||||||
|
import { useExecuteComboboxState } from 'components/contracts/sg721/ExecuteCombobox.hooks'
|
||||||
|
import { FormControl } from 'components/FormControl'
|
||||||
|
import { AddressInput, TextInput } from 'components/forms/FormInput'
|
||||||
|
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||||
|
import { JsonTextArea } from 'components/forms/FormTextArea'
|
||||||
|
import { JsonPreview } from 'components/JsonPreview'
|
||||||
|
import { LinkTabs } from 'components/LinkTabs'
|
||||||
|
import { sg721LinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { TransactionHash } from 'components/TransactionHash'
|
||||||
|
import { useContracts } from 'contexts/contracts'
|
||||||
|
import type { DispatchExecuteArgs } from 'contracts/sg721/messages/execute'
|
||||||
|
import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/sg721/messages/execute'
|
||||||
|
import type { NextPage } from 'next'
|
||||||
|
import { NextSeo } from 'next-seo'
|
||||||
|
import type { FormEvent } from 'react'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import { FaArrowRight } from 'react-icons/fa'
|
||||||
|
import { useMutation } from 'react-query'
|
||||||
|
import { parseJson } from 'utils/json'
|
||||||
|
import { withMetadata } from 'utils/layout'
|
||||||
|
import { links } from 'utils/links'
|
||||||
|
|
||||||
|
const Sg721ExecutePage: NextPage = () => {
|
||||||
|
const { sg721: contract } = useContracts()
|
||||||
|
const [lastTx, setLastTx] = useState('')
|
||||||
|
|
||||||
|
const comboboxState = useExecuteComboboxState()
|
||||||
|
const type = comboboxState.value?.id
|
||||||
|
|
||||||
|
const tokenIdState = useInputState({
|
||||||
|
id: 'token-id',
|
||||||
|
name: 'tokenId',
|
||||||
|
title: 'Token ID',
|
||||||
|
subtitle: 'Enter the token ID',
|
||||||
|
placeholder: '1',
|
||||||
|
})
|
||||||
|
|
||||||
|
const contractState = useInputState({
|
||||||
|
id: 'contract-address',
|
||||||
|
name: 'contract-address',
|
||||||
|
title: 'Minter Address',
|
||||||
|
subtitle: 'Address of the Minter contract',
|
||||||
|
})
|
||||||
|
|
||||||
|
const messageState = useInputState({
|
||||||
|
id: 'message',
|
||||||
|
name: 'message',
|
||||||
|
title: 'Message',
|
||||||
|
subtitle: 'Message to execute on the contract',
|
||||||
|
defaultValue: JSON.stringify({ key: 'value' }, null, 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
const recipientState = useInputState({
|
||||||
|
id: 'recipient-address',
|
||||||
|
name: 'recipient',
|
||||||
|
title: 'Recipient Address',
|
||||||
|
subtitle: 'Address of the recipient',
|
||||||
|
})
|
||||||
|
|
||||||
|
const operatorState = useInputState({
|
||||||
|
id: 'operator-address',
|
||||||
|
name: 'operator',
|
||||||
|
title: 'Operator Address',
|
||||||
|
subtitle: 'Address of the operator',
|
||||||
|
})
|
||||||
|
|
||||||
|
const tokenURIState = useInputState({
|
||||||
|
id: 'token-uri',
|
||||||
|
name: 'tokenURI',
|
||||||
|
title: 'Token URI',
|
||||||
|
subtitle: 'URI for the token',
|
||||||
|
placeholder: 'ipfs://xyz...',
|
||||||
|
})
|
||||||
|
|
||||||
|
const showTokenIdField = isEitherType(type, ['transfer_nft', 'send_nft', 'approve', 'revoke', 'mint', 'burn'])
|
||||||
|
const showRecipientField = isEitherType(type, ['transfer_nft', 'send_nft', 'approve', 'revoke', 'mint'])
|
||||||
|
const showOperatorField = isEitherType(type, ['approve_all', 'revoke_all'])
|
||||||
|
const showMessageField = type === 'send_nft'
|
||||||
|
const showTokenURIField = type === 'mint'
|
||||||
|
|
||||||
|
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
||||||
|
const payload: DispatchExecuteArgs = {
|
||||||
|
contract: contractState.value,
|
||||||
|
tokenId: tokenIdState.value,
|
||||||
|
messages,
|
||||||
|
recipient: recipientState.value,
|
||||||
|
operator: operatorState.value,
|
||||||
|
type,
|
||||||
|
tokenURI: tokenURIState.value,
|
||||||
|
msg: parseJson(messageState.value) || {},
|
||||||
|
}
|
||||||
|
const { isLoading, mutate } = useMutation(
|
||||||
|
async (event: FormEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!type) {
|
||||||
|
throw new Error('Please select message type!')
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="py-6 px-12 space-y-4">
|
||||||
|
<NextSeo title="Execute Sg721 Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Sg721 contract is a wrapper contract that has a set of optional extensions on top of cw721-base."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Sg721 Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={2} data={sg721LinkTabs} />
|
||||||
|
|
||||||
|
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<AddressInput {...contractState} />
|
||||||
|
<ExecuteCombobox {...comboboxState} />
|
||||||
|
{showRecipientField && <AddressInput {...recipientState} />}
|
||||||
|
{showOperatorField && <AddressInput {...operatorState} />}
|
||||||
|
{showTokenIdField && <TextInput {...tokenIdState} />}
|
||||||
|
{showTokenURIField && <TextInput {...tokenURIState} />}
|
||||||
|
{showMessageField && <JsonTextArea {...messageState} />}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="relative">
|
||||||
|
<Button className="absolute top-0 right-0" isLoading={isLoading} rightIcon={<FaArrowRight />} type="submit">
|
||||||
|
Execute
|
||||||
|
</Button>
|
||||||
|
<FormControl subtitle="View execution transaction hash" title="Transaction Hash">
|
||||||
|
<TransactionHash hash={lastTx} />
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<FormControl subtitle="View current message to be sent" title="Payload Preview">
|
||||||
|
<JsonPreview content={previewExecutePayload(payload)} isCopyable />
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(Sg721ExecutePage, { center: false })
|
1
pages/contracts/sg721/index.tsx
Normal file
1
pages/contracts/sg721/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './instantiate'
|
186
pages/contracts/sg721/instantiate.tsx
Normal file
186
pages/contracts/sg721/instantiate.tsx
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import { Alert } from 'components/Alert'
|
||||||
|
import { Button } from 'components/Button'
|
||||||
|
import { Conditional } from 'components/Conditional'
|
||||||
|
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||||
|
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 { JsonPreview } from 'components/JsonPreview'
|
||||||
|
import { LinkTabs } from 'components/LinkTabs'
|
||||||
|
import { sg721LinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { useContracts } from 'contexts/contracts'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import type { InstantiateResponse } from 'contracts/sg721'
|
||||||
|
import type { NextPage } from 'next'
|
||||||
|
import { NextSeo } from 'next-seo'
|
||||||
|
import type { FormEvent } from 'react'
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import { FaAsterisk } from 'react-icons/fa'
|
||||||
|
import { useMutation } from 'react-query'
|
||||||
|
import { SG721_CODE_ID } from 'utils/constants'
|
||||||
|
import { withMetadata } from 'utils/layout'
|
||||||
|
import { links } from 'utils/links'
|
||||||
|
|
||||||
|
const Sg721InstantiatePage: NextPage = () => {
|
||||||
|
const wallet = useWallet()
|
||||||
|
const contract = useContracts().sg721
|
||||||
|
|
||||||
|
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 minterState = useInputState({
|
||||||
|
id: 'minter-address',
|
||||||
|
name: 'minterAddress',
|
||||||
|
title: 'Minter Address',
|
||||||
|
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||||
|
subtitle: 'Address that has the permissions to mint on sg721 contract',
|
||||||
|
})
|
||||||
|
|
||||||
|
const creatorState = useInputState({
|
||||||
|
id: 'creator-address',
|
||||||
|
name: 'creatorAddress',
|
||||||
|
title: 'Creator Address',
|
||||||
|
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||||
|
subtitle: 'Address of the collection creator',
|
||||||
|
})
|
||||||
|
|
||||||
|
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',
|
||||||
|
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 = useNumberInputState({
|
||||||
|
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<InstantiateResponse | null> => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!contract) {
|
||||||
|
throw new Error('Smart contract connection failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
let royaltyInfo = null
|
||||||
|
if (royaltyPaymentAddressState.value && royaltyShareState.value) {
|
||||||
|
royaltyInfo = {
|
||||||
|
paymentAddress: royaltyPaymentAddressState.value,
|
||||||
|
share: royaltyShareState.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = {
|
||||||
|
name: nameState.value,
|
||||||
|
symbol: symbolState.value,
|
||||||
|
minter: minterState.value,
|
||||||
|
collection_info: {
|
||||||
|
creator: creatorState.value,
|
||||||
|
description: descriptionState.value,
|
||||||
|
image: imageState.value,
|
||||||
|
external_link: externalLinkState.value || null,
|
||||||
|
royalty_info: royaltyInfo,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return toast.promise(contract.instantiate(SG721_CODE_ID, msg, 'Stargaze Sg721 Contract', wallet.address), {
|
||||||
|
loading: 'Instantiating contract...',
|
||||||
|
error: 'Instantiation failed!',
|
||||||
|
success: 'Instantiation success!',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(String(error))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||||
|
<NextSeo title="Instantiate Sg721 Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Sg721 contract is a wrapper contract that has a set of optional extensions on top of cw721-base."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Sg721 Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={0} data={sg721LinkTabs} />
|
||||||
|
|
||||||
|
<Conditional test={Boolean(data)}>
|
||||||
|
<Alert type="info">
|
||||||
|
<b>Instantiate success!</b> Here is the transaction result containing the contract address and the transaction
|
||||||
|
hash.
|
||||||
|
</Alert>
|
||||||
|
<JsonPreview content={data} title="Transaction Result" />
|
||||||
|
<br />
|
||||||
|
</Conditional>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about your sg721 contract" title="SG721 Contract Details">
|
||||||
|
<TextInput isRequired {...nameState} />
|
||||||
|
<TextInput isRequired {...symbolState} />
|
||||||
|
<TextInput isRequired {...minterState} />
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about your collection" title="Collection Details">
|
||||||
|
<TextInput isRequired {...creatorState} />
|
||||||
|
<FormTextArea isRequired {...descriptionState} />
|
||||||
|
<TextInput isRequired {...imageState} />
|
||||||
|
<TextInput {...externalLinkState} />
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about royalty" title="Royalty Details">
|
||||||
|
<TextInput {...royaltyPaymentAddressState} />
|
||||||
|
<NumberInput {...royaltyShareState} />
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<div className="flex items-center p-4">
|
||||||
|
<div className="flex-grow" />
|
||||||
|
<Button isLoading={isLoading} isWide rightIcon={<FaAsterisk />} type="submit">
|
||||||
|
Instantiate Contract
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(Sg721InstantiatePage, { center: false })
|
135
pages/contracts/sg721/query.tsx
Normal file
135
pages/contracts/sg721/query.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import { Conditional } from 'components/Conditional'
|
||||||
|
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||||
|
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 { LinkTabs } from 'components/LinkTabs'
|
||||||
|
import { sg721LinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { useContracts } from 'contexts/contracts'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import type { QueryType } from 'contracts/sg721/messages/query'
|
||||||
|
import { dispatchQuery, QUERY_LIST } from 'contracts/sg721/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 Sg721QueryPage: NextPage = () => {
|
||||||
|
const { sg721: contract } = useContracts()
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
|
const contractState = useInputState({
|
||||||
|
id: 'contract-address',
|
||||||
|
name: 'contract-address',
|
||||||
|
title: 'Sg721 Address',
|
||||||
|
subtitle: 'Address of the Sg721 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 tokenIdState = useInputState({
|
||||||
|
id: 'token-id',
|
||||||
|
name: 'tokenId',
|
||||||
|
title: 'Token ID',
|
||||||
|
subtitle: 'Token ID of a given token',
|
||||||
|
})
|
||||||
|
const tokenId = tokenIdState.value
|
||||||
|
|
||||||
|
const [type, setType] = useState<QueryType>('owner_of')
|
||||||
|
|
||||||
|
const addressVisible = ['approval', 'all_operators', 'tokens'].includes(type)
|
||||||
|
const tokenIdVisible = ['owner_of', 'approval', 'approvals', 'nft_info', 'all_nft_info'].includes(type)
|
||||||
|
|
||||||
|
const { data: response } = useQuery(
|
||||||
|
[contractAddress, type, contract, wallet, tokenId, address] as const,
|
||||||
|
async ({ queryKey }) => {
|
||||||
|
const [_contractAddress, _type, _contract, _wallet, _tokenId, _address] = queryKey
|
||||||
|
const messages = contract?.use(contractAddress)
|
||||||
|
const result = await dispatchQuery({
|
||||||
|
messages,
|
||||||
|
type,
|
||||||
|
tokenId: _tokenId,
|
||||||
|
address: _address,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
{
|
||||||
|
placeholderData: null,
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
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 (
|
||||||
|
<section className="py-6 px-12 space-y-4">
|
||||||
|
<NextSeo title="Query Sg721 Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Sg721 contract is a wrapper contract that has a set of optional extensions on top of cw721-base."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Sg721 Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={1} data={sg721LinkTabs} />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 p-4 space-x-8">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<AddressInput {...contractState} />
|
||||||
|
<FormControl htmlId="contract-query-type" subtitle="Type of query to be dispatched" title="Query Type">
|
||||||
|
<select
|
||||||
|
className={clsx(
|
||||||
|
'bg-white/10 rounded border-2 border-white/20 form-select',
|
||||||
|
'placeholder:text-white/50',
|
||||||
|
'focus:ring focus:ring-plumbus-20',
|
||||||
|
)}
|
||||||
|
id="contract-query-type"
|
||||||
|
name="query-type"
|
||||||
|
onChange={(e) => setType(e.target.value as QueryType)}
|
||||||
|
>
|
||||||
|
{QUERY_LIST.map(({ id, name }) => (
|
||||||
|
<option key={`query-${id}`} value={id}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</FormControl>
|
||||||
|
<Conditional test={addressVisible}>
|
||||||
|
<AddressInput {...addressState} />
|
||||||
|
</Conditional>
|
||||||
|
<Conditional test={tokenIdVisible}>
|
||||||
|
<TextInput {...tokenIdState} />
|
||||||
|
</Conditional>
|
||||||
|
</div>
|
||||||
|
<JsonPreview content={contractAddress ? { type, response } : null} title="Query Response" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(Sg721QueryPage, { center: false })
|
162
pages/contracts/whitelist/execute.tsx
Normal file
162
pages/contracts/whitelist/execute.tsx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import { Button } from 'components/Button'
|
||||||
|
import { Conditional } from 'components/Conditional'
|
||||||
|
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||||
|
import { ExecuteCombobox } from 'components/contracts/whitelist/ExecuteCombobox'
|
||||||
|
import { useExecuteComboboxState } from 'components/contracts/whitelist/ExecuteCombobox.hooks'
|
||||||
|
import { FormControl } from 'components/FormControl'
|
||||||
|
import { AddressList } from 'components/forms/AddressList'
|
||||||
|
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||||
|
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 { whitelistLinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { TransactionHash } from 'components/TransactionHash'
|
||||||
|
import { useContracts } from 'contexts/contracts'
|
||||||
|
import type { DispatchExecuteArgs } from 'contracts/whitelist/messages/execute'
|
||||||
|
import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/whitelist/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 WhitelistExecutePage: NextPage = () => {
|
||||||
|
const { whitelist: contract } = useContracts()
|
||||||
|
const [lastTx, setLastTx] = useState('')
|
||||||
|
|
||||||
|
const comboboxState = useExecuteComboboxState()
|
||||||
|
const type = comboboxState.value?.id
|
||||||
|
|
||||||
|
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||||
|
|
||||||
|
const addressListState = useAddressListState()
|
||||||
|
|
||||||
|
const contractState = useInputState({
|
||||||
|
id: 'contract-address',
|
||||||
|
name: 'contract-address',
|
||||||
|
title: 'Whitelist Address',
|
||||||
|
subtitle: 'Address of the Whitelist contract',
|
||||||
|
})
|
||||||
|
const contractAddress = contractState.value
|
||||||
|
|
||||||
|
const limitState = useNumberInputState({
|
||||||
|
id: 'limit',
|
||||||
|
name: 'limit',
|
||||||
|
title: 'Limit',
|
||||||
|
subtitle: 'Limit value',
|
||||||
|
placeholder: '5',
|
||||||
|
})
|
||||||
|
|
||||||
|
const showLimitState = isEitherType(type, ['update_per_address_limit', 'increase_member_limit'])
|
||||||
|
const showTimestamp = isEitherType(type, ['update_start_time', 'update_end_time'])
|
||||||
|
const showMemberList = isEitherType(type, ['add_members', 'remove_members'])
|
||||||
|
|
||||||
|
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
||||||
|
const payload: DispatchExecuteArgs = {
|
||||||
|
contract: contractState.value,
|
||||||
|
messages,
|
||||||
|
type,
|
||||||
|
limit: limitState.value,
|
||||||
|
timestamp: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||||
|
members: addressListState.values.map((a) => a.address),
|
||||||
|
}
|
||||||
|
const { isLoading, mutate } = useMutation(
|
||||||
|
async (event: FormEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!type) {
|
||||||
|
throw new Error('Please select message type!')
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<section className="py-6 px-12 space-y-4">
|
||||||
|
<NextSeo title="Execute Whitelist Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Whitelist contract manages the whitelisted addresses for the collection."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Whitelist Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={3} data={whitelistLinkTabs} />
|
||||||
|
|
||||||
|
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<AddressInput {...contractState} />
|
||||||
|
<ExecuteCombobox {...comboboxState} />
|
||||||
|
<Conditional test={showLimitState}>
|
||||||
|
<NumberInput {...limitState} />
|
||||||
|
</Conditional>
|
||||||
|
<Conditional test={showTimestamp}>
|
||||||
|
<FormControl
|
||||||
|
htmlId="timestamp"
|
||||||
|
isRequired
|
||||||
|
subtitle={`${type === 'update_start_time' ? 'Start' : 'End'} time for the minting`}
|
||||||
|
title={`${type === 'update_start_time' ? 'Start' : 'End'} Time`}
|
||||||
|
>
|
||||||
|
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||||
|
</FormControl>
|
||||||
|
</Conditional>
|
||||||
|
<Conditional test={showMemberList}>
|
||||||
|
<AddressList
|
||||||
|
entries={addressListState.entries}
|
||||||
|
isRequired
|
||||||
|
onAdd={addressListState.add}
|
||||||
|
onChange={addressListState.update}
|
||||||
|
onRemove={addressListState.remove}
|
||||||
|
subtitle="Enter the member addresses"
|
||||||
|
title="Addresses"
|
||||||
|
/>
|
||||||
|
</Conditional>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="relative">
|
||||||
|
<Button className="absolute top-0 right-0" isLoading={isLoading} rightIcon={<FaArrowRight />} type="submit">
|
||||||
|
Execute
|
||||||
|
</Button>
|
||||||
|
<FormControl subtitle="View execution transaction hash" title="Transaction Hash">
|
||||||
|
<TransactionHash hash={lastTx} />
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<FormControl subtitle="View current message to be sent" title="Payload Preview">
|
||||||
|
<JsonPreview content={previewExecutePayload(payload)} isCopyable />
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(WhitelistExecutePage, { center: false })
|
1
pages/contracts/whitelist/index.tsx
Normal file
1
pages/contracts/whitelist/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './instantiate'
|
153
pages/contracts/whitelist/instantiate.tsx
Normal file
153
pages/contracts/whitelist/instantiate.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
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 { AddressList } from 'components/forms/AddressList'
|
||||||
|
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||||
|
import { NumberInput } from 'components/forms/FormInput'
|
||||||
|
import { useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||||
|
import { InputDateTime } from 'components/InputDateTime'
|
||||||
|
import { JsonPreview } from 'components/JsonPreview'
|
||||||
|
import { LinkTabs } from 'components/LinkTabs'
|
||||||
|
import { whitelistLinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { useContracts } from 'contexts/contracts'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import type { InstantiateResponse } from 'contracts/sg721'
|
||||||
|
import type { NextPage } from 'next'
|
||||||
|
import { NextSeo } from 'next-seo'
|
||||||
|
import { type FormEvent, useState } from 'react'
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import { FaAsterisk } from 'react-icons/fa'
|
||||||
|
import { useMutation } from 'react-query'
|
||||||
|
import { WHITELIST_CODE_ID } from 'utils/constants'
|
||||||
|
import { withMetadata } from 'utils/layout'
|
||||||
|
import { links } from 'utils/links'
|
||||||
|
|
||||||
|
const Sg721InstantiatePage: NextPage = () => {
|
||||||
|
const wallet = useWallet()
|
||||||
|
const { whitelist: contract } = useContracts()
|
||||||
|
|
||||||
|
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||||
|
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||||
|
|
||||||
|
const addressListState = useAddressListState()
|
||||||
|
|
||||||
|
const unitPriceState = useNumberInputState({
|
||||||
|
id: 'unit-price',
|
||||||
|
name: 'unitPrice',
|
||||||
|
title: 'Unit Price',
|
||||||
|
subtitle: 'Price of each tokens in collection',
|
||||||
|
placeholder: '500',
|
||||||
|
})
|
||||||
|
|
||||||
|
const memberLimitState = useNumberInputState({
|
||||||
|
id: 'member-limit',
|
||||||
|
name: 'memberLimit',
|
||||||
|
title: 'Member Limit',
|
||||||
|
subtitle: 'Limit of the whitelisted members',
|
||||||
|
placeholder: '1000',
|
||||||
|
})
|
||||||
|
|
||||||
|
const perAddressLimitState = useNumberInputState({
|
||||||
|
id: 'per-address-limit',
|
||||||
|
name: 'perAddressLimit',
|
||||||
|
title: 'Per Address Limit',
|
||||||
|
subtitle: 'Limit of tokens per address',
|
||||||
|
placeholder: '5',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data, isLoading, mutate } = useMutation(
|
||||||
|
async (event: FormEvent): Promise<InstantiateResponse | null> => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!contract) {
|
||||||
|
throw new Error('Smart contract connection failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startDate) {
|
||||||
|
throw new Error('Start date is required')
|
||||||
|
}
|
||||||
|
if (!endDate) {
|
||||||
|
throw new Error('End date is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = {
|
||||||
|
members: addressListState.values.map((a) => a.address),
|
||||||
|
start_time: (startDate.getTime() * 1_000_000).toString(),
|
||||||
|
end_time: (endDate.getTime() * 1_000_000).toString(),
|
||||||
|
unit_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
|
||||||
|
per_address_limit: perAddressLimitState.value,
|
||||||
|
member_limit: memberLimitState.value,
|
||||||
|
}
|
||||||
|
return toast.promise(
|
||||||
|
contract.instantiate(WHITELIST_CODE_ID, msg, 'Stargaze Whitelist Contract', wallet.address),
|
||||||
|
{
|
||||||
|
loading: 'Instantiating contract...',
|
||||||
|
error: 'Instantiation failed!',
|
||||||
|
success: 'Instantiation success!',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(String(error))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||||
|
<NextSeo title="Instantiate Whitelist Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Whitelist contract manages the whitelisted addresses for the collection."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Whitelist Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={0} data={whitelistLinkTabs} />
|
||||||
|
|
||||||
|
<Conditional test={Boolean(data)}>
|
||||||
|
<Alert type="info">
|
||||||
|
<b>Instantiate success!</b> Here is the transaction result containing the contract address and the transaction
|
||||||
|
hash.
|
||||||
|
</Alert>
|
||||||
|
<JsonPreview content={data} title="Transaction Result" />
|
||||||
|
<br />
|
||||||
|
</Conditional>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about your whitelisted addresses" title="Whitelist Details">
|
||||||
|
<AddressList
|
||||||
|
entries={addressListState.entries}
|
||||||
|
isRequired
|
||||||
|
onAdd={addressListState.add}
|
||||||
|
onChange={addressListState.update}
|
||||||
|
onRemove={addressListState.remove}
|
||||||
|
subtitle="Enter the members you want in your contract"
|
||||||
|
title="Members"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
|
||||||
|
<NumberInput isRequired {...unitPriceState} />
|
||||||
|
<NumberInput isRequired {...memberLimitState} />
|
||||||
|
<NumberInput isRequired {...perAddressLimitState} />
|
||||||
|
<FormControl htmlId="start-date" isRequired subtitle="Start time for the minting" title="Start Time">
|
||||||
|
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl htmlId="end-date" isRequired subtitle="End time for the minting" title="End Time">
|
||||||
|
<InputDateTime minDate={new Date()} onChange={(date) => setEndDate(date)} value={endDate} />
|
||||||
|
</FormControl>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<div className="flex items-center p-4">
|
||||||
|
<div className="flex-grow" />
|
||||||
|
<Button isLoading={isLoading} isWide rightIcon={<FaAsterisk />} type="submit">
|
||||||
|
Instantiate Contract
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(Sg721InstantiatePage, { center: false })
|
122
pages/contracts/whitelist/query.tsx
Normal file
122
pages/contracts/whitelist/query.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import { Conditional } from 'components/Conditional'
|
||||||
|
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 { whitelistLinkTabs } from 'components/LinkTabs.data'
|
||||||
|
import { useContracts } from 'contexts/contracts'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import type { QueryType } from 'contracts/whitelist/messages/query'
|
||||||
|
import { dispatchQuery, QUERY_LIST } from 'contracts/whitelist/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 WhitelistQueryPage: NextPage = () => {
|
||||||
|
const { whitelist: contract } = useContracts()
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
|
const contractState = useInputState({
|
||||||
|
id: 'contract-address',
|
||||||
|
name: 'contract-address',
|
||||||
|
title: 'Whitelist Address',
|
||||||
|
subtitle: 'Address of the Whitelist 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<QueryType>('has_started')
|
||||||
|
|
||||||
|
const addressVisible = type === 'has_member'
|
||||||
|
|
||||||
|
const { data: response } = useQuery(
|
||||||
|
[contractAddress, type, contract, wallet, address] as const,
|
||||||
|
async ({ queryKey }) => {
|
||||||
|
const [_contractAddress, _type, _contract, _wallet, _address] = queryKey
|
||||||
|
const messages = contract?.use(contractAddress)
|
||||||
|
const result = await dispatchQuery({
|
||||||
|
messages,
|
||||||
|
type,
|
||||||
|
address: _address,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
{
|
||||||
|
placeholderData: null,
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
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 (
|
||||||
|
<section className="py-6 px-12 space-y-4">
|
||||||
|
<NextSeo title="Query Whitelist Contract" />
|
||||||
|
<ContractPageHeader
|
||||||
|
description="Whitelist contract manages the whitelisted addresses for the collection."
|
||||||
|
link={links.Documentation}
|
||||||
|
title="Whitelist Contract"
|
||||||
|
/>
|
||||||
|
<LinkTabs activeIndex={1} data={whitelistLinkTabs} />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 p-4 space-x-8">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<AddressInput {...contractState} />
|
||||||
|
<FormControl htmlId="contract-query-type" subtitle="Type of query to be dispatched" title="Query Type">
|
||||||
|
<select
|
||||||
|
className={clsx(
|
||||||
|
'bg-white/10 rounded border-2 border-white/20 form-select',
|
||||||
|
'placeholder:text-white/50',
|
||||||
|
'focus:ring focus:ring-plumbus-20',
|
||||||
|
)}
|
||||||
|
id="contract-query-type"
|
||||||
|
name="query-type"
|
||||||
|
onChange={(e) => setType(e.target.value as QueryType)}
|
||||||
|
>
|
||||||
|
{QUERY_LIST.map(({ id, name }) => (
|
||||||
|
<option key={`query-${id}`} value={id}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</FormControl>
|
||||||
|
<Conditional test={addressVisible}>
|
||||||
|
<AddressInput {...addressState} />
|
||||||
|
</Conditional>
|
||||||
|
</div>
|
||||||
|
<JsonPreview content={contractAddress ? { type, response } : null} title="Query Response" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(WhitelistQueryPage, { center: false })
|
@ -1,13 +1,9 @@
|
|||||||
export const CW721_BASE_CODE_ID = parseInt(process.env.NEXT_PUBLIC_CW721_BASE_CODE_ID, 10)
|
export const SG721_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_CODE_ID, 10)
|
||||||
|
export const MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_MINTER_CODE_ID, 10)
|
||||||
|
export const WHITELIST_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_CODE_ID, 10)
|
||||||
|
|
||||||
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK
|
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK
|
||||||
|
|
||||||
export const S3_ENDPOINT = process.env.NEXT_PUBLIC_S3_ENDPOINT
|
|
||||||
export const S3_REGION = process.env.NEXT_PUBLIC_S3_REGION
|
|
||||||
export const S3_KEY = process.env.NEXT_PUBLIC_S3_KEY
|
|
||||||
export const S3_SECRET = process.env.NEXT_PUBLIC_S3_SECRET
|
|
||||||
export const S3_BUCKET = process.env.NEXT_PUBLIC_S3_BUCKET
|
|
||||||
|
|
||||||
export const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL
|
export const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL
|
||||||
|
|
||||||
export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL
|
export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL
|
||||||
|
@ -13,16 +13,13 @@ export const links = {
|
|||||||
Telegram: `https://t.me/joinchat/ZQ95YmIn3AI0ODFh`,
|
Telegram: `https://t.me/joinchat/ZQ95YmIn3AI0ODFh`,
|
||||||
Twitter: `https://twitter.com/stargazezone`,
|
Twitter: `https://twitter.com/stargazezone`,
|
||||||
Explorer: BLOCK_EXPLORER_URL,
|
Explorer: BLOCK_EXPLORER_URL,
|
||||||
|
Documentation: 'https://docs.stargaze.zone/guides/readme',
|
||||||
// reference links
|
|
||||||
'Docs Create Collection': ``,
|
|
||||||
'Docs CW721 Base': ``,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const footerLinks = [
|
export const footerLinks = [
|
||||||
{ text: 'Block Explorer', href: links.Explorer },
|
{ text: 'Block Explorer', href: links.Explorer },
|
||||||
{ text: 'Documentation', href: links.Docs },
|
{ text: 'Documentation', href: links.Docs },
|
||||||
{ text: 'Submit an issue', href: `${links.GitHub}/issues/new/choose` },
|
{ text: 'Submit an issue', href: `${links.GitHub}/issues/new` },
|
||||||
{ text: 'Powered by Stargaze', href: links.Stargaze },
|
{ text: 'Powered by Stargaze', href: links.Stargaze },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user