Merge pull request #48 from public-awesome/develop

dev>main
This commit is contained in:
Jorge Hernandez 2022-10-28 00:08:27 -06:00 committed by GitHub
commit 8d70edd6c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 287 additions and 22 deletions

View File

@ -1,10 +1,10 @@
APP_VERSION=0.1.0
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
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_SG721_CODE_ID=274
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=275
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1j4qn9krchp5xs8nued4j4vcr4j654wxkhf7acy76734xe5fsz08sku28s2"
NEXT_PUBLIC_WHITELIST_CODE_ID=277
NEXT_PUBLIC_API_URL=https://
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze

View File

@ -1,19 +1,45 @@
import { useState } from 'react'
import { Button } from './Button'
export interface ConfirmationModalProps {
confirm: () => void
}
export const ConfirmationModal = (props: ConfirmationModalProps) => {
const [isChecked, setIsChecked] = useState(false)
return (
<div>
<input className="modal-toggle" defaultChecked id="my-modal-2" type="checkbox" />
<label className="cursor-pointer modal" htmlFor="my-modal-2">
<label
className="absolute top-[40%] bottom-5 left-1/3 max-w-xl max-h-40 border-2 no-scrollbar modal-box"
className="absolute top-[25%] bottom-5 left-1/3 max-w-[600px] max-h-[400px] border-2 no-scrollbar modal-box"
htmlFor="temp"
>
{/* <Alert type="warning"></Alert> */}
<div className="text-xl font-bold">
<div className="text-sm font-thin">
You represent and warrant that you have, or have obtained, all rights, licenses, consents, permissions,
power and/or authority necessary to grant the rights granted herein for any content that you create,
submit, post, promote, or display on or through the Service. You represent and warrant that such contain
material subject to copyright, trademark, publicity rights, or other intellectual property rights, unless
you have necessary permission or are otherwise legally entitled to post the material and to grant Stargaze
Parties the license described above, and that the content does not violate any laws.
</div>
<br />
<div className="flex flex-row pb-4">
<label className="flex flex-col space-y-1" htmlFor="terms">
<span className="text-sm font-light text-white">I agree with the terms above.</span>
</label>
<input
checked={isChecked}
className="p-2 mb-1 ml-2"
id="terms"
name="terms"
onClick={() => setIsChecked(!isChecked)}
type="checkbox"
/>
</div>
<br />
Are you sure to create a collection with the specified assets, metadata and parameters?
</div>
<div className="flex justify-end w-full">
@ -25,9 +51,9 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
Go Back
</label>
</Button>
<Button className="px-0 mt-4 mb-4 max-h-12" onClick={props.confirm}>
<Button className="px-0 mt-4 mb-4 max-h-12" isDisabled={!isChecked} onClick={props.confirm}>
<label
className="w-full h-full text-white bg-plumbus-light hover:bg-plumbus-light border-0 btn modal-button"
className="w-full h-full text-white bg-plumbus hover:bg-plumbus-light border-0 btn modal-button"
htmlFor="my-modal-2"
>
Confirm

View File

@ -22,6 +22,7 @@ import { FaArrowRight } from 'react-icons/fa'
import { useMutation } from 'react-query'
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
import type { CollectionInfo } from '../../../contracts/sg721/contract'
import { TextInput } from '../../forms/FormInput'
interface CollectionActionsProps {
@ -31,6 +32,8 @@ interface CollectionActionsProps {
minterMessages: MinterInstance | undefined
}
type ExplicitContentType = true | false | undefined
export const CollectionActions = ({
sg721ContractAddress,
sg721Messages,
@ -43,12 +46,14 @@ export const CollectionActions = ({
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
const [airdropAllocationArray, setAirdropAllocationArray] = useState<AirdropAllocation[]>([])
const [airdropArray, setAirdropArray] = useState<string[]>([])
const [collectionInfo, setCollectionInfo] = useState<CollectionInfo>()
const [explicitContent, setExplicitContent] = useState<ExplicitContentType>(undefined)
const actionComboboxState = useActionsComboboxState()
const type = actionComboboxState.value?.id
const limitState = useNumberInputState({
id: 'per-address-limi',
id: 'per-address-limit',
name: 'perAddressLimit',
title: 'Per Address Limit',
subtitle: 'Enter the per address limit',
@ -97,6 +102,42 @@ export const CollectionActions = ({
subtitle: 'New minting price in STARS',
})
const descriptionState = useInputState({
id: 'collection-description',
name: 'description',
title: 'Collection Description',
})
const imageState = useInputState({
id: 'collection-cover-image',
name: 'cover_image',
title: 'Collection Cover Image',
subtitle: 'URL for collection cover image.',
})
const externalLinkState = useInputState({
id: 'collection-ext-link',
name: 'external_link',
title: 'External Link',
subtitle: 'External URL for the collection.',
})
const royaltyPaymentAddressState = useInputState({
id: 'royalty-payment-address',
name: 'royaltyPaymentAddress',
title: 'Royalty Payment Address',
subtitle: 'Address to receive royalties.',
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
})
const royaltyShareState = useInputState({
id: 'royalty-share',
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '8%',
})
const showWhitelistField = type === 'set_whitelist'
const showDateField = isEitherType(type, ['update_start_time', 'update_start_trading_time'])
const showLimitField = type === 'update_per_address_limit'
@ -106,6 +147,11 @@ export const CollectionActions = ({
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer'])
const showAirdropFileField = type === 'airdrop'
const showPriceField = type === 'update_mint_price'
const showDescriptionField = type === 'update_collection_info'
const showImageField = type === 'update_collection_info'
const showExternalLinkField = type === 'update_collection_info'
const showRoyaltyRelatedFields = type === 'update_collection_info'
const showExplicitContentField = type === 'update_collection_info'
const payload: DispatchExecuteArgs = {
whitelist: whitelistState.value,
@ -123,8 +169,32 @@ export const CollectionActions = ({
txSigner: wallet.address,
type,
price: priceState.value.toString(),
collectionInfo,
}
useEffect(() => {
setCollectionInfo({
description: descriptionState.value || undefined,
image: imageState.value || undefined,
explicit_content: explicitContent,
external_link: externalLinkState.value || undefined,
royalty_info:
royaltyPaymentAddressState.value && royaltyShareState.value
? {
payment_address: royaltyPaymentAddressState.value,
share: (Number(royaltyShareState.value) / 100).toString(),
}
: undefined,
})
}, [
descriptionState.value,
imageState.value,
explicitContent,
externalLinkState.value,
royaltyPaymentAddressState.value,
royaltyShareState.value,
])
useEffect(() => {
const addresses: string[] = []
airdropAllocationArray.forEach((allocation) => {
@ -150,10 +220,16 @@ export const CollectionActions = ({
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')
}
if (
type === 'update_collection_info' &&
(royaltyShareState.value ? !royaltyPaymentAddressState.value : royaltyPaymentAddressState.value)
) {
throw new Error('Royalty payment address and share percentage are both required')
}
const txHash = await toast.promise(dispatchExecute(payload), {
error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
loading: 'Executing message...',
@ -172,7 +248,6 @@ export const CollectionActions = ({
const airdropFileOnChange = (data: AirdropAllocation[]) => {
setAirdropAllocationArray(data)
console.log(data)
}
return (
@ -187,6 +262,62 @@ export const CollectionActions = ({
{showTokenIdListField && <TextInput {...tokenIdListState} />}
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
{showPriceField && <NumberInput {...priceState} />}
{showDescriptionField && <TextInput className="mb-2" {...descriptionState} />}
{showImageField && <TextInput className="mb-2" {...imageState} />}
{showExternalLinkField && <TextInput className="mb-2" {...externalLinkState} />}
{showRoyaltyRelatedFields && (
<div className="p-2 my-4 rounded border-2 border-gray-500/50">
<TextInput className="mb-2" {...royaltyPaymentAddressState} />
<NumberInput className="mb-2" {...royaltyShareState} />
</div>
)}
{showExplicitContentField && (
<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={explicitContent === true}
className="peer sr-only"
id="explicitRadio1"
name="explicitRadioOptions1"
onClick={() => {
setExplicitContent(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={explicitContent === false}
className="peer sr-only"
id="explicitRadio2"
name="explicitRadioOptions2"
onClick={() => {
setExplicitContent(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>
)}
{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

@ -1,6 +1,6 @@
import type { MinterInstance } from 'contracts/minter'
import { useMinterContract } from 'contracts/minter'
import type { SG721Instance } from 'contracts/sg721'
import type { CollectionInfo, SG721Instance } from 'contracts/sg721'
import { useSG721Contract } from 'contracts/sg721'
export type ActionType = typeof ACTION_TYPES[number]
@ -16,6 +16,8 @@ export const ACTION_TYPES = [
'update_start_time',
'update_start_trading_time',
'update_per_address_limit',
'update_collection_info',
'freeze_collection_info',
'withdraw',
'transfer',
'batch_transfer',
@ -83,6 +85,16 @@ export const ACTION_LIST: ActionListItem[] = [
name: 'Update Tokens Per Address Limit',
description: `Update token per address limit`,
},
{
id: 'update_collection_info',
name: 'Update Collection Info',
description: `Update Collection Info`,
},
{
id: 'freeze_collection_info',
name: 'Freeze Collection Info',
description: `Freeze collection info to prevent further updates`,
},
{
id: 'withdraw',
name: 'Withdraw Tokens',
@ -159,6 +171,8 @@ export type DispatchExecuteArgs = {
| { type: Select<'batch_burn'>; tokenIds: string }
| { type: Select<'airdrop'>; recipients: string[] }
| { type: Select<'burn_remaining'> }
| { type: Select<'update_collection_info'>; collectionInfo: CollectionInfo | undefined }
| { type: Select<'freeze_collection_info'> }
)
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
@ -197,6 +211,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'update_per_address_limit': {
return minterMessages.updatePerAddressLimit(txSigner, args.limit)
}
case 'update_collection_info': {
return sg721Messages.updateCollectionInfo(args.collectionInfo as CollectionInfo)
}
case 'freeze_collection_info': {
return sg721Messages.freezeCollectionInfo()
}
case 'shuffle': {
return minterMessages.shuffle(txSigner)
}
@ -264,6 +284,12 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'update_per_address_limit': {
return minterMessages(minterContract)?.updatePerAddressLimit(args.limit)
}
case 'update_collection_info': {
return sg721Messages(sg721Contract)?.updateCollectionInfo(args.collectionInfo as CollectionInfo)
}
case 'freeze_collection_info': {
return sg721Messages(sg721Contract)?.freezeCollectionInfo()
}
case 'shuffle': {
return minterMessages(minterContract)?.shuffle()
}

View File

@ -107,10 +107,6 @@ 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">
@ -118,7 +114,11 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
<TextInput {...descriptionState} isRequired />
<TextInput {...symbolState} isRequired />
<TextInput {...externalLinkState} />
<FormControl htmlId="timestamp" subtitle="Trading start time (local)" title="Trading Start Time (optional)">
<FormControl
htmlId="timestamp"
subtitle="Trading start time offset will be set as 2 weeks by default."
title="Trading Start Time (optional)"
>
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
</FormControl>

View File

@ -12,7 +12,7 @@ export interface InstantiateResponse {
readonly logs: readonly logs.Log[]
}
export interface RoyalityInfo {
export interface RoyaltyInfo {
payment_address: string
share: string
}

View File

@ -4,6 +4,8 @@ import type { Coin } from '@cosmjs/stargate'
import { coin } from '@cosmjs/stargate'
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
import type { RoyaltyInfo } from '../minter/contract'
export interface InstantiateResponse {
readonly contractAddress: string
readonly transactionHash: string
@ -11,6 +13,14 @@ export interface InstantiateResponse {
export type Expiration = { at_height: number } | { at_time: string } | { never: Record<string, never> }
export interface CollectionInfo {
description?: string
image?: string
external_link?: string
explicit_content?: boolean
royalty_info?: RoyaltyInfo | undefined
}
export interface SG721Instance {
readonly contractAddress: string
@ -65,7 +75,8 @@ export interface SG721Instance {
revokeAll: (operator: string) => Promise<string>
/// Mint a new NFT, can only be called by the contract minter
mint: (tokenId: string, owner: string, tokenURI?: string) => Promise<string> //MintMsg<T>
updateCollectionInfo: (collectionInfo: CollectionInfo) => Promise<string>
freezeCollectionInfo: () => Promise<string>
/// Burn an NFT the sender has access to
burn: (tokenId: string) => Promise<string>
batchBurn: (tokenIds: string) => Promise<string>
@ -83,6 +94,8 @@ export interface Sg721Messages {
burn: (tokenId: string) => BurnMessage
batchBurn: (tokenIds: string) => BatchBurnMessage
batchTransfer: (recipient: string, tokenIds: string) => BatchTransferMessage
updateCollectionInfo: (collectionInfo: CollectionInfo) => UpdateCollectionInfoMessage
freezeCollectionInfo: () => FreezeCollectionInfoMessage
}
export interface TransferNFTMessage {
@ -197,6 +210,24 @@ export interface BatchTransferMessage {
funds: Coin[]
}
export interface UpdateCollectionInfoMessage {
sender: string
contract: string
msg: {
update_collection_info: {
collection_info: CollectionInfo
}
}
funds: Coin[]
}
export interface FreezeCollectionInfoMessage {
sender: string
contract: string
msg: { freeze_collection_info: Record<string, never> }
funds: Coin[]
}
export interface SG721Contract {
instantiate: (
senderAddress: string,
@ -513,6 +544,33 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
return res.transactionHash
}
// eslint-disable-next-line @typescript-eslint/no-shadow
const updateCollectionInfo = async (collectionInfo: CollectionInfo): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
update_collection_info: { collection_info: collectionInfo },
},
'auto',
'',
)
return res.transactionHash
}
const freezeCollectionInfo = async (): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
freeze_collection_info: {},
},
'auto',
'',
)
return res.transactionHash
}
return {
contractAddress,
ownerOf,
@ -537,6 +595,8 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
burn,
batchBurn,
batchTransfer,
updateCollectionInfo,
freezeCollectionInfo,
}
}
@ -719,6 +779,26 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
funds: [],
}
}
const updateCollectionInfo = (collectionInfo: CollectionInfo) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_collection_info: { collection_info: collectionInfo },
},
funds: [],
}
}
const freezeCollectionInfo = () => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
freeze_collection_info: {},
},
funds: [],
}
}
return {
transferNft,
@ -731,6 +811,8 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
burn,
batchBurn,
batchTransfer,
updateCollectionInfo,
freezeCollectionInfo,
}
}

View File

@ -189,7 +189,7 @@ const CollectionCreationPage: NextPage = () => {
const msg = {
create_minter: {
init_msg: {
base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}/` : `${baseUri}`}`,
base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}` : `${baseUri}`}`,
start_time: mintingDetails?.startTime,
num_tokens: mintingDetails?.numTokens,
mint_price: {
@ -431,9 +431,9 @@ const CollectionCreationPage: NextPage = () => {
<Anchor
className="text-stargaze hover:underline"
external
href={`https://ipfs.stargaze.zone/ipfs/${baseTokenUri as string}/`}
href={`https://ipfs.stargaze.zone/ipfs/${baseTokenUri as string}`}
>
ipfs://{baseTokenUri as string}/
ipfs://{baseTokenUri as string}
</Anchor>
)}
{uploadDetails?.uploadMethod === 'existing' && (

View File

@ -37,7 +37,7 @@ const MinterExecutePage: NextPage = () => {
const type = comboboxState.value?.id
const limitState = useNumberInputState({
id: 'per-address-limi',
id: 'per-address-limit',
name: 'perAddressLimit',
title: 'Per Address Limit',
subtitle: 'Enter the per address limit',