Launchpad V2 sync (#34)

* V2 Sync

* v2 sync

* Launchpad V2 sync

* Update trading start time description

* Add explicit_content to CollectionDetails update dependencies

* Minor UI changes

* Update MintPriceMessage interface

* Add symbolState.value to CollectionDetails update dependencies

* Add external_link to Collection Details

* Remove the tab Instantiate from the minter contract dashboard

* Add price check for update_minting_price

* Implement dynamic per address limit check

* Add checks for trading start time

* Update Minter Contract Dashboard Instantiate Tab - 1

* Update Minter Contract Dashboard Instantiate Tab - 2

* Remove Instantiate tab from SG721 Contract Dashboard

* Update whitelist contract helpers

* Update whitelist instantiate fee wrt member limit

Co-authored-by: name-user1 <eray@deuslabs.fi>
Co-authored-by: Serkan Reis <serkanreis@gmail.com>
This commit is contained in:
name-user1 2022-10-21 04:02:52 +03:00 committed by GitHub
parent 2c8f66a5d6
commit 039b8b424b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 787 additions and 308 deletions

View File

@ -1,9 +1,10 @@
APP_VERSION=0.1.0
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
NEXT_PUBLIC_WHITELIST_CODE_ID=3
NEXT_PUBLIC_MINTER_CODE_ID=2
NEXT_PUBLIC_SG721_CODE_ID=1
NEXT_PUBLIC_SG721_CODE_ID=256
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=257
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1qdcxmc82uh8tqf56kprjddkfy7p4ft4z46kh9f6lhnjxtgekra5qjj5r6c"
NEXT_PUBLIC_WHITELIST_CODE_ID=267
NEXT_PUBLIC_API_URL=https://
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze

View File

@ -1,11 +1,6 @@
import type { LinkTabProps } from './LinkTab'
export const sg721LinkTabs: LinkTabProps[] = [
{
title: 'Instantiate',
description: `Create a new SG721 contract`,
href: '/contracts/sg721/instantiate',
},
{
title: 'Query',
description: `Dispatch queries with your SG721 contract`,

View File

@ -90,14 +90,22 @@ export const CollectionActions = ({
subtitle: 'Address of the whitelist contract',
})
const priceState = useNumberInputState({
id: 'update-mint-price',
name: 'updateMintPrice',
title: 'Update Mint Price',
subtitle: 'New minting price in STARS',
})
const showWhitelistField = type === 'set_whitelist'
const showDateField = type === 'update_start_time'
const showDateField = isEitherType(type, ['update_start_time', 'update_start_trading_time'])
const showLimitField = type === 'update_per_address_limit'
const showTokenIdField = isEitherType(type, ['transfer', 'mint_for', 'burn'])
const showNumberOfTokensField = type === 'batch_mint'
const showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer'])
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer'])
const showAirdropFileField = type === 'airdrop'
const showPriceField = type === 'update_mint_price'
const payload: DispatchExecuteArgs = {
whitelist: whitelistState.value,
@ -114,6 +122,7 @@ export const CollectionActions = ({
recipients: airdropArray,
txSigner: wallet.address,
type,
price: priceState.value.toString(),
}
useEffect(() => {
@ -140,6 +149,11 @@ export const CollectionActions = ({
if (minterContractAddress === '' && sg721ContractAddress === '') {
throw new Error('Please enter minter and sg721 contract addresses!')
}
if (type === 'update_mint_price' && priceState.value < 50) {
console.log('here')
throw new Error('Mint price must be at least 50 STARS')
}
const txHash = await toast.promise(dispatchExecute(payload), {
error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
loading: 'Executing message...',
@ -172,6 +186,7 @@ export const CollectionActions = ({
{showTokenIdField && <NumberInput {...tokenIdState} />}
{showTokenIdListField && <TextInput {...tokenIdListState} />}
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
{showPriceField && <NumberInput {...priceState} />}
{showAirdropFileField && (
<FormGroup
subtitle="CSV file that contains the airdrop addresses and the amount of tokens allocated for each address. Should start with the following header row: address,amount"

View File

@ -6,11 +6,15 @@ import { useSG721Contract } from 'contracts/sg721'
export type ActionType = typeof ACTION_TYPES[number]
export const ACTION_TYPES = [
'mint',
'purge',
'update_mint_price',
'mint_to',
'mint_for',
'batch_mint',
'set_whitelist',
'update_start_time',
'update_start_trading_time',
'update_per_address_limit',
'withdraw',
'transfer',
@ -19,6 +23,7 @@ export const ACTION_TYPES = [
'batch_burn',
'shuffle',
'airdrop',
'burn_remaining',
] as const
export interface ActionListItem {
@ -28,6 +33,21 @@ export interface ActionListItem {
}
export const ACTION_LIST: ActionListItem[] = [
{
id: 'mint',
name: 'Mint',
description: `Mint a token`,
},
{
id: 'purge',
name: 'Purge',
description: `Purge`,
},
{
id: 'update_mint_price',
name: 'Update Mint Price',
description: `Update mint price`,
},
{
id: 'mint_to',
name: 'Mint To',
@ -50,9 +70,14 @@ export const ACTION_LIST: ActionListItem[] = [
},
{
id: 'update_start_time',
name: 'Update Start Time',
name: 'Update Minting Start Time',
description: `Update start time for minting`,
},
{
id: 'update_start_trading_time',
name: 'Update Trading Start Time',
description: `Update start time for trading`,
},
{
id: 'update_per_address_limit',
name: 'Update Tokens Per Address Limit',
@ -93,6 +118,11 @@ export const ACTION_LIST: ActionListItem[] = [
name: 'Airdrop Tokens',
description: 'Airdrop tokens to given addresses',
},
{
id: 'burn_remaining',
name: 'Burn Remaining Tokens',
description: 'Burn remaining tokens',
},
]
export interface DispatchExecuteProps {
@ -111,11 +141,15 @@ export type DispatchExecuteArgs = {
txSigner: string
} & (
| { type: undefined }
| { type: Select<'mint'> }
| { type: Select<'purge'> }
| { type: Select<'update_mint_price'>; price: string }
| { type: Select<'mint_to'>; recipient: string }
| { type: Select<'mint_for'>; recipient: string; tokenId: number }
| { type: Select<'batch_mint'>; recipient: string; batchNumber: number }
| { type: Select<'set_whitelist'>; whitelist: string }
| { type: Select<'update_start_time'>; startTime: string }
| { type: Select<'update_start_trading_time'>; startTime: string }
| { type: Select<'update_per_address_limit'>; limit: number }
| { type: Select<'shuffle'> }
| { type: Select<'withdraw'> }
@ -124,6 +158,7 @@ export type DispatchExecuteArgs = {
| { type: Select<'burn'>; tokenId: number }
| { type: Select<'batch_burn'>; tokenIds: string }
| { type: Select<'airdrop'>; recipients: string[] }
| { type: Select<'burn_remaining'> }
)
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
@ -132,6 +167,15 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
throw new Error('Cannot execute actions')
}
switch (args.type) {
case 'mint': {
return minterMessages.mint(txSigner)
}
case 'purge': {
return minterMessages.purge(txSigner)
}
case 'update_mint_price': {
return minterMessages.updateMintPrice(txSigner, args.price)
}
case 'mint_to': {
return minterMessages.mintTo(txSigner, args.recipient)
}
@ -147,6 +191,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'update_start_time': {
return minterMessages.updateStartTime(txSigner, args.startTime)
}
case 'update_start_trading_time': {
return minterMessages.updateStartTradingTime(txSigner, args.startTime)
}
case 'update_per_address_limit': {
return minterMessages.updatePerAddressLimit(txSigner, args.limit)
}
@ -171,6 +218,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'airdrop': {
return minterMessages.airdrop(txSigner, args.recipients)
}
case 'burn_remaining': {
return minterMessages.burnRemaining(txSigner)
}
default: {
throw new Error('Unknown action')
}
@ -184,6 +234,15 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
const { messages: sg721Messages } = useSG721Contract()
const { minterContract, sg721Contract } = args
switch (args.type) {
case 'mint': {
return minterMessages(minterContract)?.mint()
}
case 'purge': {
return minterMessages(minterContract)?.purge()
}
case 'update_mint_price': {
return minterMessages(minterContract)?.updateMintPrice(args.price)
}
case 'mint_to': {
return minterMessages(minterContract)?.mintTo(args.recipient)
}
@ -199,6 +258,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'update_start_time': {
return minterMessages(minterContract)?.updateStartTime(args.startTime)
}
case 'update_start_trading_time': {
return minterMessages(minterContract)?.updateStartTradingTime(args.startTime)
}
case 'update_per_address_limit': {
return minterMessages(minterContract)?.updatePerAddressLimit(args.limit)
}
@ -223,6 +285,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'airdrop': {
return minterMessages(minterContract)?.airdrop(args.recipients)
}
case 'burn_remaining': {
return minterMessages(minterContract)?.burnRemaining()
}
default: {
return {}
}

View File

@ -7,6 +7,7 @@ import clsx from 'clsx'
import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup'
import { useInputState } from 'components/forms/FormInput.hooks'
import { InputDateTime } from 'components/InputDateTime'
import type { ChangeEvent } from 'react'
import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
@ -26,10 +27,14 @@ export interface CollectionDetailsDataProps {
symbol: string
imageFile: File[]
externalLink?: string
startTradingTime?: string
explicit: boolean
}
export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: CollectionDetailsProps) => {
const [coverImage, setCoverImage] = useState<File | null>(null)
const [timestamp, setTimestamp] = useState<Date | undefined>()
const [explicit, setExplicit] = useState<boolean>(false)
const nameState = useInputState({
id: 'name',
@ -67,6 +72,8 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
symbol: symbolState.value,
imageFile: coverImage ? [coverImage] : [],
externalLink: externalLinkState.value,
startTradingTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
explicit,
}
onChange(data)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -74,7 +81,15 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
toast.error(error.message)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nameState.value, descriptionState.value, coverImage, externalLinkState.value])
}, [
nameState.value,
descriptionState.value,
symbolState.value,
externalLinkState.value,
coverImage,
timestamp,
explicit,
])
const selectCoverImage = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files === null) return toast.error('Error selecting cover image')
@ -92,12 +107,20 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
reader.readAsArrayBuffer(event.target.files[0])
}
useEffect(() => {
console.log(explicit)
}, [explicit])
return (
<div>
<FormGroup subtitle="Information about your collection" title="Collection Details">
<TextInput {...nameState} isRequired />
<TextInput {...descriptionState} isRequired />
<TextInput {...symbolState} isRequired />
<TextInput {...externalLinkState} />
<FormControl htmlId="timestamp" subtitle="Trading start time (local)" title="Trading Start Time (optional)">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
</FormControl>
<FormControl isRequired={uploadMethod === 'new'} title="Cover Image">
{uploadMethod === 'new' && (
@ -135,8 +158,51 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
<span className="italic font-light ">Waiting for cover image URL to be specified.</span>
)}
</FormControl>
<TextInput {...externalLinkState} />
<div className="flex flex-col space-y-2">
<div>
<div className="flex">
<span className="mt-1 text-sm first-letter:capitalize">
Does the collection contain explicit content?
</span>
<div className="ml-2 font-bold form-check form-check-inline">
<input
checked={explicit}
className="peer sr-only"
id="explicitRadio1"
name="explicitRadioOptions1"
onClick={() => {
setExplicit(true)
}}
type="radio"
/>
<label
className="inline-block py-1 px-2 text-sm text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
htmlFor="explicitRadio1"
>
YES
</label>
</div>
<div className="ml-2 font-bold form-check form-check-inline">
<input
checked={!explicit}
className="peer sr-only"
id="explicitRadio2"
name="explicitRadioOptions2"
onClick={() => {
setExplicit(false)
}}
type="radio"
/>
<label
className="inline-block py-1 px-2 text-sm text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
htmlFor="explicitRadio2"
>
NO
</label>
</div>
</div>
</div>
</div>
</FormGroup>
</div>
)

View File

@ -69,6 +69,26 @@ export const TextInput = forwardRef<HTMLInputElement, FormInputProps>(
//
)
export const CheckBoxInput = forwardRef<HTMLInputElement, FormInputProps>(
function CheckBoxInput(props, ref) {
return (
<div className="flex flex-col space-y-2">
<label className="flex flex-col space-y-1" htmlFor="explicit">
<span className="font-bold first-letter:capitalize">Explicit Content</span>
</label>
<input
className="placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
id="explicit"
name="explicit"
type="checkbox"
value=""
/>
</div>
)
},
//
)
export const UrlInput = forwardRef<HTMLInputElement, FormInputProps>(
function UrlInput(props, ref) {
return <FormInput {...props} ref={ref} type="url" />

View File

@ -2,6 +2,8 @@ import type { UseMinterContractProps } from 'contracts/minter'
import { useMinterContract } from 'contracts/minter'
import type { UseSG721ContractProps } from 'contracts/sg721'
import { useSG721Contract } from 'contracts/sg721'
import type { UseVendingFactoryContractProps } from 'contracts/vendingFactory'
import { useVendingFactoryContract } from 'contracts/vendingFactory'
import type { UseWhiteListContractProps } from 'contracts/whitelist'
import { useWhiteListContract } from 'contracts/whitelist'
import type { ReactNode, VFC } from 'react'
@ -16,6 +18,7 @@ export interface ContractsStore extends State {
sg721: UseSG721ContractProps | null
minter: UseMinterContractProps | null
whitelist: UseWhiteListContractProps | null
vendingFactory: UseVendingFactoryContractProps | null
}
/**
@ -25,6 +28,7 @@ export const defaultValues: ContractsStore = {
sg721: null,
minter: null,
whitelist: null,
vendingFactory: null,
}
/**
@ -51,14 +55,16 @@ const ContractsSubscription: VFC = () => {
const sg721 = useSG721Contract()
const minter = useMinterContract()
const whitelist = useWhiteListContract()
const vendingFactory = useVendingFactoryContract()
useEffect(() => {
useContracts.setState({
sg721,
minter,
whitelist,
vendingFactory,
})
}, [sg721, minter, whitelist])
}, [sg721, minter, whitelist, vendingFactory])
return null
}

View File

@ -28,9 +28,12 @@ export interface MinterInstance {
getMintCount: (address: string) => Promise<any>
//Execute
mint: (senderAddress: string, price: string) => Promise<string>
mint: (senderAddress: string) => Promise<string>
purge: (senderAddress: string) => Promise<string>
updateMintPrice: (senderAddress: string, price: string) => Promise<string>
setWhitelist: (senderAddress: string, whitelist: string) => Promise<string>
updateStartTime: (senderAddress: string, time: Timestamp) => Promise<string>
updateStartTradingTime: (senderAddress: string, time: Timestamp) => Promise<string>
updatePerAddressLimit: (senderAddress: string, perAddressLimit: number) => Promise<string>
mintTo: (senderAddress: string, recipient: string) => Promise<string>
mintFor: (senderAddress: string, recipient: string, tokenId: number) => Promise<string>
@ -38,12 +41,16 @@ export interface MinterInstance {
shuffle: (senderAddress: string) => Promise<string>
withdraw: (senderAddress: string) => Promise<string>
airdrop: (senderAddress: string, recipients: string[]) => Promise<string>
burnRemaining: (senderAddress: string) => Promise<string>
}
export interface MinterMessages {
mint: (price: string) => MintMessage
mint: () => MintMessage
purge: () => PurgeMessage
updateMintPrice: (price: string) => UpdateMintPriceMessage
setWhitelist: (whitelist: string) => SetWhitelistMessage
updateStartTime: (time: Timestamp) => UpdateStarTimeMessage
updateStartTime: (time: Timestamp) => UpdateStartTimeMessage
updateStartTradingTime: (time: Timestamp) => UpdateStartTradingTimeMessage
updatePerAddressLimit: (perAddressLimit: number) => UpdatePerAddressLimitMessage
mintTo: (recipient: string) => MintToMessage
mintFor: (recipient: string, tokenId: number) => MintForMessage
@ -51,6 +58,7 @@ export interface MinterMessages {
shuffle: () => ShuffleMessage
withdraw: () => WithdrawMessage
airdrop: (recipients: string[]) => CustomMessage
burnRemaining: () => BurnRemainingMessage
}
export interface MintMessage {
@ -62,6 +70,26 @@ export interface MintMessage {
funds: Coin[]
}
export interface PurgeMessage {
sender: string
contract: string
msg: {
purge: Record<string, never>
}
funds: Coin[]
}
export interface UpdateMintPriceMessage {
sender: string
contract: string
msg: {
update_mint_price: {
price: string
}
}
funds: Coin[]
}
export interface SetWhitelistMessage {
sender: string
contract: string
@ -73,7 +101,7 @@ export interface SetWhitelistMessage {
funds: Coin[]
}
export interface UpdateStarTimeMessage {
export interface UpdateStartTimeMessage {
sender: string
contract: string
msg: {
@ -82,6 +110,15 @@ export interface UpdateStarTimeMessage {
funds: Coin[]
}
export interface UpdateStartTradingTimeMessage {
sender: string
contract: string
msg: {
update_start_trading_time: string
}
funds: Coin[]
}
export interface UpdatePerAddressLimitMessage {
sender: string
contract: string
@ -141,6 +178,34 @@ export interface WithdrawMessage {
funds: Coin[]
}
export interface BurnRemainingMessage {
sender: string
contract: string
msg: {
burn_remaining: Record<string, never>
}
funds: Coin[]
}
export interface MintPriceMessage {
public_price: {
denom: string
amount: string
}
airdrop_price: {
denom: string
amount: string
}
whitelist_price?: {
denom: string
amount: string
}
current_price: {
denom: string
amount: string
}
}
export interface MinterContract {
instantiate: (
senderAddress: string,
@ -180,7 +245,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
return res
}
const getMintPrice = async (): Promise<any> => {
const getMintPrice = async (): Promise<MintPriceMessage> => {
const res = await client.queryContractSmart(contractAddress, {
mint_price: {},
})
@ -195,7 +260,8 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
//Execute
const mint = async (senderAddress: string, price: string): Promise<string> => {
const mint = async (senderAddress: string): Promise<string> => {
const price = (await getMintPrice()).public_price.amount
const res = await client.execute(
senderAddress,
contractAddress,
@ -210,6 +276,36 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
return res.transactionHash
}
const purge = async (senderAddress: string): Promise<string> => {
const res = await client.execute(
senderAddress,
contractAddress,
{
purge: {},
},
'auto',
'',
)
return res.transactionHash
}
const updateMintPrice = async (senderAddress: string, price: string): Promise<string> => {
const res = await client.execute(
senderAddress,
contractAddress,
{
update_mint_price: {
price: (Number(price) * 1000000).toString(),
},
},
'auto',
'',
)
return res.transactionHash
}
const setWhitelist = async (senderAddress: string, whitelist: string): Promise<string> => {
const res = await client.execute(
senderAddress,
@ -238,6 +334,20 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
return res.transactionHash
}
const updateStartTradingTime = async (senderAddress: string, time: Timestamp): Promise<string> => {
const res = await client.execute(
senderAddress,
contractAddress,
{
update_start_trading_time: { time },
},
'auto',
'',
)
return res.transactionHash
}
const updatePerAddressLimit = async (senderAddress: string, perAddressLimit: number): Promise<string> => {
const res = await client.execute(
senderAddress,
@ -335,6 +445,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
},
'auto',
'',
[coin(500000000, 'ustars')],
)
return res.transactionHash
@ -354,6 +465,20 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
return res.transactionHash
}
const burnRemaining = async (senderAddress: string): Promise<string> => {
const res = await client.execute(
senderAddress,
contractAddress,
{
burn_remaining: {},
},
'auto',
'',
)
return res.transactionHash
}
return {
contractAddress,
getConfig,
@ -362,8 +487,11 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
getMintPrice,
getMintCount,
mint,
purge,
updateMintPrice,
setWhitelist,
updateStartTime,
updateStartTradingTime,
updatePerAddressLimit,
mintTo,
mintFor,
@ -371,6 +499,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
airdrop,
shuffle,
withdraw,
burnRemaining,
}
}
@ -392,14 +521,38 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
const messages = (contractAddress: string) => {
const mint = (price: string): MintMessage => {
const mint = (): MintMessage => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
mint: {},
},
funds: [coin(price, 'ustars')],
funds: [],
}
}
const purge = (): PurgeMessage => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
purge: {},
},
funds: [],
}
}
const updateMintPrice = (price: string): UpdateMintPriceMessage => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_mint_price: {
price: (Number(price) * 1000000).toString(),
},
},
funds: [],
}
}
@ -416,7 +569,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const updateStartTime = (startTime: string): UpdateStarTimeMessage => {
const updateStartTime = (startTime: string): UpdateStartTimeMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -427,6 +580,17 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const updateStartTradingTime = (startTime: string): UpdateStartTradingTimeMessage => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_start_trading_time: startTime,
},
funds: [],
}
}
const updatePerAddressLimit = (limit: number): UpdatePerAddressLimitMessage => {
return {
sender: txSigner,
@ -515,10 +679,24 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const burnRemaining = (): BurnRemainingMessage => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
burn_remaining: {},
},
funds: [],
}
}
return {
mint,
purge,
updateMintPrice,
setWhitelist,
updateStartTime,
updateStartTradingTime,
updatePerAddressLimit,
mintTo,
mintFor,
@ -526,6 +704,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
airdrop,
shuffle,
withdraw,
burnRemaining,
}
}

View File

@ -5,13 +5,17 @@ export type ExecuteType = typeof EXECUTE_TYPES[number]
export const EXECUTE_TYPES = [
'mint',
'purge',
'update_mint_price',
'set_whitelist',
'update_start_time',
'update_start_trading_time',
'update_per_address_limit',
'mint_to',
'mint_for',
'shuffle',
'withdraw',
'burn_remaining',
] as const
export interface ExecuteListItem {
@ -26,6 +30,16 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
name: 'Mint',
description: `Mint new tokens for a given address`,
},
{
id: 'purge',
name: 'Purge',
description: `Purge`,
},
{
id: 'update_mint_price',
name: 'Update Mint Price',
description: `Update mint price`,
},
{
id: 'set_whitelist',
name: 'Set Whitelist',
@ -36,6 +50,11 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
name: 'Update Start Time',
description: `Update start time for minting`,
},
{
id: 'update_start_trading_time',
name: 'Update Start Trading Time',
description: `Update start trading time for minting`,
},
{
id: 'update_per_address_limit',
name: 'Update Per Address Limit',
@ -56,6 +75,11 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
name: 'Shuffle',
description: `Shuffle the token IDs`,
},
{
id: 'burn_remaining',
name: 'Burn Remaining',
description: `Burn remaining tokens`,
},
]
export interface DispatchExecuteProps {
@ -72,14 +96,18 @@ export type DispatchExecuteArgs = {
txSigner: string
} & (
| { type: undefined }
| { type: Select<'mint'>; price: string }
| { type: Select<'mint'> }
| { type: Select<'purge'> }
| { type: Select<'update_mint_price'>; price: string }
| { type: Select<'set_whitelist'>; whitelist: string }
| { type: Select<'update_start_time'>; startTime: string }
| { type: Select<'update_start_trading_time'>; startTime: string }
| { type: Select<'update_per_address_limit'>; limit: number }
| { type: Select<'mint_to'>; recipient: string }
| { type: Select<'mint_for'>; recipient: string; tokenId: number }
| { type: Select<'shuffle'> }
| { type: Select<'withdraw'> }
| { type: Select<'burn_remaining'> }
)
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
@ -89,7 +117,13 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
}
switch (args.type) {
case 'mint': {
return messages.mint(txSigner, args.price === '' ? '0' : args.price)
return messages.mint(txSigner)
}
case 'purge': {
return messages.purge(txSigner)
}
case 'update_mint_price': {
return messages.updateMintPrice(txSigner, args.price)
}
case 'set_whitelist': {
return messages.setWhitelist(txSigner, args.whitelist)
@ -97,6 +131,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'update_start_time': {
return messages.updateStartTime(txSigner, args.startTime)
}
case 'update_start_trading_time': {
return messages.updateStartTradingTime(txSigner, args.startTime)
}
case 'update_per_address_limit': {
return messages.updatePerAddressLimit(txSigner, args.limit)
}
@ -112,6 +149,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'withdraw': {
return messages.withdraw(txSigner)
}
case 'burn_remaining': {
return messages.burnRemaining(txSigner)
}
default: {
throw new Error('unknown execute type')
}
@ -124,7 +164,13 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
const { contract } = args
switch (args.type) {
case 'mint': {
return messages(contract)?.mint(args.price === '' ? '0' : args.price)
return messages(contract)?.mint()
}
case 'purge': {
return messages(contract)?.purge()
}
case 'update_mint_price': {
return messages(contract)?.updateMintPrice(args.price)
}
case 'set_whitelist': {
return messages(contract)?.setWhitelist(args.whitelist)
@ -132,6 +178,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'update_start_time': {
return messages(contract)?.updateStartTime(args.startTime)
}
case 'update_start_trading_time': {
return messages(contract)?.updateStartTradingTime(args.startTime)
}
case 'update_per_address_limit': {
return messages(contract)?.updatePerAddressLimit(args.limit)
}
@ -147,6 +196,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'withdraw': {
return messages(contract)?.withdraw()
}
case 'burn_remaining': {
return messages(contract)?.burnRemaining()
}
default: {
return {}
}

View File

@ -0,0 +1,82 @@
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import type { Coin } from '@cosmjs/proto-signing'
import { coin } from '@cosmjs/proto-signing'
import type { logs } from '@cosmjs/stargate'
import { VENDING_FACTORY_ADDRESS } from 'utils/constants'
export interface CreateMinterResponse {
readonly minterAddress: string
readonly sg721Address: string
readonly transactionHash: string
readonly logs: readonly logs.Log[]
}
export interface VendingFactoryInstance {
readonly contractAddress: string
//Query
//Execute
createMinter: (senderAddress: string, msg: Record<string, unknown>, funds: Coin[]) => Promise<CreateMinterResponse>
}
export interface VendingFactoryMessages {
createMinter: (msg: Record<string, unknown>) => CreateMinterMessage
}
export interface CreateMinterMessage {
sender: string
contract: string
msg: Record<string, unknown>
funds: Coin[]
}
export interface VendingFactoryContract {
use: (contractAddress: string) => VendingFactoryInstance
messages: (contractAddress: string) => VendingFactoryMessages
}
export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string): VendingFactoryContract => {
const use = (contractAddress: string): VendingFactoryInstance => {
//Query
//Execute
const createMinter = async (
senderAddress: string,
msg: Record<string, unknown>,
funds: Coin[],
): Promise<CreateMinterResponse> => {
const result = await client.execute(senderAddress, VENDING_FACTORY_ADDRESS, msg, 'auto', '', funds)
return {
minterAddress: result.logs[0].events[5].attributes[0].value,
sg721Address: result.logs[0].events[5].attributes[2].value,
transactionHash: result.transactionHash,
logs: result.logs,
}
}
return {
contractAddress,
createMinter,
}
}
const messages = (contractAddress: string) => {
const createMinter = (msg: Record<string, unknown>): CreateMinterMessage => {
return {
sender: txSigner,
contract: contractAddress,
msg,
funds: [coin('1000000000', 'ustars')],
}
}
return {
createMinter,
}
}
return { use, messages }
}

View File

@ -0,0 +1,2 @@
export * from './contract'
export * from './useContract'

View File

@ -0,0 +1,28 @@
import type { Coin } from '@cosmjs/proto-signing'
import type { VendingFactoryInstance } from '../index'
import { useVendingFactoryContract } from '../index'
/** @see {@link VendingFactoryInstance} */
export interface DispatchExecuteArgs {
contract: string
messages?: VendingFactoryInstance
txSigner: string
msg: Record<string, unknown>
funds: Coin[]
}
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
const { messages, txSigner } = args
if (!messages) {
throw new Error('cannot dispatch execute, messages is not defined')
}
return messages.createMinter(txSigner, args.msg, args.funds)
}
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages } = useVendingFactoryContract()
const { contract } = args
return messages(contract)?.createMinter(args.msg)
}

View File

@ -0,0 +1,76 @@
import type { logs } from '@cosmjs/stargate'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import type { VendingFactoryContract, VendingFactoryInstance, VendingFactoryMessages } from './contract'
import { vendingFactory as initContract } 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 UseVendingFactoryContractProps {
use: (customAddress: string) => VendingFactoryInstance | undefined
updateContractAddress: (contractAddress: string) => void
getContractAddress: () => string | undefined
messages: (contractAddress: string) => VendingFactoryMessages | undefined
}
export function useVendingFactoryContract(): UseVendingFactoryContractProps {
const wallet = useWallet()
const [address, setAddress] = useState<string>('')
const [vendingFactory, setVendingFactory] = useState<VendingFactoryContract>()
useEffect(() => {
setAddress(localStorage.getItem('contract_address') || '')
}, [])
useEffect(() => {
const VendingFactoryBaseContract = initContract(wallet.getClient(), wallet.address)
setVendingFactory(VendingFactoryBaseContract)
}, [wallet])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
}
const use = useCallback(
(customAddress = ''): VendingFactoryInstance | undefined => {
return vendingFactory?.use(address || customAddress)
},
[vendingFactory, address],
)
const getContractAddress = (): string | undefined => {
return address
}
const messages = useCallback(
(customAddress = ''): VendingFactoryMessages | undefined => {
return vendingFactory?.messages(address || customAddress)
},
[vendingFactory, address],
)
return {
use,
updateContractAddress,
getContractAddress,
messages,
}
}

View File

@ -13,7 +13,7 @@ export interface ConfigResponse {
readonly member_limit: number
readonly start_time: string
readonly end_time: string
readonly unit_price: Coin
readonly mint_price: Coin
readonly is_active: boolean
}
export interface WhiteListInstance {
@ -219,7 +219,7 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
admin?: string,
): Promise<InstantiateResponse> => {
const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
funds: [coin('100000000', 'ustars')],
funds: [coin((Math.ceil(Number(initMsg.member_limit) / 1000) * 100000000).toString(), 'ustars')],
admin,
})

18
env.
View File

@ -1,18 +0,0 @@
APP_VERSION=0.1.0
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
NEXT_PUBLIC_WHITELIST_CODE_ID=3
NEXT_PUBLIC_MINTER_CODE_ID=2
NEXT_PUBLIC_SG721_CODE_ID=1
NEXT_PUBLIC_API_URL=https://
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze
NEXT_PUBLIC_NETWORK=testnet
NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev
NEXT_PUBLIC_WEBSITE_URL=https://
NEXT_PUBLIC_S3_BUCKET= # TODO
NEXT_PUBLIC_S3_ENDPOINT= # TODO
NEXT_PUBLIC_S3_KEY= # TODO
NEXT_PUBLIC_S3_REGION= # TODO
NEXT_PUBLIC_S3_SECRET= # TODO

3
env.d.ts vendored
View File

@ -15,8 +15,9 @@ declare namespace NodeJS {
readonly APP_VERSION: string
readonly NEXT_PUBLIC_SG721_CODE_ID: string
readonly NEXT_PUBLIC_MINTER_CODE_ID: string
readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string
readonly NEXT_PUBLIC_VENDING_MINTER_CODE_ID: string
readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_PINATA_ENDPOINT_URL: string
readonly NEXT_PUBLIC_API_URL: string

View File

@ -23,18 +23,20 @@ import { Conditional } from 'components/Conditional'
import { LoadingModal } from 'components/LoadingModal'
import { useContracts } from 'contexts/contracts'
import { useWallet } from 'contexts/wallet'
import type { DispatchExecuteArgs } from 'contracts/vendingFactory/messages/execute'
import { dispatchExecute } from 'contracts/vendingFactory/messages/execute'
import type { NextPage } from 'next'
import { NextSeo } from 'next-seo'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { upload } from 'services/upload'
import { compareFileArrays } from 'utils/compareFileArrays'
import {
BLOCK_EXPLORER_URL,
MINTER_CODE_ID,
NETWORK,
SG721_CODE_ID,
STARGAZE_URL,
VENDING_FACTORY_ADDRESS,
WHITELIST_CODE_ID,
} from 'utils/constants'
import { withMetadata } from 'utils/layout'
@ -46,8 +48,16 @@ import { getAssetType } from '../../utils/getAssetType'
const CollectionCreationPage: NextPage = () => {
const wallet = useWallet()
const { minter: minterContract, whitelist: whitelistContract } = useContracts()
const {
minter: minterContract,
whitelist: whitelistContract,
vendingFactory: vendingFactoryContract,
} = useContracts()
const scrollRef = useRef<HTMLDivElement>(null)
const messages = useMemo(
() => vendingFactoryContract?.use(VENDING_FACTORY_ADDRESS),
[vendingFactoryContract, wallet.address],
)
const [uploadDetails, setUploadDetails] = useState<UploadDetailsDataProps | null>(null)
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
@ -148,7 +158,7 @@ const CollectionCreationPage: NextPage = () => {
members: whitelistDetails?.members,
start_time: whitelistDetails?.startTime,
end_time: whitelistDetails?.endTime,
unit_price: coin(String(Number(whitelistDetails?.unitPrice)), 'ustars'),
mint_price: coin(String(Number(whitelistDetails?.unitPrice)), 'ustars'),
per_address_limit: whitelistDetails?.perAddressLimit,
member_limit: whitelistDetails?.memberLimit,
}
@ -176,35 +186,50 @@ const CollectionCreationPage: NextPage = () => {
}
const msg = {
base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}/` : `${baseUri}`}`,
num_tokens: mintingDetails?.numTokens,
sg721_code_id: SG721_CODE_ID,
sg721_instantiate_msg: {
name: collectionDetails?.name,
symbol: collectionDetails?.symbol,
minter: wallet.address,
collection_info: {
creator: wallet.address,
description: collectionDetails?.description,
image: `${
uploadDetails?.uploadMethod === 'new'
? `ipfs://${coverImageUri}/${collectionDetails?.imageFile[0].name as string}`
: `${coverImageUri}`
}`,
external_link: collectionDetails?.externalLink === '' ? null : collectionDetails?.externalLink,
royalty_info: royaltyInfo,
create_minter: {
init_msg: {
base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}/` : `${baseUri}`}`,
start_time: mintingDetails?.startTime,
num_tokens: mintingDetails?.numTokens,
mint_price: {
amount: mintingDetails?.unitPrice,
denom: 'ustars',
},
per_address_limit: mintingDetails?.perAddressLimit,
whitelist,
},
collection_params: {
code_id: SG721_CODE_ID,
name: collectionDetails?.name,
symbol: collectionDetails?.symbol,
info: {
creator: wallet.address,
description: collectionDetails?.description,
image: `${
uploadDetails?.uploadMethod === 'new'
? `ipfs://${coverImageUri}/${collectionDetails?.imageFile[0].name as string}`
: `${coverImageUri}`
}`,
external_link: collectionDetails?.externalLink,
explicit_content: collectionDetails?.explicit,
royalty_info: royaltyInfo,
start_trading_time: collectionDetails?.startTradingTime || null,
},
},
},
per_address_limit: mintingDetails?.perAddressLimit,
unit_price: coin(String(Number(mintingDetails?.unitPrice)), 'ustars'),
whitelist,
start_time: mintingDetails?.startTime,
}
const data = await minterContract.instantiate(MINTER_CODE_ID, msg, 'Stargaze Minter Contract', wallet.address)
const payload: DispatchExecuteArgs = {
contract: VENDING_FACTORY_ADDRESS,
messages,
txSigner: wallet.address,
msg,
funds: [coin('1000000000', 'ustars')],
}
const data = await dispatchExecute(payload)
setTransactionHash(data.transactionHash)
setMinterContractAddress(data.contractAddress)
setSg721ContractAddress(data.logs[0].events[3].attributes[2].value)
setMinterContractAddress(data.minterAddress)
setSg721ContractAddress(data.sg721Address)
}
const uploadFiles = async (): Promise<string> => {
@ -302,6 +327,16 @@ const CollectionCreationPage: NextPage = () => {
if (collectionDetails.description === '') throw new Error('Collection description is required')
if (uploadDetails?.uploadMethod === 'new' && collectionDetails.imageFile.length === 0)
throw new Error('Collection cover image is required')
if (
collectionDetails.startTradingTime &&
Number(collectionDetails.startTradingTime) < new Date().getTime() * 1000000
)
throw new Error('Invalid trading start time')
if (
collectionDetails.startTradingTime &&
Number(collectionDetails.startTradingTime) < Number(mintingDetails?.startTime)
)
throw new Error('Trading start time must be after minting start time')
}
const checkMintingDetails = () => {
@ -315,6 +350,12 @@ const CollectionCreationPage: NextPage = () => {
mintingDetails.perAddressLimit > mintingDetails.numTokens
)
throw new Error('Invalid limit for tokens per address')
if (
mintingDetails.numTokens > 100 &&
mintingDetails.numTokens < 100 * mintingDetails.perAddressLimit &&
mintingDetails.perAddressLimit > mintingDetails.numTokens / 100
)
throw new Error('Invalid limit for tokens per address. The limit cannot exceed 1% of the total number of tokens.')
if (mintingDetails.startTime === '') throw new Error('Start time is required')
if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time')
}

View File

@ -14,7 +14,6 @@ import { LinkTabs } from 'components/LinkTabs'
import { minterLinkTabs } from 'components/LinkTabs.data'
import { useContracts } from 'contexts/contracts'
import { useWallet } from 'contexts/wallet'
import type { InstantiateResponse } from 'contracts/minter'
import type { NextPage } from 'next'
import { NextSeo } from 'next-seo'
import type { FormEvent } from 'react'
@ -22,15 +21,19 @@ import { useState } from 'react'
import { toast } from 'react-hot-toast'
import { FaAsterisk } from 'react-icons/fa'
import { useMutation } from 'react-query'
import { MINTER_CODE_ID } from 'utils/constants'
import { VENDING_FACTORY_ADDRESS } from 'utils/constants'
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
import type { CreateMinterResponse } from '../../../contracts/vendingFactory/contract'
const MinterInstantiatePage: NextPage = () => {
const wallet = useWallet()
const contract = useContracts().minter
const contract = useContracts().vendingFactory
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
const [timestamp, setTimestamp] = useState<Date | undefined>()
const [explicit, setExplicit] = useState<boolean>(false)
const nameState = useInputState({
id: 'name',
@ -48,14 +51,6 @@ const MinterInstantiatePage: NextPage = () => {
subtitle: 'Symbol of the sg721 contract',
})
const minterState = useInputState({
id: 'minter-address',
name: 'minterAddress',
title: 'Minter Address',
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
subtitle: 'Address that has the permissions to mint on sg721 contract',
})
const codeIdState = useNumberInputState({
id: 'code-id',
name: 'code-id',
@ -102,12 +97,12 @@ const MinterInstantiatePage: NextPage = () => {
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
})
const royaltyShareState = useNumberInputState({
const royaltyShareState = useInputState({
id: 'royalty-share',
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '8',
placeholder: '8%',
})
const unitPriceState = useNumberInputState({
@ -115,7 +110,7 @@ const MinterInstantiatePage: NextPage = () => {
name: 'unitPrice',
title: 'Unit Price',
subtitle: 'Price of each tokens in collection',
placeholder: '500',
placeholder: '50',
})
const baseTokenUriState = useInputState({
@ -151,7 +146,7 @@ const MinterInstantiatePage: NextPage = () => {
})
const { data, isLoading, mutate } = useMutation(
async (event: FormEvent): Promise<InstantiateResponse | null> => {
async (event: FormEvent): Promise<CreateMinterResponse | null> => {
event.preventDefault()
if (!contract) {
throw new Error('Smart contract connection failed')
@ -160,8 +155,8 @@ const MinterInstantiatePage: NextPage = () => {
let royaltyInfo = null
if (royaltyPaymentAddressState.value && royaltyShareState.value) {
royaltyInfo = {
paymentAddress: royaltyPaymentAddressState.value,
share: royaltyShareState.value,
payment_address: royaltyPaymentAddressState.value,
share: (Number(royaltyShareState.value) / 100).toString(),
}
}
@ -173,8 +168,8 @@ const MinterInstantiatePage: NextPage = () => {
throw new Error('Per address limit must be between 1 and 50')
}
if (Number(unitPriceState.value) < 500) {
throw new Error('Unit price must be greater than 500 STARS')
if (Number(unitPriceState.value) < 50) {
throw new Error('Unit price must be greater than 50 STARS')
}
if (!startDate) {
@ -182,31 +177,42 @@ const MinterInstantiatePage: NextPage = () => {
}
const msg = {
base_token_uri: baseTokenUriState.value,
num_tokens: tokenNumberState.value,
sg721_code_id: codeIdState.value,
sg721_instantiate_msg: {
name: nameState.value,
symbol: symbolState.value,
minter: minterState.value,
collection_info: {
creator: creatorState.value,
description: descriptionState.value,
image: imageState.value,
external_link: externalLinkState.value || null,
royalty_info: royaltyInfo,
create_minter: {
init_msg: {
base_token_uri: baseTokenUriState.value,
start_time: (startDate.getTime() * 1_000_000).toString(),
num_tokens: tokenNumberState.value,
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
per_address_limit: perAddressLimitState.value,
whitelist: whitelistAddressState.value || null,
},
collection_params: {
code_id: codeIdState.value,
name: nameState.value,
symbol: symbolState.value,
info: {
creator: creatorState.value,
description: descriptionState.value,
image: imageState.value,
external_link: externalLinkState.value || null,
explicit_content: explicit,
start_trading_time: timestamp ? (timestamp.getTime() * 1_000_000).toString() : null,
royalty_info: royaltyInfo,
},
},
},
per_address_limit: perAddressLimitState.value,
unit_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
whitelist_address: whitelistAddressState.value || null,
start_time: (startDate.getTime() * 1_000_000).toString(),
}
return toast.promise(contract.instantiate(MINTER_CODE_ID, msg, 'Stargaze Minter Contract', wallet.address), {
loading: 'Instantiating contract...',
error: 'Instantiation failed!',
success: 'Instantiation success!',
})
return toast.promise(
contract
.use(VENDING_FACTORY_ADDRESS)
?.createMinter(wallet.address, msg, [coin('1000000000', 'ustars')]) as Promise<CreateMinterResponse>,
{
loading: 'Instantiating contract...',
error: 'Instantiation failed!',
success: 'Instantiation success!',
},
)
},
{
onError: (error) => {
@ -240,7 +246,6 @@ const MinterInstantiatePage: NextPage = () => {
<NumberInput isRequired {...codeIdState} />
<TextInput isRequired {...nameState} />
<TextInput isRequired {...symbolState} />
<TextInput isRequired {...minterState} />
</FormGroup>
<FormGroup subtitle="Information about your collection" title="Collection Details">
@ -248,6 +253,54 @@ const MinterInstantiatePage: NextPage = () => {
<FormTextArea isRequired {...descriptionState} />
<TextInput isRequired {...imageState} />
<TextInput {...externalLinkState} />
<FormControl htmlId="timestamp" subtitle="Trading start time (local)" title="Trading Start Time (optional)">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
</FormControl>
<div className="flex flex-col space-y-2">
<div>
<div className="flex">
<span className="mt-1 text-sm first-letter:capitalize">
Does the collection contain explicit content?
</span>
<div className="ml-2 font-bold form-check form-check-inline">
<input
checked={explicit}
className="peer sr-only"
id="explicitRadio1"
name="explicitRadioOptions1"
onClick={() => {
setExplicit(true)
}}
type="radio"
/>
<label
className="inline-block py-1 px-2 text-sm text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
htmlFor="explicitRadio1"
>
YES
</label>
</div>
<div className="ml-2 font-bold form-check form-check-inline">
<input
checked={!explicit}
className="peer sr-only"
id="explicitRadio2"
name="explicitRadioOptions2"
onClick={() => {
setExplicit(false)
}}
type="radio"
/>
<label
className="inline-block py-1 px-2 text-sm text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
htmlFor="explicitRadio2"
>
NO
</label>
</div>
</div>
</div>
</div>
</FormGroup>
<FormGroup subtitle="Information about royalty" title="Royalty Details">

View File

@ -145,7 +145,7 @@ const Sg721ExecutePage: NextPage = () => {
link={links.Documentation}
title="Sg721 Contract"
/>
<LinkTabs activeIndex={2} data={sg721LinkTabs} />
<LinkTabs activeIndex={1} data={sg721LinkTabs} />
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
<div className="space-y-8">

View File

@ -1 +1 @@
export { default } from './instantiate'
export { default } from './query'

View File

@ -1,186 +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 { NumberInput, TextInput } from 'components/forms/FormInput'
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
import { FormTextArea } from 'components/forms/FormTextArea'
import { JsonPreview } from 'components/JsonPreview'
import { LinkTabs } from 'components/LinkTabs'
import { sg721LinkTabs } from 'components/LinkTabs.data'
import { useContracts } from 'contexts/contracts'
import { useWallet } from 'contexts/wallet'
import type { InstantiateResponse } from 'contracts/sg721'
import type { NextPage } from 'next'
import { NextSeo } from 'next-seo'
import type { FormEvent } from 'react'
import { toast } from 'react-hot-toast'
import { FaAsterisk } from 'react-icons/fa'
import { useMutation } from 'react-query'
import { SG721_CODE_ID } from 'utils/constants'
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
const Sg721InstantiatePage: NextPage = () => {
const wallet = useWallet()
const contract = useContracts().sg721
const nameState = useInputState({
id: 'name',
name: 'name',
title: 'Name',
placeholder: 'My Awesome SG721 Contract',
subtitle: 'Name of the sg721 contract',
})
const symbolState = useInputState({
id: 'symbol',
name: 'symbol',
title: 'Symbol',
placeholder: 'AWSM',
subtitle: 'Symbol of the sg721 contract',
})
const minterState = useInputState({
id: 'minter-address',
name: 'minterAddress',
title: 'Minter Address',
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
subtitle: 'Address that has the permissions to mint on sg721 contract',
})
const creatorState = useInputState({
id: 'creator-address',
name: 'creatorAddress',
title: 'Creator Address',
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
subtitle: 'Address of the collection creator',
})
const descriptionState = useInputState({
id: 'description',
name: 'description',
title: 'Description',
subtitle: 'Description of the collection',
})
const imageState = useInputState({
id: 'image',
name: 'image',
title: 'Image',
subtitle: 'Image of the collection',
placeholder: 'ipfs://bafybe....',
})
const externalLinkState = useInputState({
id: 'external-link',
name: 'externalLink',
title: 'External Link',
subtitle: 'External link to the collection',
})
const royaltyPaymentAddressState = useInputState({
id: 'royalty-payment-address',
name: 'royaltyPaymentAddress',
title: 'Payment Address',
subtitle: 'Address to receive royalties',
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
})
const royaltyShareState = useNumberInputState({
id: 'royalty-share',
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '8',
})
const { data, isLoading, mutate } = useMutation(
async (event: FormEvent): Promise<InstantiateResponse | null> => {
event.preventDefault()
if (!contract) {
throw new Error('Smart contract connection failed')
}
let royaltyInfo = null
if (royaltyPaymentAddressState.value && royaltyShareState.value) {
royaltyInfo = {
paymentAddress: royaltyPaymentAddressState.value,
share: royaltyShareState.value,
}
}
const msg = {
name: nameState.value,
symbol: symbolState.value,
minter: minterState.value,
collection_info: {
creator: creatorState.value,
description: descriptionState.value,
image: imageState.value,
external_link: externalLinkState.value || null,
royalty_info: royaltyInfo,
},
}
return toast.promise(contract.instantiate(SG721_CODE_ID, msg, 'Stargaze Sg721 Contract', wallet.address), {
loading: 'Instantiating contract...',
error: 'Instantiation failed!',
success: 'Instantiation success!',
})
},
{
onError: (error) => {
toast.error(String(error))
},
},
)
return (
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
<NextSeo title="Instantiate Sg721 Contract" />
<ContractPageHeader
description="Sg721 contract is a wrapper contract that has a set of optional extensions on top of cw721-base."
link={links.Documentation}
title="Sg721 Contract"
/>
<LinkTabs activeIndex={0} data={sg721LinkTabs} />
<Conditional test={Boolean(data)}>
<Alert type="info">
<b>Instantiate success!</b> Here is the transaction result containing the contract address and the transaction
hash.
</Alert>
<JsonPreview content={data} title="Transaction Result" />
<br />
</Conditional>
<FormGroup subtitle="Information about your sg721 contract" title="SG721 Contract Details">
<TextInput isRequired {...nameState} />
<TextInput isRequired {...symbolState} />
<TextInput isRequired {...minterState} />
</FormGroup>
<FormGroup subtitle="Information about your collection" title="Collection Details">
<TextInput isRequired {...creatorState} />
<FormTextArea isRequired {...descriptionState} />
<TextInput isRequired {...imageState} />
<TextInput {...externalLinkState} />
</FormGroup>
<FormGroup subtitle="Information about royalty" title="Royalty Details">
<TextInput {...royaltyPaymentAddressState} />
<NumberInput {...royaltyShareState} />
</FormGroup>
<div className="flex items-center p-4">
<div className="flex-grow" />
<Button isLoading={isLoading} isWide rightIcon={<FaAsterisk />} type="submit">
Instantiate Contract
</Button>
</div>
</form>
)
}
export default withMetadata(Sg721InstantiatePage, { center: false })

View File

@ -96,7 +96,7 @@ const Sg721QueryPage: NextPage = () => {
link={links.Documentation}
title="Sg721 Contract"
/>
<LinkTabs activeIndex={1} data={sg721LinkTabs} />
<LinkTabs activeIndex={0} data={sg721LinkTabs} />
<div className="grid grid-cols-2 p-4 space-x-8">
<div className="space-y-8">

View File

@ -76,7 +76,7 @@ const Sg721InstantiatePage: NextPage = () => {
members: whitelistArray,
start_time: (startDate.getTime() * 1_000_000).toString(),
end_time: (endDate.getTime() * 1_000_000).toString(),
unit_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
per_address_limit: perAddressLimitState.value,
member_limit: memberLimitState.value,
}

View File

@ -1,6 +1,7 @@
export const SG721_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_CODE_ID, 10)
export const MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_MINTER_CODE_ID, 10)
export const WHITELIST_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_CODE_ID, 10)
export const VENDING_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10)
export const VENDING_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_ADDRESS
export const PINATA_ENDPOINT_URL = process.env.NEXT_PUBLIC_PINATA_ENDPOINT_URL
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK