Added contract helpers for minter, sg721 & whitelist
This commit is contained in:
parent
4dde6db215
commit
a0affdaa4d
@ -1,6 +1,6 @@
|
|||||||
APP_VERSION=0.1.0
|
APP_VERSION=0.1.0
|
||||||
|
|
||||||
NEXT_PUBLIC_CW721_BASE_CODE_ID=5
|
NEXT_PUBLIC_SG721_CODE_ID=5
|
||||||
|
|
||||||
NEXT_PUBLIC_API_URL=https://
|
NEXT_PUBLIC_API_URL=https://
|
||||||
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze
|
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze
|
||||||
|
@ -33,3 +33,5 @@ export function Anchor({ children, external, href = '', rel = '', ...rest }: Anc
|
|||||||
function trimHttp(str: string) {
|
function trimHttp(str: string) {
|
||||||
return str.replace(/https?:\/\//, '')
|
return str.replace(/https?:\/\//, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Anchor
|
||||||
|
@ -34,3 +34,5 @@ export const AnchorButton = (props: AnchorButtonProps) => {
|
|||||||
</Anchor>
|
</Anchor>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AnchorButton
|
||||||
|
@ -22,8 +22,8 @@ export const Button = (props: ButtonProps) => {
|
|||||||
'group flex items-center py-2 space-x-2 font-bold focus:ring',
|
'group flex items-center py-2 space-x-2 font-bold focus:ring',
|
||||||
isWide ? 'px-8' : 'px-4',
|
isWide ? 'px-8' : 'px-4',
|
||||||
{
|
{
|
||||||
'bg-plumbus-60 hover:bg-plumbus-50 rounded ': variant === 'solid',
|
'bg-plumbus hover:bg-plumbus-light rounded ': variant === 'solid',
|
||||||
'bg-plumbus/10 hover:bg-plumbus/20 rounded border border-plumbus': variant === 'outline',
|
'bg-plumbus hover:bg-plumbus-light rounded border border-plumbus-dark': variant === 'outline',
|
||||||
'opacity-50 cursor-not-allowed pointer-events-none': isDisabled,
|
'opacity-50 cursor-not-allowed pointer-events-none': isDisabled,
|
||||||
'animate-pulse cursor-wait pointer-events-none': isLoading,
|
'animate-pulse cursor-wait pointer-events-none': isLoading,
|
||||||
},
|
},
|
||||||
@ -39,3 +39,4 @@ export const Button = (props: ButtonProps) => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
export default Button
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import type { LinkTabProps } from './LinkTab'
|
import type { LinkTabProps } from './LinkTab'
|
||||||
|
|
||||||
export const cw721BaseLinkTabs: LinkTabProps[] = [
|
export const sg721LinkTabs: LinkTabProps[] = [
|
||||||
{
|
{
|
||||||
title: 'Instantiate',
|
title: 'Instantiate',
|
||||||
description: `Create a new CW721 Base contract`,
|
description: `Create a new SG721 contract`,
|
||||||
href: '/contracts/cw721/base/instantiate',
|
href: '/contracts/sg721/instantiate',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Query',
|
title: 'Query',
|
||||||
description: `Dispatch queries with your CW721 Base contract`,
|
description: `Dispatch queries with your SG721 contract`,
|
||||||
href: '/contracts/cw721/base/query',
|
href: '/contracts/sg721/query',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Execute',
|
title: 'Execute',
|
||||||
description: `Execute CW721 Base contract actions`,
|
description: `Execute SG721 contract actions`,
|
||||||
href: '/contracts/cw721/base/execute',
|
href: '/contracts/sg721/execute',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -9,8 +9,7 @@ import { SidebarLayout } from './SidebarLayout'
|
|||||||
import { WalletLoader } from './WalletLoader'
|
import { WalletLoader } from './WalletLoader'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ text: 'Create Collection', href: `/collection/create` },
|
{ text: 'Create Collection', href: `/collection/` },
|
||||||
{ text: 'CW721 Base', href: `/contracts/cw721/base` },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import type { ExecuteListItem } from 'utils/contracts/cw721/base/execute'
|
|
||||||
|
|
||||||
export const useExecuteComboboxState = () => {
|
|
||||||
const [value, setValue] = useState<ExecuteListItem | null>(null)
|
|
||||||
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
import { Combobox, Transition } from '@headlessui/react'
|
|
||||||
import clsx from 'clsx'
|
|
||||||
import { FormControl } from 'components/FormControl'
|
|
||||||
import { matchSorter } from 'match-sorter'
|
|
||||||
import { Fragment, useState } from 'react'
|
|
||||||
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
|
|
||||||
import type { ExecuteListItem } from 'utils/contracts/cw721/base/execute'
|
|
||||||
import { EXECUTE_LIST } from 'utils/contracts/cw721/base/execute'
|
|
||||||
|
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
56
contexts/collection.ts
Normal file
56
contexts/collection.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import create from 'zustand'
|
||||||
|
|
||||||
|
export const useCollectionStore = create(() => ({
|
||||||
|
name: 'Example',
|
||||||
|
base_token_uri:
|
||||||
|
'ipfs://bafkreiei4e437w3hqf5vy4yqroukx22fag7akbajygettvylzqg6shvkfq',
|
||||||
|
description: 'Lorem',
|
||||||
|
image:
|
||||||
|
'ipfs://bafybeigi3bwpvyvsmnbj46ra4hyffcxdeaj6ntfk5jpic5mx27x6ih2qvq/images/1.png',
|
||||||
|
num_tokens: 10,
|
||||||
|
per_address_limit: 1,
|
||||||
|
start_time: '1650982532000000000',
|
||||||
|
symbol: 'EXP',
|
||||||
|
unit_price: 50,
|
||||||
|
whitelist: '',
|
||||||
|
}))
|
||||||
|
|
||||||
|
export const setName = (value: string) => {
|
||||||
|
useCollectionStore.setState({ name: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setBaseTokenUri = (value: string) => {
|
||||||
|
useCollectionStore.setState({ base_token_uri: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setDescription = (value: string) => {
|
||||||
|
useCollectionStore.setState({ description: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setImage = (value: string) => {
|
||||||
|
useCollectionStore.setState({ image: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setNumTokens = (value: number) => {
|
||||||
|
useCollectionStore.setState({ num_tokens: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setPerAddressLimit = (value: number) => {
|
||||||
|
useCollectionStore.setState({ per_address_limit: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setStartTime = (value: string) => {
|
||||||
|
useCollectionStore.setState({ start_time: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setSymbol = (value: string) => {
|
||||||
|
useCollectionStore.setState({ symbol: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setUnitPrice = (value: number) => {
|
||||||
|
useCollectionStore.setState({ unit_price: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setWhitelist = (value: string) => {
|
||||||
|
useCollectionStore.setState({ whitelist: value })
|
||||||
|
}
|
@ -1,22 +1,30 @@
|
|||||||
import type { UseCW721BaseContractProps } from 'contracts/cw721/base'
|
import { useMinterContract, UseMinterContractProps } from 'contracts/minter'
|
||||||
import { useCW721BaseContract } from 'contracts/cw721/base'
|
import { useSG721Contract, UseSG721ContractProps } from 'contracts/sg721'
|
||||||
import type { ReactNode } from 'react'
|
import {
|
||||||
import { useEffect } from 'react'
|
useWhiteListContract,
|
||||||
import type { State } from 'zustand'
|
useWhiteListContractProps,
|
||||||
import create from 'zustand'
|
} from 'contracts/whitelist'
|
||||||
|
|
||||||
|
import { Fragment, ReactNode, useEffect, VFC } from 'react'
|
||||||
|
import create, { State } from 'zustand'
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contracts store type definitions
|
* Contracts store type definitions
|
||||||
*/
|
*/
|
||||||
export interface ContractsStore extends State {
|
export interface ContractsStore extends State {
|
||||||
cw721Base: UseCW721BaseContractProps | null
|
sg721: UseSG721ContractProps | null
|
||||||
|
minter: UseMinterContractProps | null
|
||||||
|
whitelist: useWhiteListContractProps | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contracts store default values as a separate variable for reusability
|
* Contracts store default values as a separate variable for reusability
|
||||||
*/
|
*/
|
||||||
export const defaultValues: ContractsStore = {
|
export const defaultValues: ContractsStore = {
|
||||||
cw721Base: null,
|
sg721: null,
|
||||||
|
minter: null,
|
||||||
|
whitelist: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,28 +40,34 @@ 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)
|
* Contracts store subscriptions (side effects)
|
||||||
*
|
*
|
||||||
* TODO: refactor all contract logics to zustand store
|
* @todo refactor all contract logics to zustand store
|
||||||
*/
|
*/
|
||||||
const ContractsSubscription = () => {
|
const ContractsSubscription: VFC = () => {
|
||||||
const cw721Base = useCW721BaseContract()
|
const sg721 = useSG721Contract()
|
||||||
|
const minter = useMinterContract()
|
||||||
|
const whitelist = useWhiteListContract()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
useContracts.setState({
|
useContracts.setState({
|
||||||
cw721Base,
|
sg721,
|
||||||
|
minter,
|
||||||
|
whitelist,
|
||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
cw721Base,
|
sg721,
|
||||||
//
|
minter,
|
||||||
|
whitelist,
|
||||||
])
|
])
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@ -1,458 +0,0 @@
|
|||||||
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
|
||||||
import { toBase64, toUtf8 } from '@cosmjs/encoding'
|
|
||||||
import type { Coin } from '@cosmjs/proto-signing'
|
|
||||||
// import { isDeliverTxFailure } from '@cosmjs/stargate'
|
|
||||||
// import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
|
|
||||||
// import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
|
|
||||||
import { getExecuteFee } from 'utils/fees'
|
|
||||||
|
|
||||||
const jsonToBinary = (json: Record<string, unknown>): string => {
|
|
||||||
return toBase64(toUtf8(JSON.stringify(json)))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Expiration = { at_height: number } | { at_time: string } | { never: object }
|
|
||||||
|
|
||||||
// interface ExecuteWithSignDataResponse {
|
|
||||||
// signed: TxRaw
|
|
||||||
// txHash: string
|
|
||||||
// }
|
|
||||||
|
|
||||||
export interface InstantiateResponse {
|
|
||||||
readonly contractAddress: string
|
|
||||||
readonly transactionHash: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CW721BaseInstance {
|
|
||||||
readonly contractAddress: string
|
|
||||||
|
|
||||||
// Queries
|
|
||||||
ownerOf: (tokenId: string, includeExpired?: boolean) => Promise<any>
|
|
||||||
approval: (tokenId: string, spender: string, includeExpired?: boolean) => Promise<any>
|
|
||||||
approvals: (tokenId: string, includeExpired?: boolean) => Promise<any>
|
|
||||||
allOperators: (owner: string, includeExpired?: boolean, startAfter?: string, limit?: number) => Promise<any>
|
|
||||||
numTokens: () => Promise<any>
|
|
||||||
contractInfo: () => Promise<any>
|
|
||||||
nftInfo: (tokenId: string) => Promise<any>
|
|
||||||
allNftInfo: (tokenId: string, includeExpired?: boolean) => Promise<any>
|
|
||||||
tokens: (owner: string, startAfter?: string, limit?: number) => Promise<any>
|
|
||||||
allTokens: (startAfter?: string, limit?: number) => Promise<any>
|
|
||||||
minter: () => Promise<any>
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
transferNft: (recipient: string, tokenId: string) => Promise<string>
|
|
||||||
sendNft: (contract: string, tokenId: string, msg: Record<string, unknown>) => Promise<string>
|
|
||||||
approve: (spender: string, tokenId: string, expires?: Expiration) => Promise<string>
|
|
||||||
revoke: (spender: string, tokenId: string) => Promise<string>
|
|
||||||
approveAll: (operator: string, expires?: Expiration) => Promise<string>
|
|
||||||
revokeAll: (operator: string) => Promise<string>
|
|
||||||
mint: (tokenId: string, owner: string, tokenUri?: string) => Promise<string>
|
|
||||||
burn: (tokenId: string) => Promise<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CW721BaseMessages {
|
|
||||||
transferNft: (contractAddress: string, recipient: string, tokenId: string) => TransferNftMessage
|
|
||||||
sendNft: (contractAddress: string, contract: string, tokenId: string, msg: Record<string, unknown>) => SendNFtMessage
|
|
||||||
approve: (contractAddress: string, spender: string, tokenId: string, expires?: Expiration) => ApproveMessage
|
|
||||||
revoke: (contractAddress: string, spender: string, tokenId: string) => RevokeMessage
|
|
||||||
approveAll: (contractAddress: string, operator: string, expires?: Expiration) => ApproveAllMessage
|
|
||||||
revokeAll: (contractAddress: string, operator: string) => RevokeAllMessage
|
|
||||||
mint: (contractAddress: string, tokenId: string, owner: string, tokenUri?: string) => MintMessage
|
|
||||||
burn: (contractAddress: string, 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 CW721BaseContract {
|
|
||||||
instantiate: (
|
|
||||||
senderAddress: string,
|
|
||||||
codeId: number,
|
|
||||||
initMsg: Record<string, unknown>,
|
|
||||||
label: string,
|
|
||||||
admin?: string,
|
|
||||||
) => Promise<InstantiateResponse>
|
|
||||||
|
|
||||||
use: (contractAddress: string) => CW721BaseInstance
|
|
||||||
|
|
||||||
messages: () => CW721BaseMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CW721Base = (client: SigningCosmWasmClient, txSigner: string): CW721BaseContract => {
|
|
||||||
const fee = getExecuteFee()
|
|
||||||
|
|
||||||
const use = (contractAddress: string): CW721BaseInstance => {
|
|
||||||
const ownerOf = async (tokenId: string, includeExpired?: boolean) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
owner_of: { token_id: tokenId, include_expired: includeExpired },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const approval = async (tokenId: string, spender: string, includeExpired?: boolean) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
approval: { token_id: tokenId, spender, include_expired: includeExpired },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const approvals = async (tokenId: string, includeExpired?: boolean) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
approvals: { token_id: tokenId, include_expired: includeExpired },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const allOperators = async (owner: string, includeExpired?: boolean, startAfter?: string, limit?: number) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
all_operators: { owner, include_expired: includeExpired, start_after: startAfter, limit },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const numTokens = async () => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
num_tokens: {},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const contractInfo = async () => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
contract_info: {},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const nftInfo = async (tokenId: string) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
nft_info: { token_id: tokenId },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const allNftInfo = async (tokenId: string, includeExpired?: boolean) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
all_nft_info: { token_id: tokenId, include_expired: includeExpired },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokens = async (owner: string, startAfter?: string, limit?: number) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
tokens: { owner, start_after: startAfter, limit },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const allTokens = async (startAfter?: string, limit?: number) => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
all_tokens: { start_after: startAfter, limit },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const minter = async () => {
|
|
||||||
return client.queryContractSmart(contractAddress, {
|
|
||||||
minter: {},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const transferNft = async (recipient: string, tokenId: string): Promise<string> => {
|
|
||||||
const result = await client.execute(
|
|
||||||
txSigner,
|
|
||||||
contractAddress,
|
|
||||||
{ transfer_nft: { recipient, token_id: tokenId } },
|
|
||||||
fee,
|
|
||||||
)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendNft = async (contract: string, tokenId: string, msg: Record<string, unknown>): Promise<string> => {
|
|
||||||
const result = await client.execute(
|
|
||||||
txSigner,
|
|
||||||
contractAddress,
|
|
||||||
{ send_nft: { contract, token_id: tokenId, msg: jsonToBinary(msg) } },
|
|
||||||
fee,
|
|
||||||
)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const approve = async (spender: string, tokenId: string, expires?: Expiration): Promise<string> => {
|
|
||||||
const result = await client.execute(
|
|
||||||
txSigner,
|
|
||||||
contractAddress,
|
|
||||||
{ approve: { spender, token_id: tokenId, expires } },
|
|
||||||
fee,
|
|
||||||
)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const revoke = async (spender: string, tokenId: string): Promise<string> => {
|
|
||||||
const result = await client.execute(txSigner, contractAddress, { revoke: { spender, token_id: tokenId } }, fee)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const approveAll = async (operator: string, expires?: Expiration): Promise<string> => {
|
|
||||||
const result = await client.execute(txSigner, contractAddress, { approve_all: { operator, expires } }, fee)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const revokeAll = async (operator: string): Promise<string> => {
|
|
||||||
const result = await client.execute(txSigner, contractAddress, { revoke_all: { operator } }, fee)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const mint = async (tokenId: string, owner: string, tokenUri?: string): Promise<string> => {
|
|
||||||
const result = await client.execute(
|
|
||||||
txSigner,
|
|
||||||
contractAddress,
|
|
||||||
{ mint: { token_id: tokenId, owner, token_uri: tokenUri } },
|
|
||||||
fee,
|
|
||||||
)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
const burn = async (tokenId: string): Promise<string> => {
|
|
||||||
const result = await client.execute(txSigner, contractAddress, { burn: { token_id: tokenId } }, fee)
|
|
||||||
return result.transactionHash
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
contractAddress,
|
|
||||||
ownerOf,
|
|
||||||
approval,
|
|
||||||
approvals,
|
|
||||||
allOperators,
|
|
||||||
numTokens,
|
|
||||||
contractInfo,
|
|
||||||
nftInfo,
|
|
||||||
allNftInfo,
|
|
||||||
tokens,
|
|
||||||
allTokens,
|
|
||||||
minter,
|
|
||||||
transferNft,
|
|
||||||
sendNft,
|
|
||||||
approve,
|
|
||||||
revoke,
|
|
||||||
approveAll,
|
|
||||||
revokeAll,
|
|
||||||
mint,
|
|
||||||
burn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const instantiate = async (
|
|
||||||
senderAddress: string,
|
|
||||||
codeId: number,
|
|
||||||
initMsg: Record<string, unknown>,
|
|
||||||
label: string,
|
|
||||||
admin?: string,
|
|
||||||
): Promise<InstantiateResponse> => {
|
|
||||||
const result = await client.instantiate(senderAddress, codeId, initMsg, label, 'auto', {
|
|
||||||
memo: '',
|
|
||||||
admin,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
contractAddress: result.contractAddress,
|
|
||||||
transactionHash: result.transactionHash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = () => {
|
|
||||||
const transferNft = (contractAddress: string, recipient: string, tokenId: string): TransferNftMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
transfer_nft: { recipient, token_id: tokenId },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendNft = (
|
|
||||||
contractAddress: string,
|
|
||||||
contract: string,
|
|
||||||
tokenId: string,
|
|
||||||
msg: Record<string, unknown>,
|
|
||||||
): SendNFtMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
send_nft: { contract, token_id: tokenId, msg },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const approve = (
|
|
||||||
contractAddress: string,
|
|
||||||
spender: string,
|
|
||||||
tokenId: string,
|
|
||||||
expires?: Expiration,
|
|
||||||
): ApproveMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
approve: { spender, token_id: tokenId, expires },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const revoke = (contractAddress: string, spender: string, tokenId: string): RevokeMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
revoke: { spender, token_id: tokenId },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const approveAll = (contractAddress: string, operator: string, expires?: Expiration): ApproveAllMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
approve_all: { operator, expires },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const revokeAll = (contractAddress: string, operator: string): RevokeAllMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
revoke_all: { operator },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mint = (contractAddress: string, tokenId: string, owner: string, tokenUri?: string): MintMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
mint: { token_id: tokenId, owner, token_uri: tokenUri },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const burn = (contractAddress: string, tokenId: string): BurnMessage => {
|
|
||||||
return {
|
|
||||||
sender: txSigner,
|
|
||||||
contract: contractAddress,
|
|
||||||
msg: {
|
|
||||||
burn: { token_id: tokenId },
|
|
||||||
},
|
|
||||||
funds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
transferNft,
|
|
||||||
sendNft,
|
|
||||||
approve,
|
|
||||||
revoke,
|
|
||||||
approveAll,
|
|
||||||
revokeAll,
|
|
||||||
mint,
|
|
||||||
burn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { instantiate, use, messages }
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
import { useWallet } from 'contexts/wallet'
|
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import type { CW721BaseContract, CW721BaseInstance, CW721BaseMessages } from './contract'
|
|
||||||
import { CW721Base as initContract } from './contract'
|
|
||||||
|
|
||||||
interface InstantiateResponse {
|
|
||||||
readonly contractAddress: string
|
|
||||||
readonly transactionHash: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UseCW721BaseContractProps {
|
|
||||||
instantiate: (
|
|
||||||
codeId: number,
|
|
||||||
initMsg: Record<string, unknown>,
|
|
||||||
label: string,
|
|
||||||
admin?: string,
|
|
||||||
) => Promise<InstantiateResponse>
|
|
||||||
use: (customAddress: string) => CW721BaseInstance | undefined
|
|
||||||
updateContractAddress: (contractAddress: string) => void
|
|
||||||
messages: () => CW721BaseMessages | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCW721BaseContract(): UseCW721BaseContractProps {
|
|
||||||
const wallet = useWallet()
|
|
||||||
|
|
||||||
const [address, setAddress] = useState<string>('')
|
|
||||||
const [CW721Base, setCW721Base] = useState<CW721BaseContract>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setAddress(localStorage.getItem('contract_address') || '')
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const cw721BaseContract = initContract(wallet.getClient(), wallet.address)
|
|
||||||
setCW721Base(cw721BaseContract)
|
|
||||||
}, [wallet])
|
|
||||||
|
|
||||||
const updateContractAddress = (contractAddress: string) => {
|
|
||||||
setAddress(contractAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
const instantiate = useCallback(
|
|
||||||
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!CW721Base) {
|
|
||||||
reject(new Error('Contract is not initialized.'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
CW721Base.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[CW721Base, wallet],
|
|
||||||
)
|
|
||||||
|
|
||||||
const use = useCallback(
|
|
||||||
(customAddress = ''): CW721BaseInstance | undefined => {
|
|
||||||
return CW721Base?.use(address || customAddress)
|
|
||||||
},
|
|
||||||
[CW721Base, address],
|
|
||||||
)
|
|
||||||
|
|
||||||
const messages = useCallback((): CW721BaseMessages | undefined => {
|
|
||||||
return CW721Base?.messages()
|
|
||||||
}, [CW721Base])
|
|
||||||
|
|
||||||
return {
|
|
||||||
instantiate,
|
|
||||||
use,
|
|
||||||
updateContractAddress,
|
|
||||||
messages,
|
|
||||||
}
|
|
||||||
}
|
|
256
contracts/minter/contract.ts
Normal file
256
contracts/minter/contract.ts
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
|
import { Coin } from '@cosmjs/proto-signing'
|
||||||
|
import { logs } from '@cosmjs/stargate'
|
||||||
|
import { Timestamp } from '@stargazezone/types/contracts/minter/shared-types'
|
||||||
|
|
||||||
|
export interface InstantiateResponse {
|
||||||
|
readonly contractAddress: string
|
||||||
|
readonly transactionHash: string
|
||||||
|
readonly logs: readonly logs.Log[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RoyalityInfo = {
|
||||||
|
payment_address: string
|
||||||
|
share: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MinterInstance {
|
||||||
|
readonly contractAddress: string
|
||||||
|
|
||||||
|
//Query
|
||||||
|
getConfig: () => Promise<any>
|
||||||
|
getMintableNumTokens: () => Promise<any>
|
||||||
|
getStartTime: () => Promise<any>
|
||||||
|
getMintPrice: () => Promise<any>
|
||||||
|
getMintCount: (address: string) => Promise<any>
|
||||||
|
|
||||||
|
//Execute
|
||||||
|
mint: (senderAddress: string) => Promise<string>
|
||||||
|
setWhitelist: (senderAddress: string, whitelist: string) => Promise<string>
|
||||||
|
updateStartTime: (senderAddress: string, time: Timestamp) => Promise<string>
|
||||||
|
updatePerAddressLimit: (
|
||||||
|
senderAddress: string,
|
||||||
|
per_address_limit: number
|
||||||
|
) => Promise<string>
|
||||||
|
mintTo: (senderAddress: string, recipient: string) => Promise<string>
|
||||||
|
mintFor: (
|
||||||
|
senderAddress: string,
|
||||||
|
token_id: number,
|
||||||
|
recipient: string
|
||||||
|
) => Promise<string>
|
||||||
|
withdraw: (senderAddress: string) => Promise<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MinterContract {
|
||||||
|
instantiate: (
|
||||||
|
senderAddress: string,
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
admin?: string,
|
||||||
|
funds?: Coin[]
|
||||||
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
|
use: (contractAddress: string) => MinterInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
export const minter = (client: SigningCosmWasmClient): MinterContract => {
|
||||||
|
const use = (contractAddress: string): MinterInstance => {
|
||||||
|
//Query
|
||||||
|
const getConfig = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
config: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMintableNumTokens = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
mintable_num_tokens: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStartTime = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
start_time: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMintPrice = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
mint_price: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMintCount = async (address: string): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
mint_count: { address },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
//Execute
|
||||||
|
const mint = async (senderAddress: string): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
mint: {},
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const setWhitelist = async (
|
||||||
|
senderAddress: string,
|
||||||
|
whitelist: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
set_whitelist: { whitelist },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStartTime = async (
|
||||||
|
senderAddress: string,
|
||||||
|
time: Timestamp
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
update_start_time: { time },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePerAddressLimit = async (
|
||||||
|
senderAddress: string,
|
||||||
|
per_address_limit: number
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
update_per_address_limit: { per_address_limit },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const mintTo = async (
|
||||||
|
senderAddress: string,
|
||||||
|
recipient: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
mint_to: { recipient },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const mintFor = async (
|
||||||
|
senderAddress: string,
|
||||||
|
token_id: number,
|
||||||
|
recipient: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
mint_for: { token_id, recipient },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const withdraw = async (senderAddress: string): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
withdraw: {},
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
contractAddress,
|
||||||
|
getConfig,
|
||||||
|
getMintableNumTokens,
|
||||||
|
getStartTime,
|
||||||
|
getMintPrice,
|
||||||
|
getMintCount,
|
||||||
|
mint,
|
||||||
|
setWhitelist,
|
||||||
|
updateStartTime,
|
||||||
|
updatePerAddressLimit,
|
||||||
|
mintTo,
|
||||||
|
mintFor,
|
||||||
|
withdraw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instantiate = async (
|
||||||
|
senderAddress: string,
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
admin?: string,
|
||||||
|
funds?: Coin[]
|
||||||
|
): Promise<InstantiateResponse> => {
|
||||||
|
console.log(funds)
|
||||||
|
const result = await client.instantiate(
|
||||||
|
senderAddress,
|
||||||
|
codeId,
|
||||||
|
initMsg,
|
||||||
|
label,
|
||||||
|
'auto',
|
||||||
|
{
|
||||||
|
funds,
|
||||||
|
admin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
contractAddress: result.contractAddress,
|
||||||
|
transactionHash: result.transactionHash,
|
||||||
|
logs: result.logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { use, instantiate }
|
||||||
|
}
|
98
contracts/minter/useContract.ts
Normal file
98
contracts/minter/useContract.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { Coin } from '@cosmjs/proto-signing'
|
||||||
|
import { logs } from '@cosmjs/stargate'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
minter as initContract,
|
||||||
|
MinterContract,
|
||||||
|
MinterInstance,
|
||||||
|
} from './contract'
|
||||||
|
|
||||||
|
/*export interface InstantiateResponse {
|
||||||
|
/** The address of the newly instantiated contract *-/
|
||||||
|
readonly contractAddress: string
|
||||||
|
readonly logs: readonly logs.Log[]
|
||||||
|
/** Block height in which the transaction is included *-/
|
||||||
|
readonly height: number
|
||||||
|
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex *-/
|
||||||
|
readonly transactionHash: string
|
||||||
|
readonly gasWanted: number
|
||||||
|
readonly gasUsed: number
|
||||||
|
}*/
|
||||||
|
|
||||||
|
interface InstantiateResponse {
|
||||||
|
readonly contractAddress: string
|
||||||
|
readonly transactionHash: string
|
||||||
|
readonly logs: readonly logs.Log[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseMinterContractProps {
|
||||||
|
instantiate: (
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
admin?: string,
|
||||||
|
funds?: Coin[]
|
||||||
|
) => Promise<InstantiateResponse>
|
||||||
|
use: (customAddress: string) => MinterInstance | undefined
|
||||||
|
updateContractAddress: (contractAddress: string) => void
|
||||||
|
getContractAddress: () => string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMinterContract(): UseMinterContractProps {
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
|
const [address, setAddress] = useState<string>('')
|
||||||
|
const [minter, setMinter] = useState<MinterContract>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAddress(localStorage.getItem('contract_address') || '')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (wallet.initialized) {
|
||||||
|
const getMinterBaseInstance = async (): Promise<void> => {
|
||||||
|
const MinterBaseContract = initContract(wallet.getClient())
|
||||||
|
setMinter(MinterBaseContract)
|
||||||
|
}
|
||||||
|
|
||||||
|
getMinterBaseInstance()
|
||||||
|
}
|
||||||
|
}, [wallet])
|
||||||
|
|
||||||
|
const updateContractAddress = (contractAddress: string) => {
|
||||||
|
setAddress(contractAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
const instantiate = useCallback(
|
||||||
|
(codeId, initMsg, label, admin?, funds?): Promise<InstantiateResponse> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!minter) return reject('Contract is not initialized.')
|
||||||
|
minter
|
||||||
|
.instantiate(wallet.address, codeId, initMsg, label, admin, funds)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[minter, wallet]
|
||||||
|
)
|
||||||
|
|
||||||
|
const use = useCallback(
|
||||||
|
(customAddress = ''): MinterInstance | undefined => {
|
||||||
|
return minter?.use(address || customAddress)
|
||||||
|
},
|
||||||
|
[minter, address]
|
||||||
|
)
|
||||||
|
|
||||||
|
const getContractAddress = (): string | undefined => {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
instantiate,
|
||||||
|
use,
|
||||||
|
updateContractAddress,
|
||||||
|
getContractAddress,
|
||||||
|
}
|
||||||
|
}
|
434
contracts/sg721/contract.ts
Normal file
434
contracts/sg721/contract.ts
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
|
import { Coin } from '@cosmjs/stargate'
|
||||||
|
|
||||||
|
export interface InstantiateResponse {
|
||||||
|
readonly contractAddress: string
|
||||||
|
readonly transactionHash: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Expiration =
|
||||||
|
| { at_height: number }
|
||||||
|
| { at_time: string }
|
||||||
|
| { never: {} }
|
||||||
|
|
||||||
|
export interface SG721Instance {
|
||||||
|
readonly contractAddress: string
|
||||||
|
|
||||||
|
// queries
|
||||||
|
getOwnerOf: (
|
||||||
|
token_id: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
getApproval: (
|
||||||
|
token_id: string,
|
||||||
|
spender: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
getApprovals: (
|
||||||
|
token_id: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
getAllOperators: (
|
||||||
|
owner: string,
|
||||||
|
include_expired: boolean | null,
|
||||||
|
start_after: string | null,
|
||||||
|
limit: number | null
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
getNumTokens: () => Promise<any>
|
||||||
|
|
||||||
|
getContractInfo: () => Promise<any>
|
||||||
|
|
||||||
|
getNftInfo: (token_id: string) => Promise<any>
|
||||||
|
|
||||||
|
getAllNftInfo: (
|
||||||
|
token_id: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
getTokens: (
|
||||||
|
owner: string,
|
||||||
|
start_after: string | null,
|
||||||
|
limit: number | null
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
getAllTokens: (
|
||||||
|
start_after: string | null,
|
||||||
|
limit: number | null
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
getMinter: () => Promise<any>
|
||||||
|
|
||||||
|
getCollectionInfo: () => Promise<any>
|
||||||
|
|
||||||
|
//Execute
|
||||||
|
transferNft: (
|
||||||
|
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
|
||||||
|
/// on the receiving contract.
|
||||||
|
sendNft: (
|
||||||
|
senderAddress: string,
|
||||||
|
contract: string,
|
||||||
|
token_id: string,
|
||||||
|
msg: string //Binary
|
||||||
|
) => Promise<string>
|
||||||
|
/// Allows operator to transfer / send the token from the owner's account.
|
||||||
|
/// If expiration is set, then this allowance has a time/height limit
|
||||||
|
approve: (
|
||||||
|
senderAddress: string,
|
||||||
|
spender: string,
|
||||||
|
token_id: string,
|
||||||
|
expires: Expiration | null
|
||||||
|
) => Promise<string>
|
||||||
|
/// Remove previously granted Approval
|
||||||
|
revoke: (
|
||||||
|
senderAddress: string,
|
||||||
|
spender: string,
|
||||||
|
token_id: string
|
||||||
|
) => Promise<string>
|
||||||
|
/// Allows operator to transfer / send any token from the owner's account.
|
||||||
|
/// If expiration is set, then this allowance has a time/height limit
|
||||||
|
approveAll: (
|
||||||
|
senderAddress: string,
|
||||||
|
operator: string,
|
||||||
|
expires: Expiration | null
|
||||||
|
) => Promise<string>
|
||||||
|
/// Remove previously granted ApproveAll permission
|
||||||
|
revokeAll: (senderAddress: string, operator: string) => Promise<string>
|
||||||
|
/// Mint a new NFT, can only be called by the contract minter
|
||||||
|
mint: (senderAddress: string, msg: string) => Promise<string> //MintMsg<T>
|
||||||
|
|
||||||
|
/// Burn an NFT the sender has access to
|
||||||
|
burn: (senderAddress: string, token_id: string) => Promise<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SG721Contract {
|
||||||
|
instantiate: (
|
||||||
|
senderAddress: string,
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
funds: Coin[],
|
||||||
|
admin?: string
|
||||||
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
|
use: (contractAddress: string) => SG721Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SG721 = (client: SigningCosmWasmClient): SG721Contract => {
|
||||||
|
const use = (contractAddress: string): SG721Instance => {
|
||||||
|
const encode = (str: string): string =>
|
||||||
|
Buffer.from(str, 'binary').toString('base64')
|
||||||
|
|
||||||
|
const getOwnerOf = async (
|
||||||
|
token_id: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
owner_of: { token_id, include_expired },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getApproval = async (
|
||||||
|
token_id: string,
|
||||||
|
spender: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
approval: { token_id, spender, include_expired },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getApprovals = async (
|
||||||
|
token_id: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
approvals: { token_id, include_expired },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllOperators = async (
|
||||||
|
owner: string,
|
||||||
|
include_expired: boolean | null,
|
||||||
|
start_after: string | null,
|
||||||
|
limit: number | null
|
||||||
|
): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
all_operators: { owner, include_expired, start_after, limit },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNumTokens = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
num_tokens: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getContractInfo = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
contract_info: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNftInfo = async (token_id: string): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
nft_info: { token_id },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllNftInfo = async (
|
||||||
|
token_id: string,
|
||||||
|
include_expired: boolean | null
|
||||||
|
): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
all_nft_info: { token_id, include_expired },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokens = async (
|
||||||
|
owner: string,
|
||||||
|
start_after: string | null,
|
||||||
|
limit: number | null
|
||||||
|
): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
tokens: { owner, start_after, limit },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllTokens = async (
|
||||||
|
start_after: string | null,
|
||||||
|
limit: number | null
|
||||||
|
): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
all_tokens: { start_after, limit },
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMinter = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
minter: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionInfo = async (): Promise<any> => {
|
||||||
|
const res = await client.queryContractSmart(contractAddress, {
|
||||||
|
collection_info: {},
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
//Execute
|
||||||
|
const transferNft = async (
|
||||||
|
senderAddress: string,
|
||||||
|
recipient: string,
|
||||||
|
token_id: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
transfer_nft: { recipient, token_id },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendNft = async (
|
||||||
|
senderAddress: string,
|
||||||
|
contract: string,
|
||||||
|
token_id: string,
|
||||||
|
msg: string //Binary
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
send_nft: { contract, token_id, msg: encode(msg) },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const approve = async (
|
||||||
|
senderAddress: string,
|
||||||
|
spender: string,
|
||||||
|
token_id: string,
|
||||||
|
expires: Expiration | null
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
approve: { spender, token_id, expires },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const revoke = async (
|
||||||
|
senderAddress: string,
|
||||||
|
spender: string,
|
||||||
|
token_id: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
revoke: { spender, token_id },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const approveAll = async (
|
||||||
|
senderAddress: string,
|
||||||
|
operator: string,
|
||||||
|
expires: Expiration | null
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
approve_all: { operator, expires },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const revokeAll = async (
|
||||||
|
senderAddress: string,
|
||||||
|
operator: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
revoke_all: { operator },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const mint = async (
|
||||||
|
senderAddress: string,
|
||||||
|
msg: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
mint: { msg },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const burn = async (
|
||||||
|
senderAddress: string,
|
||||||
|
token_id: string
|
||||||
|
): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{
|
||||||
|
burn: { token_id },
|
||||||
|
},
|
||||||
|
'auto',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
contractAddress,
|
||||||
|
getOwnerOf,
|
||||||
|
getApproval,
|
||||||
|
getApprovals,
|
||||||
|
getAllOperators,
|
||||||
|
getNumTokens,
|
||||||
|
getContractInfo,
|
||||||
|
getNftInfo,
|
||||||
|
getAllNftInfo,
|
||||||
|
getTokens,
|
||||||
|
getAllTokens,
|
||||||
|
getMinter,
|
||||||
|
getCollectionInfo,
|
||||||
|
transferNft,
|
||||||
|
sendNft,
|
||||||
|
approve,
|
||||||
|
revoke,
|
||||||
|
approveAll,
|
||||||
|
revokeAll,
|
||||||
|
mint,
|
||||||
|
burn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instantiate = async (
|
||||||
|
senderAddress: string,
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
funds: Coin[],
|
||||||
|
admin?: string
|
||||||
|
): Promise<InstantiateResponse> => {
|
||||||
|
const result = await client.instantiate(
|
||||||
|
senderAddress,
|
||||||
|
codeId,
|
||||||
|
initMsg,
|
||||||
|
label,
|
||||||
|
'auto',
|
||||||
|
{
|
||||||
|
funds,
|
||||||
|
memo: '',
|
||||||
|
admin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
contractAddress: result.contractAddress,
|
||||||
|
transactionHash: result.transactionHash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { use, instantiate }
|
||||||
|
}
|
2
contracts/sg721/index.ts
Normal file
2
contracts/sg721/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './contract'
|
||||||
|
export * from './useContract'
|
73
contracts/sg721/useContract.ts
Normal file
73
contracts/sg721/useContract.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import { Coin } from 'cosmwasm'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { SG721 as initContract, SG721Contract, SG721Instance } from './contract'
|
||||||
|
|
||||||
|
interface InstantiateResponse {
|
||||||
|
readonly contractAddress: string
|
||||||
|
readonly transactionHash: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseSG721ContractProps {
|
||||||
|
instantiate: (
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
funds: Coin[],
|
||||||
|
admin?: string
|
||||||
|
) => Promise<InstantiateResponse>
|
||||||
|
use: (customAddress: string) => SG721Instance | undefined
|
||||||
|
updateContractAddress: (contractAddress: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSG721Contract(): UseSG721ContractProps {
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
|
const [address, setAddress] = useState<string>('')
|
||||||
|
const [SG721, setSG721] = useState<SG721Contract>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAddress(localStorage.getItem('contract_address') || '')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (wallet.initialized) {
|
||||||
|
const getSG721Instance = async (): Promise<void> => {
|
||||||
|
const SG721Contract = initContract(wallet.getClient())
|
||||||
|
setSG721(SG721Contract)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSG721Instance()
|
||||||
|
}
|
||||||
|
}, [wallet])
|
||||||
|
|
||||||
|
const updateContractAddress = (contractAddress: string) => {
|
||||||
|
setAddress(contractAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
const instantiate = useCallback(
|
||||||
|
(codeId, initMsg, label, admin?): Promise<InstantiateResponse> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!SG721) return reject('Contract is not initialized.')
|
||||||
|
SG721.instantiate(wallet.address, codeId, initMsg, label, admin)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[SG721, wallet]
|
||||||
|
)
|
||||||
|
|
||||||
|
const use = useCallback(
|
||||||
|
(customAddress = ''): SG721Instance | undefined => {
|
||||||
|
return SG721?.use(address || customAddress)
|
||||||
|
},
|
||||||
|
[SG721, address]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
instantiate,
|
||||||
|
use,
|
||||||
|
updateContractAddress,
|
||||||
|
}
|
||||||
|
}
|
201
contracts/whitelist/contract.ts
Normal file
201
contracts/whitelist/contract.ts
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
|
import { Coin } from '@cosmjs/proto-signing'
|
||||||
|
|
||||||
|
type Expiration = { at_height: number } | { at_time: string } | { never: {} }
|
||||||
|
|
||||||
|
export interface InstantiateResponse {
|
||||||
|
readonly contractAddress: string
|
||||||
|
readonly transactionHash: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigResponse {
|
||||||
|
readonly num_members: number
|
||||||
|
readonly per_address: number
|
||||||
|
readonly member_limit: number
|
||||||
|
readonly start_time: string
|
||||||
|
readonly end_time: string
|
||||||
|
readonly unit_price: Coin
|
||||||
|
readonly is_active: boolean
|
||||||
|
}
|
||||||
|
export interface WhiteListInstance {
|
||||||
|
readonly contractAddress: string
|
||||||
|
//Query
|
||||||
|
hasStarted: () => Promise<boolean>
|
||||||
|
hasEnded: () => Promise<boolean>
|
||||||
|
isActive: () => Promise<boolean>
|
||||||
|
members: (limit: number, startAfter?: string) => Promise<string[]>
|
||||||
|
hasMember: (member: string) => Promise<boolean>
|
||||||
|
config: () => Promise<ConfigResponse>
|
||||||
|
|
||||||
|
//Execute
|
||||||
|
updateStartTime: (startTime: string) => Promise<string>
|
||||||
|
updateEndTime: (endTime: string) => Promise<string>
|
||||||
|
addMembers: (to_add: string[]) => Promise<string>
|
||||||
|
removeMembers: (to_remove: string[]) => Promise<string>
|
||||||
|
updatePerAddressLimit: (limit: number) => Promise<string>
|
||||||
|
increaseMemberLimit: (limit: number) => Promise<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhiteListContract {
|
||||||
|
instantiate: (
|
||||||
|
senderAddress: string,
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
admin?: string,
|
||||||
|
funds?: Coin[]
|
||||||
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
|
use: (contractAddress: string) => WhiteListInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WhiteList = (
|
||||||
|
client: SigningCosmWasmClient,
|
||||||
|
senderAddress: string
|
||||||
|
): WhiteListContract => {
|
||||||
|
const use = (contractAddress: string): WhiteListInstance => {
|
||||||
|
console.log(client, 'client')
|
||||||
|
console.log(senderAddress, 'senderAddress')
|
||||||
|
///QUERY START
|
||||||
|
const hasStarted = async (): Promise<boolean> => {
|
||||||
|
return client.queryContractSmart(contractAddress, { has_started: {} })
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasEnded = async (): Promise<boolean> => {
|
||||||
|
return client.queryContractSmart(contractAddress, { has_ended: {} })
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActive = async (): Promise<boolean> => {
|
||||||
|
return client.queryContractSmart(contractAddress, { is_active: {} })
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = async (
|
||||||
|
limit: number,
|
||||||
|
startAfter?: string
|
||||||
|
): Promise<string[]> => {
|
||||||
|
return client.queryContractSmart(contractAddress, {
|
||||||
|
members: { limit, start_after: startAfter },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasMember = async (member: string): Promise<boolean> => {
|
||||||
|
return client.queryContractSmart(contractAddress, {
|
||||||
|
has_member: { member },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = async (): Promise<ConfigResponse> => {
|
||||||
|
return client.queryContractSmart(contractAddress, {
|
||||||
|
config: {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// QUERY END
|
||||||
|
/// EXECUTE START
|
||||||
|
const updateStartTime = async (startTime: string): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{ update_start_time: startTime },
|
||||||
|
'auto',
|
||||||
|
'memo'
|
||||||
|
)
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateEndTime = async (endTime: string): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{ update_end_time: endTime },
|
||||||
|
'auto'
|
||||||
|
)
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMembers = async (to_add: string[]): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{ add_members: to_add },
|
||||||
|
'auto'
|
||||||
|
)
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeMembers = async (to_remove: string[]): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{ remove_members: to_remove },
|
||||||
|
'auto'
|
||||||
|
)
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePerAddressLimit = async (limit: number): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{ update_per_address_limit: limit },
|
||||||
|
'auto'
|
||||||
|
)
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
|
const increaseMemberLimit = async (limit: number): Promise<string> => {
|
||||||
|
const res = await client.execute(
|
||||||
|
senderAddress,
|
||||||
|
contractAddress,
|
||||||
|
{ increase_member_limit: limit },
|
||||||
|
'auto'
|
||||||
|
)
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
/// EXECUTE END
|
||||||
|
|
||||||
|
return {
|
||||||
|
contractAddress,
|
||||||
|
updateStartTime,
|
||||||
|
updateEndTime,
|
||||||
|
addMembers,
|
||||||
|
removeMembers,
|
||||||
|
updatePerAddressLimit,
|
||||||
|
increaseMemberLimit,
|
||||||
|
hasStarted,
|
||||||
|
hasEnded,
|
||||||
|
isActive,
|
||||||
|
members,
|
||||||
|
hasMember,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instantiate = async (
|
||||||
|
senderAddress: string,
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
admin?: string,
|
||||||
|
funds?: Coin[]
|
||||||
|
): Promise<InstantiateResponse> => {
|
||||||
|
console.log('Funds:' + funds)
|
||||||
|
const result = await client.instantiate(
|
||||||
|
senderAddress,
|
||||||
|
codeId,
|
||||||
|
initMsg,
|
||||||
|
label,
|
||||||
|
'auto',
|
||||||
|
{
|
||||||
|
funds,
|
||||||
|
admin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
contractAddress: result.contractAddress,
|
||||||
|
transactionHash: result.transactionHash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { use, instantiate }
|
||||||
|
}
|
2
contracts/whitelist/index.ts
Normal file
2
contracts/whitelist/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './contract'
|
||||||
|
export * from './useContract'
|
83
contracts/whitelist/useContract.ts
Normal file
83
contracts/whitelist/useContract.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { Coin } from '@cosmjs/proto-signing'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { WhiteList } from './contract'
|
||||||
|
import {
|
||||||
|
InstantiateResponse,
|
||||||
|
WhiteList as initContract,
|
||||||
|
WhiteListContract,
|
||||||
|
WhiteListInstance,
|
||||||
|
} from './contract'
|
||||||
|
|
||||||
|
export interface useWhiteListContractProps {
|
||||||
|
instantiate: (
|
||||||
|
codeId: number,
|
||||||
|
initMsg: Record<string, unknown>,
|
||||||
|
label: string,
|
||||||
|
admin?: string,
|
||||||
|
funds?: Coin[]
|
||||||
|
) => Promise<InstantiateResponse>
|
||||||
|
|
||||||
|
use: (customAddress: string) => WhiteListInstance | undefined
|
||||||
|
updateContractAddress: (contractAddress: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWhiteListContract(): useWhiteListContractProps {
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
|
const [address, setAddress] = useState<string>('')
|
||||||
|
const [WhiteList, setWhiteList] = useState<WhiteListContract>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAddress(localStorage.getItem('contract_address') || '')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (wallet.initialized) {
|
||||||
|
const getWhiteListInstance = async (): Promise<void> => {
|
||||||
|
const client = wallet.getClient()
|
||||||
|
const whiteListContract = initContract(client, wallet.address)
|
||||||
|
setWhiteList(whiteListContract)
|
||||||
|
}
|
||||||
|
|
||||||
|
getWhiteListInstance()
|
||||||
|
}
|
||||||
|
}, [wallet])
|
||||||
|
|
||||||
|
const updateContractAddress = (contractAddress: string) => {
|
||||||
|
setAddress(contractAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
const instantiate = useCallback(
|
||||||
|
(codeId, initMsg, label, admin?, funds?): Promise<InstantiateResponse> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!WhiteList) return reject('Contract is not initialized.')
|
||||||
|
WhiteList.instantiate(
|
||||||
|
wallet.address,
|
||||||
|
codeId,
|
||||||
|
initMsg,
|
||||||
|
label,
|
||||||
|
admin,
|
||||||
|
funds
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[WhiteList, wallet]
|
||||||
|
)
|
||||||
|
|
||||||
|
const use = useCallback(
|
||||||
|
(customAddress = ''): WhiteListInstance | undefined => {
|
||||||
|
return WhiteList?.use(address || customAddress)
|
||||||
|
},
|
||||||
|
[WhiteList]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
instantiate,
|
||||||
|
use,
|
||||||
|
updateContractAddress,
|
||||||
|
}
|
||||||
|
}
|
2
env.d.ts
vendored
2
env.d.ts
vendored
@ -14,7 +14,7 @@ declare namespace NodeJS {
|
|||||||
declare interface ProcessEnv {
|
declare interface ProcessEnv {
|
||||||
readonly APP_VERSION: string
|
readonly APP_VERSION: string
|
||||||
|
|
||||||
readonly NEXT_PUBLIC_CW721_BASE_CODE_ID: string
|
readonly NEXT_PUBLIC_SG721_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
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"match-sorter": "^6",
|
"match-sorter": "^6",
|
||||||
"next": "^12",
|
"next": "^12",
|
||||||
"next-seo": "^4",
|
"next-seo": "^4",
|
||||||
|
"nft.storage": "^6.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-datetime-picker": "^3",
|
"react-datetime-picker": "^3",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
1
pages/collection/index.tsx
Normal file
1
pages/collection/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './upload'
|
315
pages/collection/upload.tsx
Normal file
315
pages/collection/upload.tsx
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import Anchor from 'components/Anchor'
|
||||||
|
import AnchorButton from 'components/AnchorButton'
|
||||||
|
import Button from 'components/Button'
|
||||||
|
import { useCollectionStore } from 'contexts/collection'
|
||||||
|
import { setBaseTokenUri, setImage } from 'contexts/collection'
|
||||||
|
import { useWallet } from 'contexts/wallet'
|
||||||
|
import { NextPage } from 'next'
|
||||||
|
import { NextSeo } from 'next-seo'
|
||||||
|
import { Blob, File, NFTStorage } from 'nft.storage'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { FaArrowRight } from 'react-icons/fa'
|
||||||
|
import { withMetadata } from 'utils/layout'
|
||||||
|
import { links } from 'utils/links'
|
||||||
|
import { naturalCompare } from 'utils/sort'
|
||||||
|
|
||||||
|
const UploadPage: NextPage = () => {
|
||||||
|
const wallet = useWallet()
|
||||||
|
|
||||||
|
const baseTokenURI = useCollectionStore().base_token_uri
|
||||||
|
const [baseImageURI, setBaseImageURI] = useState('')
|
||||||
|
const [uploadMethod, setUploadMethod] = useState('New')
|
||||||
|
|
||||||
|
const [imageFiles, setImageFiles] = useState<File[]>([])
|
||||||
|
const [metadataFiles, setMetadataFiles] = useState<File[]>([])
|
||||||
|
const [updatedMetadataFiles, setUpdatedMetadataFiles] = useState<File[]>([])
|
||||||
|
let imageFilesArray: File[] = []
|
||||||
|
let metadataFilesArray: File[] = []
|
||||||
|
let updatedMetadataFilesArray: File[] = []
|
||||||
|
|
||||||
|
const imageFilesRef = useRef<HTMLInputElement>(null)
|
||||||
|
const metadataFilesRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const NFT_STORAGE_TOKEN =
|
||||||
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDJBODk5OGI4ZkE2YTM1NzMyYmMxQTRDQzNhOUU2M0Y2NUM3ZjA1RWIiLCJpc3MiOiJuZnQtc3RvcmFnZSIsImlhdCI6MTY1NTE5MTcwNDQ2MiwibmFtZSI6IlRlc3QifQ.IbdV_26bkPHSdd81sxox5AoG-5a4CCEY4aCrdbCXwAE'
|
||||||
|
const client = new NFTStorage({ token: NFT_STORAGE_TOKEN })
|
||||||
|
|
||||||
|
const handleChangeBaseTokenUri = (event: {
|
||||||
|
target: { value: React.SetStateAction<string> }
|
||||||
|
}) => {
|
||||||
|
setBaseTokenUri(event.target.value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChangeImage = (event: {
|
||||||
|
target: { value: React.SetStateAction<string> }
|
||||||
|
}) => {
|
||||||
|
setImage(event.target.value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectImages = async () => {
|
||||||
|
imageFilesArray = []
|
||||||
|
let reader: FileReader
|
||||||
|
if (!imageFilesRef.current?.files) return toast.error('No files selected.')
|
||||||
|
for (let i = 0; i < imageFilesRef.current.files.length; i++) {
|
||||||
|
reader = new FileReader()
|
||||||
|
reader.onload = function (e) {
|
||||||
|
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||||
|
if (!imageFilesRef.current?.files)
|
||||||
|
return toast.error('No files selected.')
|
||||||
|
let imageFile = new File(
|
||||||
|
[e.target.result],
|
||||||
|
imageFilesRef.current.files[i].name,
|
||||||
|
{ type: 'image/jpg' }
|
||||||
|
)
|
||||||
|
imageFilesArray.push(imageFile)
|
||||||
|
if (i === imageFilesRef.current.files.length - 1) {
|
||||||
|
imageFilesArray.sort((a, b) => naturalCompare(a.name, b.name))
|
||||||
|
console.log(imageFilesArray)
|
||||||
|
selectMetadata()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!imageFilesRef.current.files) return toast.error('No file selected.')
|
||||||
|
reader.readAsArrayBuffer(imageFilesRef.current.files[i])
|
||||||
|
//reader.onloadend = function (e) { ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const selectMetadata = async () => {
|
||||||
|
metadataFilesArray = []
|
||||||
|
|
||||||
|
let reader: FileReader
|
||||||
|
if (!metadataFilesRef.current?.files)
|
||||||
|
return toast.error('No files selected.')
|
||||||
|
for (let i = 0; i < metadataFilesRef.current.files.length; i++) {
|
||||||
|
reader = new FileReader()
|
||||||
|
reader.onload = function (e) {
|
||||||
|
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||||
|
if (!metadataFilesRef.current?.files)
|
||||||
|
return toast.error('No files selected.')
|
||||||
|
let metadataFile = new File(
|
||||||
|
[e.target.result],
|
||||||
|
metadataFilesRef.current.files[i].name,
|
||||||
|
{ type: 'image/jpg' }
|
||||||
|
)
|
||||||
|
metadataFilesArray.push(metadataFile)
|
||||||
|
if (i === metadataFilesRef.current.files.length - 1) {
|
||||||
|
metadataFilesArray.sort((a, b) => naturalCompare(a.name, b.name))
|
||||||
|
console.log(metadataFilesArray)
|
||||||
|
updateMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!metadataFilesRef.current?.files)
|
||||||
|
return toast.error('No file selected.')
|
||||||
|
reader.readAsText(metadataFilesRef.current.files[i], 'utf8')
|
||||||
|
//reader.onloadend = function (e) { ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updateMetadata = async () => {
|
||||||
|
const imageURI = await client.storeDirectory(imageFilesArray)
|
||||||
|
console.log(imageURI)
|
||||||
|
updatedMetadataFilesArray = []
|
||||||
|
let reader: FileReader
|
||||||
|
for (let i = 0; i < metadataFilesArray.length; i++) {
|
||||||
|
reader = new FileReader()
|
||||||
|
reader.onload = function (e) {
|
||||||
|
let metadataJSON = JSON.parse(e.target?.result as string)
|
||||||
|
metadataJSON.image = `ipfs://${imageURI}/${imageFilesArray[i].name}`
|
||||||
|
let metadataFileBlob = new Blob([JSON.stringify(metadataJSON)], {
|
||||||
|
type: 'application/json',
|
||||||
|
})
|
||||||
|
let updatedMetadataFile = new File(
|
||||||
|
[metadataFileBlob],
|
||||||
|
metadataFilesArray[i].name,
|
||||||
|
{ type: 'application/json' }
|
||||||
|
)
|
||||||
|
updatedMetadataFilesArray.push(updatedMetadataFile)
|
||||||
|
console.log(updatedMetadataFile.name + ' => ' + metadataJSON.image)
|
||||||
|
if (i === metadataFilesArray.length - 1) {
|
||||||
|
upload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsText(metadataFilesArray[i], 'utf8')
|
||||||
|
//reader.onloadend = function (e) { ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upload = async () => {
|
||||||
|
const baseTokenURI = await client.storeDirectory(updatedMetadataFilesArray)
|
||||||
|
console.log(baseTokenURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<NextSeo title="Create Collection" />
|
||||||
|
|
||||||
|
<div className="space-y-8 mt-5 text-center">
|
||||||
|
<h1 className="font-heading text-4xl font-bold">
|
||||||
|
Upload Assets & Metadata
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Make sure you check our{' '}
|
||||||
|
<Anchor
|
||||||
|
href={links['Docs']}
|
||||||
|
className="font-bold text-plumbus hover:underline"
|
||||||
|
>
|
||||||
|
documentation
|
||||||
|
</Anchor>{' '}
|
||||||
|
on how to create your collection
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="border-white/20" />
|
||||||
|
|
||||||
|
<div className="justify-items-start mt-5 mb-3 ml-3 flex-column">
|
||||||
|
<div className="mt-3 ml-4 form-check form-check-inline">
|
||||||
|
<input
|
||||||
|
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||||
|
type="radio"
|
||||||
|
name="inlineRadioOptions2"
|
||||||
|
id="inlineRadio2"
|
||||||
|
value="Existing"
|
||||||
|
onClick={() => {
|
||||||
|
setUploadMethod('Existing')
|
||||||
|
}}
|
||||||
|
onChange={() => { }}
|
||||||
|
checked={uploadMethod === 'Existing'}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="inline-block text-white cursor-pointer form-check-label"
|
||||||
|
htmlFor="inlineRadio2"
|
||||||
|
>
|
||||||
|
Use an existing URI
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 ml-4 form-check form-check-inline">
|
||||||
|
<input
|
||||||
|
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||||
|
type="radio"
|
||||||
|
name="inlineRadioOptions"
|
||||||
|
id="inlineRadio3"
|
||||||
|
value="New"
|
||||||
|
onClick={() => {
|
||||||
|
setUploadMethod('New')
|
||||||
|
}}
|
||||||
|
onChange={() => { }}
|
||||||
|
checked={uploadMethod === 'New'}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="inline-block text-white cursor-pointer form-check-label"
|
||||||
|
htmlFor="inlineRadio3"
|
||||||
|
>
|
||||||
|
Upload assets & metadata
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="border-white/20" />
|
||||||
|
|
||||||
|
{uploadMethod == 'Existing' && (
|
||||||
|
<div className="ml-3 flex-column">
|
||||||
|
<p className="my-3 ml-5">
|
||||||
|
Though Stargaze's sg721 contract allows for off-chain metadata
|
||||||
|
storage, it is recommended to use a decentralized storage solution,
|
||||||
|
such as IPFS. <br /> You may head over to{' '}
|
||||||
|
<Anchor
|
||||||
|
href="https://nft.storage"
|
||||||
|
className="font-bold text-plumbus hover:underline"
|
||||||
|
>
|
||||||
|
NFT Storage
|
||||||
|
</Anchor>{' '}
|
||||||
|
and upload your assets & metadata manually to get a base URI for
|
||||||
|
your collection.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<label className="block mr-1 mb-1 ml-5 font-bold text-white dark:text-gray-300">
|
||||||
|
Collection Cover Image
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
onChange={handleChangeImage}
|
||||||
|
placeholder="ipfs://bafybeigi3bwpvyvsmnbj46ra4hyffcxdeaj6ntfk5jpic5mx27x6ih2qvq/images/1.png"
|
||||||
|
className="py-2 px-1 mx-5 mt-2 mb-2 w-1/2 bg-white/10 rounded border-2 border-white/20 focus:ring
|
||||||
|
focus:ring-plumbus-20
|
||||||
|
form-input, placeholder:text-white/50,"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block mt-3 mr-1 mb-1 ml-5 font-bold text-white dark:text-gray-300">
|
||||||
|
Base Token URI
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
onChange={handleChangeBaseTokenUri}
|
||||||
|
placeholder="ipfs://..."
|
||||||
|
className="py-2 px-1 mx-5 mt-2 mb-2 w-1/2 bg-white/10 rounded border-2 border-white/20 focus:ring
|
||||||
|
focus:ring-plumbus-20
|
||||||
|
form-input, placeholder:text-white/50,"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{uploadMethod == 'New' && (
|
||||||
|
<div>
|
||||||
|
<label className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300">
|
||||||
|
Image File Selection
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-1/2 h-32',
|
||||||
|
'rounded border-2 border-white/20 border-dashed'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="imageFiles"
|
||||||
|
accept="image/*"
|
||||||
|
className={clsx(
|
||||||
|
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||||
|
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition'
|
||||||
|
)}
|
||||||
|
onChange={() => { }}
|
||||||
|
ref={imageFilesRef}
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300">
|
||||||
|
Metadata Selection
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-1/2 h-32',
|
||||||
|
'rounded border-2 border-white/20 border-dashed'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="metadataFiles"
|
||||||
|
accept=""
|
||||||
|
className={clsx(
|
||||||
|
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||||
|
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition'
|
||||||
|
)}
|
||||||
|
onChange={() => { }}
|
||||||
|
ref={metadataFilesRef}
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-5 ml-8">
|
||||||
|
<Button
|
||||||
|
onClick={selectImages}
|
||||||
|
variant="solid"
|
||||||
|
isWide
|
||||||
|
className="w-[120px]"
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withMetadata(UploadPage, { center: false })
|
@ -1,148 +0,0 @@
|
|||||||
import { Button } from 'components/Button'
|
|
||||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
|
||||||
import { ExecuteCombobox } from 'components/contracts/cw721/base/ExecuteCombobox'
|
|
||||||
import { useExecuteComboboxState } from 'components/contracts/cw721/base/ExecuteCombobox.hooks'
|
|
||||||
import { FormControl } from 'components/FormControl'
|
|
||||||
import { AddressInput } 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 { cw721BaseLinkTabs } from 'components/LinkTabs.data'
|
|
||||||
import { TransactionHash } from 'components/TransactionHash'
|
|
||||||
import { useContracts } from 'contexts/contracts'
|
|
||||||
import { useWallet } from 'contexts/wallet'
|
|
||||||
import type { NextPage } from 'next'
|
|
||||||
import { NextSeo } from 'next-seo'
|
|
||||||
import type { FormEvent } from 'react'
|
|
||||||
import { useMemo, useState } from 'react'
|
|
||||||
import { toast } from 'react-hot-toast'
|
|
||||||
import { FaArrowRight } from 'react-icons/fa'
|
|
||||||
import { useMutation } from 'react-query'
|
|
||||||
import type { DispatchExecuteArgs } from 'utils/contracts/cw721/base/execute'
|
|
||||||
import { dispatchExecute, isEitherType, previewExecutePayload } from 'utils/contracts/cw721/base/execute'
|
|
||||||
import { parseJson } from 'utils/json'
|
|
||||||
import { withMetadata } from 'utils/layout'
|
|
||||||
import { links } from 'utils/links'
|
|
||||||
|
|
||||||
const CW721BaseExecutePage: NextPage = () => {
|
|
||||||
const { cw721Base: contract } = useContracts()
|
|
||||||
const wallet = useWallet()
|
|
||||||
const [lastTx, setLastTx] = useState('')
|
|
||||||
|
|
||||||
const comboboxState = useExecuteComboboxState()
|
|
||||||
const type = comboboxState.value?.id
|
|
||||||
|
|
||||||
const contractState = useInputState({
|
|
||||||
id: 'contract-address',
|
|
||||||
name: 'contract-address',
|
|
||||||
title: 'CW721 Contract Address',
|
|
||||||
subtitle: 'Address of the CW721 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 tokenIdState = useInputState({
|
|
||||||
id: 'token-id',
|
|
||||||
name: 'token-id',
|
|
||||||
title: 'Token ID',
|
|
||||||
subtitle: 'Identifier of the token',
|
|
||||||
placeholder: 'some_token_id',
|
|
||||||
})
|
|
||||||
|
|
||||||
const showMessageField = type === 'send_nft'
|
|
||||||
const showRecipientField = isEitherType(type, [
|
|
||||||
'transfer_nft',
|
|
||||||
'send_nft',
|
|
||||||
'approve',
|
|
||||||
'revoke',
|
|
||||||
'approve_all',
|
|
||||||
'revoke_all',
|
|
||||||
'mint',
|
|
||||||
])
|
|
||||||
const showTokenIdField = isEitherType(type, ['transfer_nft', 'send_nft', 'approve', 'revoke', 'mint', 'burn'])
|
|
||||||
|
|
||||||
const messages = useMemo(() => contract?.use(contractState.value), [contract, wallet.address, contractState.value])
|
|
||||||
const payload: DispatchExecuteArgs = {
|
|
||||||
contract: contractState.value,
|
|
||||||
messages,
|
|
||||||
msg: parseJson(messageState.value) || {},
|
|
||||||
recipient: recipientState.value,
|
|
||||||
txSigner: wallet.address,
|
|
||||||
type,
|
|
||||||
tokenId: tokenIdState.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) => {
|
|
||||||
console.error(error)
|
|
||||||
toast.error(String(error))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-6 px-12 space-y-4">
|
|
||||||
<NextSeo title="Execute CW721 Base Contract" />
|
|
||||||
<ContractPageHeader
|
|
||||||
description="CW721 Base is a specification for non fungible tokens based on CosmWasm."
|
|
||||||
link={links['Docs CW721 Base']}
|
|
||||||
title="CW721 Base Contract"
|
|
||||||
/>
|
|
||||||
<LinkTabs activeIndex={2} data={cw721BaseLinkTabs} />
|
|
||||||
|
|
||||||
<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} />}
|
|
||||||
{showTokenIdField && <AddressInput {...tokenIdState} />}
|
|
||||||
{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(CW721BaseExecutePage, { center: false })
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './instantiate'
|
|
@ -1,113 +0,0 @@
|
|||||||
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 { TextInput } from 'components/forms/FormInput'
|
|
||||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
|
||||||
import { JsonPreview } from 'components/JsonPreview'
|
|
||||||
import { LinkTabs } from 'components/LinkTabs'
|
|
||||||
import { cw721BaseLinkTabs } from 'components/LinkTabs.data'
|
|
||||||
import { useContracts } from 'contexts/contracts'
|
|
||||||
import { useWallet } from 'contexts/wallet'
|
|
||||||
import type { InstantiateResponse } from 'contracts/cw721/base'
|
|
||||||
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 { CW721_BASE_CODE_ID } from 'utils/constants'
|
|
||||||
import { withMetadata } from 'utils/layout'
|
|
||||||
import { links } from 'utils/links'
|
|
||||||
|
|
||||||
const CW721BaseInstantiatePage: NextPage = () => {
|
|
||||||
const wallet = useWallet()
|
|
||||||
const contract = useContracts().cw721Base
|
|
||||||
|
|
||||||
const nameState = useInputState({
|
|
||||||
id: 'name',
|
|
||||||
name: 'name',
|
|
||||||
title: 'Name',
|
|
||||||
placeholder: 'My Awesome CW721 Contract',
|
|
||||||
})
|
|
||||||
|
|
||||||
const symbolState = useInputState({
|
|
||||||
id: 'symbol',
|
|
||||||
name: 'symbol',
|
|
||||||
title: 'Symbol',
|
|
||||||
placeholder: 'AWSM',
|
|
||||||
})
|
|
||||||
|
|
||||||
const minterState = useInputState({
|
|
||||||
id: 'minter-address',
|
|
||||||
name: 'minterAddress',
|
|
||||||
title: 'Minter Address',
|
|
||||||
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data, isLoading, mutate } = useMutation(
|
|
||||||
async (event: FormEvent): Promise<InstantiateResponse | null> => {
|
|
||||||
event.preventDefault()
|
|
||||||
if (!contract) {
|
|
||||||
throw new Error('Smart contract connection failed')
|
|
||||||
}
|
|
||||||
const msg = {
|
|
||||||
name: nameState.value,
|
|
||||||
symbol: symbolState.value,
|
|
||||||
minter: minterState.value,
|
|
||||||
}
|
|
||||||
return toast.promise(
|
|
||||||
contract.instantiate(CW721_BASE_CODE_ID, msg, 'StargazeTools CW721 Base 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 CW721 Base Contract" />
|
|
||||||
<ContractPageHeader
|
|
||||||
description="CW721 Base is a specification for non fungible tokens based on CosmWasm."
|
|
||||||
link={links['Docs CW721 Base']}
|
|
||||||
title="CW721 Base Contract"
|
|
||||||
/>
|
|
||||||
<LinkTabs activeIndex={0} data={cw721BaseLinkTabs} />
|
|
||||||
|
|
||||||
<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="Basic information about your new contract" title="Contract Details">
|
|
||||||
<TextInput isRequired {...nameState} />
|
|
||||||
<TextInput isRequired {...symbolState} />
|
|
||||||
<TextInput isRequired {...minterState} />
|
|
||||||
</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(CW721BaseInstantiatePage, { center: false })
|
|
@ -1,140 +0,0 @@
|
|||||||
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 { cw721BaseLinkTabs } from 'components/LinkTabs.data'
|
|
||||||
import { useContracts } from 'contexts/contracts'
|
|
||||||
import { useWallet } from 'contexts/wallet'
|
|
||||||
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 type { QueryType } from 'utils/contracts/cw721/base/query'
|
|
||||||
import { dispatchQuery, QUERY_LIST } from 'utils/contracts/cw721/base/query'
|
|
||||||
import { withMetadata } from 'utils/layout'
|
|
||||||
import { links } from 'utils/links'
|
|
||||||
|
|
||||||
const CW721QueryPage: NextPage = () => {
|
|
||||||
const { cw721Base: contract } = useContracts()
|
|
||||||
const wallet = useWallet()
|
|
||||||
|
|
||||||
const contractState = useInputState({
|
|
||||||
id: 'contract-address',
|
|
||||||
name: 'contract-address',
|
|
||||||
title: 'CW721 contract Address',
|
|
||||||
subtitle: 'Address of the CW721 contract',
|
|
||||||
})
|
|
||||||
const address = contractState.value
|
|
||||||
|
|
||||||
const ownerState = useInputState({
|
|
||||||
id: 'owner-address',
|
|
||||||
name: 'owner-address',
|
|
||||||
title: 'Owner Address',
|
|
||||||
subtitle: 'Address of the owner',
|
|
||||||
})
|
|
||||||
const ownerAddress = ownerState.value
|
|
||||||
|
|
||||||
const tokenIdState = useInputState({
|
|
||||||
id: 'token-id',
|
|
||||||
name: 'token-id',
|
|
||||||
title: 'Token ID',
|
|
||||||
subtitle: 'Identifier of the token',
|
|
||||||
placeholder: 'some_token_id',
|
|
||||||
})
|
|
||||||
const tokenId = tokenIdState.value
|
|
||||||
|
|
||||||
const [type, setType] = useState<QueryType>('owner_of')
|
|
||||||
|
|
||||||
const addressVisible = type === 'approval' || type === 'all_operators' || type === 'tokens'
|
|
||||||
const tokenVisible =
|
|
||||||
type === 'owner_of' || type === 'approval' || type === 'approvals' || type === 'nft_info' || type === 'all_nft_info'
|
|
||||||
|
|
||||||
const { data: response } = useQuery(
|
|
||||||
[address, type, contract, wallet, ownerAddress, tokenId] as const,
|
|
||||||
async ({ queryKey }) => {
|
|
||||||
const [_address, _type, _contract, _wallet, _ownerAddress, _tokenId] = queryKey
|
|
||||||
const messages = contract?.use(_address)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
||||||
const ownerAddress = _ownerAddress || _wallet.address
|
|
||||||
|
|
||||||
const result = await dispatchQuery({
|
|
||||||
ownerAddress,
|
|
||||||
tokenId,
|
|
||||||
messages,
|
|
||||||
type,
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
{
|
|
||||||
placeholderData: null,
|
|
||||||
onError: (error: any) => {
|
|
||||||
toast.error(error.message)
|
|
||||||
},
|
|
||||||
enabled: Boolean(address && type && contract && wallet),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (address.length > 0) {
|
|
||||||
void router.replace({ query: { contractAddress: address } })
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [address])
|
|
||||||
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 CW721 Base Contract" />
|
|
||||||
<ContractPageHeader
|
|
||||||
description="CW721 Base is a specification for non fungible tokens based on CosmWasm."
|
|
||||||
link={links['Docs CW721 Base']}
|
|
||||||
title="CW721 Base Contract"
|
|
||||||
/>
|
|
||||||
<LinkTabs activeIndex={1} data={cw721BaseLinkTabs} />
|
|
||||||
|
|
||||||
<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 {...ownerState} />
|
|
||||||
</Conditional>
|
|
||||||
<Conditional test={tokenVisible}>
|
|
||||||
<AddressInput {...tokenIdState} />
|
|
||||||
</Conditional>
|
|
||||||
</div>
|
|
||||||
<JsonPreview content={address ? { type, response } : null} title="Query Response" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withMetadata(CW721QueryPage, { center: false })
|
|
@ -1,5 +0,0 @@
|
|||||||
const Minting = () => {
|
|
||||||
return <div>dsadsa</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Minting
|
|
@ -1,117 +0,0 @@
|
|||||||
import clsx from 'clsx'
|
|
||||||
import { Alert } from 'components/Alert'
|
|
||||||
import { Button } from 'components/Button'
|
|
||||||
import { Conditional } from 'components/Conditional'
|
|
||||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
|
||||||
import { JsonPreview } from 'components/JsonPreview'
|
|
||||||
import { useWallet } from 'contexts/wallet'
|
|
||||||
import type { NextPage } from 'next'
|
|
||||||
import { NextSeo } from 'next-seo'
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import toast from 'react-hot-toast'
|
|
||||||
import { FaAsterisk } from 'react-icons/fa'
|
|
||||||
import { withMetadata } from 'utils/layout'
|
|
||||||
|
|
||||||
const UploadContract: NextPage = () => {
|
|
||||||
const { getClient, address } = useWallet()
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [transactionResult, setTransactionResult] = useState<any>()
|
|
||||||
const [wasmFile, setWasmFile] = useState<File | null>(null)
|
|
||||||
const [wasmByteArray, setWasmByteArray] = useState<Uint8Array | null>(null)
|
|
||||||
|
|
||||||
const inputFile = useRef<HTMLInputElement>(null)
|
|
||||||
|
|
||||||
const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (!e.target.files) return
|
|
||||||
setWasmFile(e.target.files[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (wasmFile) {
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.onload = (e) => {
|
|
||||||
try {
|
|
||||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
|
||||||
const byteArray = new Uint8Array(e.target.result as ArrayBuffer)
|
|
||||||
setWasmByteArray(byteArray)
|
|
||||||
} catch (error: any) {
|
|
||||||
toast.error(error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.readAsArrayBuffer(wasmFile)
|
|
||||||
}
|
|
||||||
}, [wasmFile])
|
|
||||||
|
|
||||||
const upload = async () => {
|
|
||||||
try {
|
|
||||||
if (!wasmFile || !wasmByteArray) return toast.error('No file selected.')
|
|
||||||
|
|
||||||
setLoading(true)
|
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
|
|
||||||
const result = await client.upload(address, wasmByteArray, 'auto')
|
|
||||||
|
|
||||||
setTransactionResult({
|
|
||||||
transactionHash: result.transactionHash,
|
|
||||||
codeId: result.codeId,
|
|
||||||
originalSize: result.originalSize,
|
|
||||||
compressedSize: result.compressedSize,
|
|
||||||
originalChecksum: result.originalChecksum,
|
|
||||||
compressedChecksum: result.compressedChecksum,
|
|
||||||
})
|
|
||||||
|
|
||||||
setLoading(false)
|
|
||||||
} catch (err: any) {
|
|
||||||
setLoading(false)
|
|
||||||
toast.error(err.message, { style: { maxWidth: 'none' } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-6 px-12 space-y-4">
|
|
||||||
<NextSeo title="Upload Contract" />
|
|
||||||
<ContractPageHeader
|
|
||||||
description="Here you can upload a contract on Stargaze Network."
|
|
||||||
link=""
|
|
||||||
title="Upload Contract"
|
|
||||||
/>
|
|
||||||
<div className="inset-x-0 bottom-0 border-b-2 border-white/25" />
|
|
||||||
|
|
||||||
<Conditional test={Boolean(transactionResult)}>
|
|
||||||
<Alert type="info">
|
|
||||||
<b>Upload success!</b> Here is the transaction result containing the code ID, transaction hash and other data.
|
|
||||||
</Alert>
|
|
||||||
<JsonPreview content={transactionResult} title="Transaction Result" />
|
|
||||||
<br />
|
|
||||||
</Conditional>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
'flex relative justify-center items-center space-y-4 h-32',
|
|
||||||
'rounded border-2 border-white/20 border-dashed',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
accept=".wasm"
|
|
||||||
className={clsx(
|
|
||||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
|
||||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
|
||||||
)}
|
|
||||||
onChange={onFileChange}
|
|
||||||
ref={inputFile}
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end pb-6">
|
|
||||||
<Button isDisabled={!wasmFile} isLoading={loading} isWide leftIcon={<FaAsterisk />} onClick={upload}>
|
|
||||||
Upload Contract
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withMetadata(UploadContract, { center: false })
|
|
@ -26,7 +26,7 @@ const HomePage: NextPage = () => {
|
|||||||
<div className="grid gap-8 md:grid-cols-2">
|
<div className="grid gap-8 md:grid-cols-2">
|
||||||
<HomeCard
|
<HomeCard
|
||||||
title="Create"
|
title="Create"
|
||||||
link="/collection/minter"
|
link="/collection/"
|
||||||
className="p-4 -m-4 hover:bg-gray-500/10 rounded"
|
className="p-4 -m-4 hover:bg-gray-500/10 rounded"
|
||||||
>
|
>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
@ -8,7 +8,7 @@ module.exports = {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
stargaze: { DEFAULT: '#FDC06D' },
|
stargaze: { DEFAULT: '#FFA900' },
|
||||||
dark: { DEFAULT: '#06090B' },
|
dark: { DEFAULT: '#06090B' },
|
||||||
gray: { DEFAULT: '#F3F6F8' },
|
gray: { DEFAULT: '#F3F6F8' },
|
||||||
'dark-gray': { DEFAULT: '#191D20' },
|
'dark-gray': { DEFAULT: '#191D20' },
|
||||||
@ -16,16 +16,16 @@ module.exports = {
|
|||||||
|
|
||||||
neutral: colors.neutral,
|
neutral: colors.neutral,
|
||||||
plumbus: {
|
plumbus: {
|
||||||
DEFAULT: '#FFC27D',
|
DEFAULT: '#FFA900',
|
||||||
light: '#FFC27D',
|
light: '#FFC922',
|
||||||
matte: '#FFC27D',
|
matte: '#5D89E9',
|
||||||
dark: '#FFC27D',
|
dark: '#FFC900',
|
||||||
10: '#FFF0ED',
|
10: '#FFF0ED',
|
||||||
20: '#FACBC8',
|
20: '#5D89E9',
|
||||||
30: '#F5A7A2',
|
30: '#F5A7A2',
|
||||||
40: '#F0827D',
|
40: '#FFA900',
|
||||||
50: '#D9726F',
|
50: '#FFA900',
|
||||||
60: '#C26261',
|
60: '#FFA900',
|
||||||
70: '#AB5152',
|
70: '#AB5152',
|
||||||
80: '#944144',
|
80: '#944144',
|
||||||
90: '#7D3136',
|
90: '#7D3136',
|
||||||
@ -54,10 +54,10 @@ module.exports = {
|
|||||||
plugin(({ addUtilities }) => {
|
plugin(({ addUtilities }) => {
|
||||||
addUtilities({
|
addUtilities({
|
||||||
'.stargaze-gradient-bg': {
|
'.stargaze-gradient-bg': {
|
||||||
background: `linear-gradient(64.38deg, #00027D 15.06%, #FFC27D 100.6%), #252020`,
|
background: `linear-gradient(64.38deg, #00027D 15.06%, #7F97D2 100.6%), #252020`,
|
||||||
},
|
},
|
||||||
'.stargaze-gradient-brand': {
|
'.stargaze-gradient-brand': {
|
||||||
background: `linear-gradient(102.33deg, #FFC27D 10.96%, #FFC27D 93.51%)`,
|
background: `linear-gradient(102.33deg, #FFC27D 10.96%, #7F97D2 93.51%)`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
@ -1,170 +0,0 @@
|
|||||||
import { CW721BaseInstance } from './../../../../contracts/cw721/base/contract';
|
|
||||||
import { useCW721BaseContract } from 'contracts/cw721/base'
|
|
||||||
|
|
||||||
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 CW721BaseInstance} */
|
|
||||||
export type DispatchExecuteArgs = {
|
|
||||||
contract: string
|
|
||||||
messages?: CW721BaseInstance
|
|
||||||
txSigner: string
|
|
||||||
} & (
|
|
||||||
| { 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 }
|
|
||||||
| { type: Select<'revoke'>; recipient: string; tokenId: string }
|
|
||||||
| { type: Select<'approve_all'>; recipient: string }
|
|
||||||
| { type: Select<'revoke_all'>; recipient: string }
|
|
||||||
| { type: Select<'mint'>; recipient: string; tokenId: 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.contract, args.tokenId, args.msg)
|
|
||||||
}
|
|
||||||
case 'approve': {
|
|
||||||
return messages.approve(args.recipient, args.tokenId)
|
|
||||||
}
|
|
||||||
case 'revoke': {
|
|
||||||
return messages.revoke(args.recipient, args.tokenId)
|
|
||||||
}
|
|
||||||
case 'approve_all': {
|
|
||||||
return messages.approveAll(args.recipient)
|
|
||||||
}
|
|
||||||
case 'revoke_all': {
|
|
||||||
return messages.revokeAll(args.recipient)
|
|
||||||
}
|
|
||||||
case 'mint': {
|
|
||||||
return messages.mint(args.tokenId, args.recipient)
|
|
||||||
}
|
|
||||||
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 } = useCW721BaseContract()
|
|
||||||
switch (args.type) {
|
|
||||||
case 'transfer_nft': {
|
|
||||||
const { contract, recipient, tokenId } = args
|
|
||||||
return messages()?.transferNft(contract, recipient, tokenId)
|
|
||||||
}
|
|
||||||
case 'send_nft': {
|
|
||||||
const { contract, recipient, tokenId, msg } = args
|
|
||||||
return messages()?.sendNft(contract, recipient, tokenId, msg)
|
|
||||||
}
|
|
||||||
case 'approve': {
|
|
||||||
const { contract, recipient, tokenId } = args
|
|
||||||
return messages()?.approve(contract, recipient, tokenId)
|
|
||||||
}
|
|
||||||
case 'revoke': {
|
|
||||||
const { contract, recipient, tokenId } = args
|
|
||||||
return messages()?.revoke(contract, recipient, tokenId)
|
|
||||||
}
|
|
||||||
case 'approve_all': {
|
|
||||||
const { contract, recipient } = args
|
|
||||||
return messages()?.approveAll(contract, recipient)
|
|
||||||
}
|
|
||||||
case 'revoke_all': {
|
|
||||||
const { contract, recipient } = args
|
|
||||||
return messages()?.revokeAll(contract, recipient)
|
|
||||||
}
|
|
||||||
case 'mint': {
|
|
||||||
const { contract, recipient, tokenId } = args
|
|
||||||
return messages()?.mint(contract, tokenId, recipient)
|
|
||||||
}
|
|
||||||
case 'burn': {
|
|
||||||
const { contract, tokenId } = args
|
|
||||||
return messages()?.burn(contract, tokenId)
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
|
|
||||||
return arr.some((val) => type === val)
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
import type { CW721BaseInstance } from 'contracts/cw721/base'
|
|
||||||
|
|
||||||
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',
|
|
||||||
] 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' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export interface DispatchQueryProps {
|
|
||||||
ownerAddress: string
|
|
||||||
tokenId: string
|
|
||||||
messages: CW721BaseInstance | undefined
|
|
||||||
type: QueryType
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dispatchQuery = async (props: DispatchQueryProps) => {
|
|
||||||
const { ownerAddress, tokenId, messages, type } = props
|
|
||||||
switch (type) {
|
|
||||||
case 'owner_of': {
|
|
||||||
return messages?.ownerOf(tokenId)
|
|
||||||
}
|
|
||||||
case 'approval': {
|
|
||||||
return messages?.approval(tokenId, ownerAddress)
|
|
||||||
}
|
|
||||||
case 'approvals': {
|
|
||||||
return messages?.approvals(tokenId)
|
|
||||||
}
|
|
||||||
case 'all_operators': {
|
|
||||||
return messages?.allOperators(ownerAddress)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
case 'tokens': {
|
|
||||||
return messages?.tokens(ownerAddress)
|
|
||||||
}
|
|
||||||
case 'all_tokens': {
|
|
||||||
return messages?.allTokens()
|
|
||||||
}
|
|
||||||
case 'minter': {
|
|
||||||
return messages?.minter()
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new Error('unknown query type')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
45
utils/isValidFile.ts
Normal file
45
utils/isValidFile.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
export const checkFiles = (images: string[], metadata: string[]) => {
|
||||||
|
// Check images length is equal to metadata length
|
||||||
|
if (images.length !== metadata.length) {
|
||||||
|
throw Error('Images files must have matching number of metadata files')
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFileName(path: string | null): number {
|
||||||
|
// Check file name is not null
|
||||||
|
if (!path) {
|
||||||
|
throw Error('File cannot be null')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract fileName from path
|
||||||
|
const fileName = path.match(
|
||||||
|
/([a-zA-Z0-9\s_\\.\-:]+)(.png|.jpg|.gif|.json)?$/i
|
||||||
|
)![1]
|
||||||
|
|
||||||
|
// Check that file name is an Integer
|
||||||
|
if (isNaN(parseInt(fileName, 10))) {
|
||||||
|
throw Error('Filenames must be numbers. Invalid fileName: ' + fileName)
|
||||||
|
}
|
||||||
|
return parseInt(fileName, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to ensure that the files are numerically sorted (as opposed to lexicographically)
|
||||||
|
const sortedImages = [...images.map(parseFileName)].sort(function (a, b) {
|
||||||
|
return a - b
|
||||||
|
})
|
||||||
|
const sortedMetadata = [...metadata.map(parseFileName)].sort(function (a, b) {
|
||||||
|
return a - b
|
||||||
|
})
|
||||||
|
let lastValue
|
||||||
|
// Check each image is sequentially named with a number and has a matching metadata file
|
||||||
|
for (let i = 0; i < sortedImages.length; i++) {
|
||||||
|
const image = sortedImages[i]
|
||||||
|
const json = sortedMetadata[i]
|
||||||
|
if (image !== json) {
|
||||||
|
throw Error('Images must have matching JSON files')
|
||||||
|
}
|
||||||
|
if (lastValue && lastValue + 1 !== image) {
|
||||||
|
throw Error('Images must be sequential')
|
||||||
|
}
|
||||||
|
lastValue = image
|
||||||
|
}
|
||||||
|
}
|
21
utils/sort.ts
Normal file
21
utils/sort.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
// https://stackoverflow.com/questions/15478954/sort-array-elements-string-with-numbers-natural-sort/15479354#15479354
|
||||||
|
export function naturalCompare(a: string, b: string) {
|
||||||
|
var ax = []
|
||||||
|
var bx = []
|
||||||
|
a.replace(/(\d+)|(\D+)/g, function (_, $1, $2) {
|
||||||
|
ax.push([$1 || Infinity, $2 || ''])
|
||||||
|
})
|
||||||
|
b.replace(/(\d+)|(\D+)/g, function (_, $1, $2) {
|
||||||
|
bx.push([$1 || Infinity, $2 || ''])
|
||||||
|
})
|
||||||
|
|
||||||
|
while (ax.length && bx.length) {
|
||||||
|
var an = ax.shift()
|
||||||
|
var bn = bx.shift()
|
||||||
|
var nn = an[0] - bn[0] || an[1].localeCompare(bn[1])
|
||||||
|
if (nn) return nn
|
||||||
|
}
|
||||||
|
|
||||||
|
return ax.length - bx.length
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user