Merge pull request #132 from public-awesome/develop
Sync development > main
This commit is contained in:
commit
fe75598b17
19
.env.example
19
.env.example
@ -1,17 +1,22 @@
|
||||
APP_VERSION=0.4.8
|
||||
APP_VERSION=0.5.2
|
||||
|
||||
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||
NEXT_PUBLIC_SG721_CODE_ID=1702
|
||||
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1701
|
||||
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1xz4d6wzxqn3udgsm5qnr78y032xng4r2ycv7aw6mjtsuw59s2n9s93ec0v"
|
||||
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1c6juqgd7cm80afpmuszun66rl9zdc4kgfht8fk34tfq3zk87l78sdxngzv"
|
||||
NEXT_PUBLIC_SG721_CODE_ID=1911
|
||||
NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=1912
|
||||
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1909
|
||||
NEXT_PUBLIC_BASE_MINTER_CODE_ID=1910
|
||||
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1ynec878x5phexq3hj4zdgvp6r5ayfmxks38kvunwyjugqn3hqeqq3cgtuw"
|
||||
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1fnfywcnzzwledr93at65qm8gf953tjxgh6u2u4r8n9vsdv7u75eqe7ecn3"
|
||||
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1sr37phnuahzsc6tpner9875g3fy69khlgvvyzgs2vjtuupw6lffqd7lark"
|
||||
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars13pw8r33dsnghlxfj2upaywf38z2fc6npuw9maq9e5cpet4v285sscgzjp2"
|
||||
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
|
||||
NEXT_PUBLIC_BASE_MINTER_CODE_ID=613
|
||||
NEXT_PUBLIC_WHITELIST_CODE_ID=277
|
||||
NEXT_PUBLIC_WHITELIST_CODE_ID=1913
|
||||
NEXT_PUBLIC_BADGE_HUB_CODE_ID=1336
|
||||
NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa"
|
||||
NEXT_PUBLIC_BADGE_NFT_CODE_ID=1337
|
||||
NEXT_PUBLIC_BADGE_NFT_ADDRESS="stars1vlw4y54dyzt3zg7phj8yey9fg4zj49czknssngwmgrnwymyktztstalg7t"
|
||||
NEXT_PUBLIC_SPLITS_CODE_ID=1904
|
||||
NEXT_PUBLIC_CW4_GROUP_CODE_ID=1905
|
||||
|
||||
|
||||
NEXT_PUBLIC_API_URL=https://nft-api.elgafar-1.stargaze-apis.com
|
||||
|
@ -26,7 +26,7 @@ export const vendingMinterLinkTabs: LinkTabProps[] = [
|
||||
},
|
||||
{
|
||||
title: 'Query',
|
||||
description: `Dispatch queries with your Vending Minter contract`,
|
||||
description: `Dispatch queries for your Vending Minter contract`,
|
||||
href: '/contracts/vendingMinter/query',
|
||||
},
|
||||
{
|
||||
@ -49,7 +49,7 @@ export const baseMinterLinkTabs: LinkTabProps[] = [
|
||||
},
|
||||
{
|
||||
title: 'Query',
|
||||
description: `Dispatch queries with your Base Minter contract`,
|
||||
description: `Dispatch queries for your Base Minter contract`,
|
||||
href: '/contracts/baseMinter/query',
|
||||
},
|
||||
{
|
||||
@ -72,7 +72,7 @@ export const whitelistLinkTabs: LinkTabProps[] = [
|
||||
},
|
||||
{
|
||||
title: 'Query',
|
||||
description: `Dispatch queries with your Whitelist contract`,
|
||||
description: `Dispatch queries for your Whitelist contract`,
|
||||
href: '/contracts/whitelist/query',
|
||||
},
|
||||
{
|
||||
@ -104,3 +104,26 @@ export const badgeHubLinkTabs: LinkTabProps[] = [
|
||||
href: '/contracts/badgeHub/migrate',
|
||||
},
|
||||
]
|
||||
|
||||
export const splitsLinkTabs: LinkTabProps[] = [
|
||||
{
|
||||
title: 'Instantiate',
|
||||
description: `Initialize a new Splits contract`,
|
||||
href: '/contracts/splits/instantiate',
|
||||
},
|
||||
{
|
||||
title: 'Query',
|
||||
description: `Dispatch queries for your Splits contract`,
|
||||
href: '/contracts/splits/query',
|
||||
},
|
||||
{
|
||||
title: 'Execute',
|
||||
description: `Execute Splits contract actions`,
|
||||
href: '/contracts/splits/execute',
|
||||
},
|
||||
{
|
||||
title: 'Migrate',
|
||||
description: `Migrate Splits contract`,
|
||||
href: '/contracts/splits/migrate',
|
||||
},
|
||||
]
|
||||
|
@ -190,6 +190,15 @@ export const Sidebar = () => {
|
||||
<Link href="/contracts/badgeHub/">Badge Hub Contract</Link>
|
||||
</li>
|
||||
</Conditional>
|
||||
<li
|
||||
className={clsx(
|
||||
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
|
||||
router.asPath.includes('/contracts/splits/') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Link href="/contracts/splits/">Splits Contract</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -26,7 +26,7 @@ import { resolveAddress } from 'utils/resolveAddress'
|
||||
|
||||
import type { CollectionInfo } from '../../../contracts/sg721/contract'
|
||||
import { TextInput } from '../../forms/FormInput'
|
||||
import type { MinterType } from './Combobox'
|
||||
import type { MinterType, Sg721Type } from './Combobox'
|
||||
|
||||
interface CollectionActionsProps {
|
||||
minterContractAddress: string
|
||||
@ -35,6 +35,7 @@ interface CollectionActionsProps {
|
||||
vendingMinterMessages: VendingMinterInstance | undefined
|
||||
baseMinterMessages: BaseMinterInstance | undefined
|
||||
minterType: MinterType
|
||||
sg721Type: Sg721Type
|
||||
}
|
||||
|
||||
type ExplicitContentType = true | false | undefined
|
||||
@ -46,6 +47,7 @@ export const CollectionActions = ({
|
||||
vendingMinterMessages,
|
||||
baseMinterMessages,
|
||||
minterType,
|
||||
sg721Type,
|
||||
}: CollectionActionsProps) => {
|
||||
const wallet = useWallet()
|
||||
const [lastTx, setLastTx] = useState('')
|
||||
@ -100,7 +102,15 @@ export const CollectionActions = ({
|
||||
id: 'token-uri',
|
||||
name: 'tokenURI',
|
||||
title: 'Token URI',
|
||||
subtitle: 'URI for the token to be minted',
|
||||
subtitle: 'URI for the token',
|
||||
placeholder: 'ipfs://',
|
||||
})
|
||||
|
||||
const baseURIState = useInputState({
|
||||
id: 'base-uri',
|
||||
name: 'baseURI',
|
||||
title: 'Base URI',
|
||||
subtitle: 'Base URI to batch update token metadata with',
|
||||
placeholder: 'ipfs://',
|
||||
})
|
||||
|
||||
@ -154,13 +164,18 @@ export const CollectionActions = ({
|
||||
placeholder: '5%',
|
||||
})
|
||||
|
||||
const showTokenUriField = type === 'mint_token_uri'
|
||||
const showTokenUriField = isEitherType(type, ['mint_token_uri', 'update_token_metadata'])
|
||||
const showWhitelistField = type === 'set_whitelist'
|
||||
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 showTokenIdField = isEitherType(type, ['transfer', 'mint_for', 'burn', 'update_token_metadata'])
|
||||
const showNumberOfTokensField = type === 'batch_mint'
|
||||
const showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer', 'batch_mint_for'])
|
||||
const showTokenIdListField = isEitherType(type, [
|
||||
'batch_burn',
|
||||
'batch_transfer',
|
||||
'batch_mint_for',
|
||||
'batch_update_token_metadata',
|
||||
])
|
||||
const showRecipientField = isEitherType(type, [
|
||||
'transfer',
|
||||
'mint_to',
|
||||
@ -176,6 +191,7 @@ export const CollectionActions = ({
|
||||
const showExternalLinkField = type === 'update_collection_info'
|
||||
const showRoyaltyRelatedFields = type === 'update_collection_info'
|
||||
const showExplicitContentField = type === 'update_collection_info'
|
||||
const showBaseUriField = type === 'batch_update_token_metadata'
|
||||
|
||||
const payload: DispatchExecuteArgs = {
|
||||
whitelist: whitelistState.value,
|
||||
@ -185,7 +201,9 @@ export const CollectionActions = ({
|
||||
sg721Contract: sg721ContractAddress,
|
||||
tokenId: tokenIdState.value,
|
||||
tokenIds: tokenIdListState.value,
|
||||
tokenUri: tokenURIState.value,
|
||||
tokenUri: tokenURIState.value.trim().endsWith('/')
|
||||
? tokenURIState.value.trim().slice(0, -1)
|
||||
: tokenURIState.value.trim(),
|
||||
batchNumber: batchNumberState.value,
|
||||
vendingMinterMessages,
|
||||
baseMinterMessages,
|
||||
@ -195,6 +213,9 @@ export const CollectionActions = ({
|
||||
txSigner: wallet.address,
|
||||
type,
|
||||
price: priceState.value.toString(),
|
||||
baseUri: baseURIState.value.trim().endsWith('/')
|
||||
? baseURIState.value.trim().slice(0, -1)
|
||||
: baseURIState.value.trim(),
|
||||
collectionInfo,
|
||||
}
|
||||
const resolveRecipientAddress = async () => {
|
||||
@ -350,13 +371,14 @@ export const CollectionActions = ({
|
||||
<form>
|
||||
<div className="grid grid-cols-2 mt-4">
|
||||
<div className="mr-2">
|
||||
<ActionsCombobox minterType={minterType} {...actionComboboxState} />
|
||||
<ActionsCombobox minterType={minterType} sg721Type={sg721Type} {...actionComboboxState} />
|
||||
{showRecipientField && <AddressInput {...recipientState} />}
|
||||
{showTokenUriField && <TextInput className="mt-2" {...tokenURIState} />}
|
||||
{showWhitelistField && <AddressInput {...whitelistState} />}
|
||||
{showLimitField && <NumberInput {...limitState} />}
|
||||
{showTokenIdField && <NumberInput {...tokenIdState} />}
|
||||
{showTokenIdField && <NumberInput className="mt-2" {...tokenIdState} />}
|
||||
{showTokenIdListField && <TextInput className="mt-2" {...tokenIdListState} />}
|
||||
{showBaseUriField && <TextInput className="mt-2" {...baseURIState} />}
|
||||
{showNumberOfTokensField && <NumberInput className="mt-2" {...batchNumberState} />}
|
||||
{showPriceField && <NumberInput className="mt-2" {...priceState} />}
|
||||
{showDescriptionField && <TextInput className="my-2" {...descriptionState} />}
|
||||
|
@ -6,27 +6,31 @@ import { Fragment, useEffect, useState } from 'react'
|
||||
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
|
||||
|
||||
import type { ActionListItem } from './actions'
|
||||
import { BASE_ACTION_LIST, VENDING_ACTION_LIST } from './actions'
|
||||
import { BASE_ACTION_LIST, SG721_UPDATABLE_ACTION_LIST, VENDING_ACTION_LIST } from './actions'
|
||||
|
||||
export type MinterType = 'base' | 'vending'
|
||||
export type Sg721Type = 'updatable' | 'base'
|
||||
|
||||
export interface ActionsComboboxProps {
|
||||
value: ActionListItem | null
|
||||
onChange: (item: ActionListItem) => void
|
||||
minterType?: MinterType
|
||||
sg721Type?: Sg721Type
|
||||
}
|
||||
|
||||
export const ActionsCombobox = ({ value, onChange, minterType }: ActionsComboboxProps) => {
|
||||
export const ActionsCombobox = ({ value, onChange, minterType, sg721Type }: ActionsComboboxProps) => {
|
||||
const [search, setSearch] = useState('')
|
||||
const [ACTION_LIST, SET_ACTION_LIST] = useState<ActionListItem[]>(VENDING_ACTION_LIST)
|
||||
|
||||
useEffect(() => {
|
||||
if (minterType === 'base') {
|
||||
SET_ACTION_LIST(BASE_ACTION_LIST)
|
||||
} else {
|
||||
SET_ACTION_LIST(VENDING_ACTION_LIST)
|
||||
}
|
||||
}, [minterType])
|
||||
if (sg721Type === 'updatable') SET_ACTION_LIST(BASE_ACTION_LIST.concat(SG721_UPDATABLE_ACTION_LIST))
|
||||
else SET_ACTION_LIST(BASE_ACTION_LIST)
|
||||
} else if (minterType === 'vending') {
|
||||
if (sg721Type === 'updatable') SET_ACTION_LIST(VENDING_ACTION_LIST.concat(SG721_UPDATABLE_ACTION_LIST))
|
||||
else SET_ACTION_LIST(VENDING_ACTION_LIST)
|
||||
} else SET_ACTION_LIST(VENDING_ACTION_LIST.concat(SG721_UPDATABLE_ACTION_LIST))
|
||||
}, [minterType, sg721Type])
|
||||
|
||||
const filtered =
|
||||
search === '' ? ACTION_LIST : matchSorter(ACTION_LIST, search, { keys: ['id', 'name', 'description'] })
|
||||
|
@ -1,3 +1,7 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
|
||||
import { useBaseMinterContract } from 'contracts/baseMinter'
|
||||
import type { CollectionInfo, SG721Instance } from 'contracts/sg721'
|
||||
import { useSG721Contract } from 'contracts/sg721'
|
||||
@ -9,9 +13,7 @@ import type { BaseMinterInstance } from '../../../contracts/baseMinter/contract'
|
||||
export type ActionType = typeof ACTION_TYPES[number]
|
||||
|
||||
export const ACTION_TYPES = [
|
||||
'mint',
|
||||
'mint_token_uri',
|
||||
'purge',
|
||||
'update_mint_price',
|
||||
'update_discount_price',
|
||||
'remove_discount_price',
|
||||
@ -32,6 +34,9 @@ export const ACTION_TYPES = [
|
||||
'shuffle',
|
||||
'airdrop',
|
||||
'burn_remaining',
|
||||
'update_token_metadata',
|
||||
'batch_update_token_metadata',
|
||||
'freeze_token_metadata',
|
||||
] as const
|
||||
|
||||
export interface ActionListItem {
|
||||
@ -84,11 +89,6 @@ export const BASE_ACTION_LIST: ActionListItem[] = [
|
||||
]
|
||||
|
||||
export const VENDING_ACTION_LIST: ActionListItem[] = [
|
||||
{
|
||||
id: 'mint',
|
||||
name: 'Mint',
|
||||
description: `Mint a token`,
|
||||
},
|
||||
{
|
||||
id: 'update_mint_price',
|
||||
name: 'Update Mint Price',
|
||||
@ -111,7 +111,7 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [
|
||||
},
|
||||
{
|
||||
id: 'batch_mint',
|
||||
name: 'Batch Mint',
|
||||
name: 'Batch Mint To',
|
||||
description: `Mint multiple tokens to a user`,
|
||||
},
|
||||
{
|
||||
@ -189,10 +189,23 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Burn Remaining Tokens',
|
||||
description: 'Burn remaining tokens',
|
||||
},
|
||||
]
|
||||
|
||||
export const SG721_UPDATABLE_ACTION_LIST: ActionListItem[] = [
|
||||
{
|
||||
id: 'purge',
|
||||
name: 'Purge',
|
||||
description: `Purge`,
|
||||
id: 'update_token_metadata',
|
||||
name: 'Update Token Metadata',
|
||||
description: `Update the metadata URI for a token`,
|
||||
},
|
||||
{
|
||||
id: 'batch_update_token_metadata',
|
||||
name: 'Batch Update Token Metadata',
|
||||
description: `Update the metadata URI for a range of tokens`,
|
||||
},
|
||||
{
|
||||
id: 'freeze_token_metadata',
|
||||
name: 'Freeze Token Metadata',
|
||||
description: `Render the metadata for tokens no longer updatable`,
|
||||
},
|
||||
]
|
||||
|
||||
@ -213,9 +226,7 @@ export type DispatchExecuteArgs = {
|
||||
txSigner: string
|
||||
} & (
|
||||
| { type: undefined }
|
||||
| { type: Select<'mint'> }
|
||||
| { type: Select<'mint_token_uri'>; tokenUri: string }
|
||||
| { type: Select<'purge'> }
|
||||
| { type: Select<'update_mint_price'>; price: string }
|
||||
| { type: Select<'update_discount_price'>; price: string }
|
||||
| { type: Select<'remove_discount_price'> }
|
||||
@ -236,6 +247,9 @@ export type DispatchExecuteArgs = {
|
||||
| { type: Select<'burn_remaining'> }
|
||||
| { type: Select<'update_collection_info'>; collectionInfo: CollectionInfo | undefined }
|
||||
| { type: Select<'freeze_collection_info'> }
|
||||
| { type: Select<'update_token_metadata'>; tokenId: number; tokenUri: string }
|
||||
| { type: Select<'batch_update_token_metadata'>; tokenIds: string; baseUri: string }
|
||||
| { type: Select<'freeze_token_metadata'> }
|
||||
)
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@ -244,15 +258,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
throw new Error('Cannot execute actions')
|
||||
}
|
||||
switch (args.type) {
|
||||
case 'mint': {
|
||||
return vendingMinterMessages.mint(txSigner)
|
||||
}
|
||||
case 'mint_token_uri': {
|
||||
return baseMinterMessages.mint(txSigner, args.tokenUri)
|
||||
}
|
||||
case 'purge': {
|
||||
return vendingMinterMessages.purge(txSigner)
|
||||
}
|
||||
case 'update_mint_price': {
|
||||
return vendingMinterMessages.updateMintPrice(txSigner, args.price)
|
||||
}
|
||||
@ -289,6 +297,15 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'freeze_collection_info': {
|
||||
return sg721Messages.freezeCollectionInfo()
|
||||
}
|
||||
case 'update_token_metadata': {
|
||||
return sg721Messages.updateTokenMetadata(args.tokenId.toString(), args.tokenUri)
|
||||
}
|
||||
case 'batch_update_token_metadata': {
|
||||
return sg721Messages.batchUpdateTokenMetadata(args.tokenIds, args.baseUri)
|
||||
}
|
||||
case 'freeze_token_metadata': {
|
||||
return sg721Messages.freezeTokenMetadata()
|
||||
}
|
||||
case 'shuffle': {
|
||||
return vendingMinterMessages.shuffle(txSigner)
|
||||
}
|
||||
@ -328,15 +345,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
const { messages: baseMinterMessages } = useBaseMinterContract()
|
||||
const { minterContract, sg721Contract } = args
|
||||
switch (args.type) {
|
||||
case 'mint': {
|
||||
return vendingMinterMessages(minterContract)?.mint()
|
||||
}
|
||||
case 'mint_token_uri': {
|
||||
return baseMinterMessages(minterContract)?.mint(args.tokenUri)
|
||||
}
|
||||
case 'purge': {
|
||||
return vendingMinterMessages(minterContract)?.purge()
|
||||
}
|
||||
case 'update_mint_price': {
|
||||
return vendingMinterMessages(minterContract)?.updateMintPrice(args.price)
|
||||
}
|
||||
@ -373,6 +384,15 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'freeze_collection_info': {
|
||||
return sg721Messages(sg721Contract)?.freezeCollectionInfo()
|
||||
}
|
||||
case 'update_token_metadata': {
|
||||
return sg721Messages(sg721Contract)?.updateTokenMetadata(args.tokenId.toString(), args.tokenUri)
|
||||
}
|
||||
case 'batch_update_token_metadata': {
|
||||
return sg721Messages(sg721Contract)?.batchUpdateTokenMetadata(args.tokenIds, args.baseUri)
|
||||
}
|
||||
case 'freeze_token_metadata': {
|
||||
return sg721Messages(sg721Contract)?.freezeTokenMetadata()
|
||||
}
|
||||
case 'shuffle': {
|
||||
return vendingMinterMessages(minterContract)?.shuffle()
|
||||
}
|
||||
|
@ -4,13 +4,16 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
import clsx from 'clsx'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { SG721_UPDATABLE_CODE_ID } from 'utils/constants'
|
||||
|
||||
import { TextInput } from '../../forms/FormInput'
|
||||
import type { MinterType } from '../actions/Combobox'
|
||||
@ -31,12 +34,16 @@ export interface CollectionDetailsDataProps {
|
||||
externalLink?: string
|
||||
startTradingTime?: string
|
||||
explicit: boolean
|
||||
updatable: boolean
|
||||
}
|
||||
|
||||
export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minterType }: CollectionDetailsProps) => {
|
||||
const [coverImage, setCoverImage] = useState<File | null>(null)
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||
const [explicit, setExplicit] = useState<boolean>(false)
|
||||
const [updatable, setUpdatable] = useState<boolean>(false)
|
||||
|
||||
const initialRender = useRef(true)
|
||||
|
||||
const nameState = useInputState({
|
||||
id: 'name',
|
||||
@ -76,6 +83,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
externalLink: externalLinkState.value || undefined,
|
||||
startTradingTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||
explicit,
|
||||
updatable,
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -91,6 +99,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
coverImage,
|
||||
timestamp,
|
||||
explicit,
|
||||
updatable,
|
||||
])
|
||||
|
||||
const selectCoverImage = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
@ -109,6 +118,22 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
reader.readAsArrayBuffer(event.target.files[0])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (initialRender.current) {
|
||||
initialRender.current = false
|
||||
} else if (updatable) {
|
||||
toast.success('Token metadata will be updatable upon collection creation.', {
|
||||
style: { maxWidth: 'none' },
|
||||
icon: '✅📝',
|
||||
})
|
||||
} else {
|
||||
toast.error('Token metadata will not be updatable upon collection creation.', {
|
||||
style: { maxWidth: 'none' },
|
||||
icon: '⛔🔏',
|
||||
})
|
||||
}
|
||||
}, [updatable])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormGroup subtitle="Information about your collection" title="Collection Details">
|
||||
@ -119,9 +144,9 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
<TextInput className="mt-2" {...symbolState} isRequired />
|
||||
</div>
|
||||
<div className={clsx(minterType === 'base' ? 'ml-10' : '')}>
|
||||
<TextInput {...externalLinkState} />
|
||||
<TextInput className="mt-2" {...externalLinkState} />
|
||||
<FormControl
|
||||
className={clsx(minterType === 'base' ? 'mt-12' : '')}
|
||||
className={clsx(minterType === 'base' ? 'mt-12' : 'mt-2')}
|
||||
htmlId="timestamp"
|
||||
subtitle="Trading start time offset will be set as 2 weeks by default."
|
||||
title="Trading Start Time (optional)"
|
||||
@ -140,6 +165,8 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
<input
|
||||
accept="image/*"
|
||||
className={clsx(
|
||||
minterType === 'base' ? 'w-1/2' : 'w-full',
|
||||
'p-[13px] rounded border-2 border-white/20 border-dashed cursor-pointer h-18',
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:hover:bg-white/5 before:transition',
|
||||
)}
|
||||
@ -173,8 +200,8 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
</FormControl>
|
||||
<div className={clsx(minterType === 'base' ? 'flex flex-col -ml-16 space-y-2' : 'flex flex-col space-y-2')}>
|
||||
<div>
|
||||
<div className="flex">
|
||||
<span className="mt-1 text-sm first-letter:capitalize">
|
||||
<div className="flex mt-4">
|
||||
<span className="mt-1 ml-[2px] text-sm first-letter:capitalize">
|
||||
Does the collection contain explicit content?
|
||||
</span>
|
||||
<div className="ml-2 font-bold form-check form-check-inline">
|
||||
@ -216,6 +243,38 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Conditional test={SG721_UPDATABLE_CODE_ID > 0}>
|
||||
<Tooltip
|
||||
backgroundColor="bg-blue-500"
|
||||
label={
|
||||
<div className="grid grid-flow-row">
|
||||
<span>
|
||||
ℹ️ When enabled, the metadata for tokens can be updated after the collection is created until the
|
||||
collection is frozen by the creator.
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
placement="bottom"
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
minterType === 'base'
|
||||
? 'flex flex-col -ml-16 space-y-2 w-1/2 form-control'
|
||||
: 'flex flex-col space-y-2 w-3/4 form-control',
|
||||
)}
|
||||
>
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Updatable Token Metadata</span>
|
||||
<input
|
||||
checked={updatable}
|
||||
className={`toggle ${updatable ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
onClick={() => setUpdatable(!updatable)}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Conditional>
|
||||
</FormGroup>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { resolveAddress } from 'utils/resolveAddress'
|
||||
|
||||
import { NumberInput } from '../../forms/FormInput'
|
||||
import { useWallet } from '../../../contexts/wallet'
|
||||
import { NumberInput, TextInput } from '../../forms/FormInput'
|
||||
import type { UploadMethod } from './UploadDetails'
|
||||
|
||||
interface MintingDetailsProps {
|
||||
@ -18,9 +20,12 @@ export interface MintingDetailsDataProps {
|
||||
unitPrice: string
|
||||
perAddressLimit: number
|
||||
startTime: string
|
||||
paymentAddress?: string
|
||||
}
|
||||
|
||||
export const MintingDetails = ({ onChange, numberOfTokens, uploadMethod }: MintingDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||
|
||||
const numberOfTokensState = useNumberInputState({
|
||||
@ -47,6 +52,24 @@ export const MintingDetails = ({ onChange, numberOfTokens, uploadMethod }: Minti
|
||||
placeholder: '1',
|
||||
})
|
||||
|
||||
const paymentAddressState = useInputState({
|
||||
id: 'payment-address',
|
||||
name: 'paymentAddress',
|
||||
title: 'Payment Address (optional)',
|
||||
subtitle: 'Address to receive minting revenues (defaults to current wallet address)',
|
||||
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||
})
|
||||
|
||||
const resolvePaymentAddress = async () => {
|
||||
await resolveAddress(paymentAddressState.value.trim(), wallet).then((resolvedAddress) => {
|
||||
paymentAddressState.onChange(resolvedAddress)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void resolvePaymentAddress()
|
||||
}, [paymentAddressState.value])
|
||||
|
||||
useEffect(() => {
|
||||
if (numberOfTokens) numberOfTokensState.onChange(numberOfTokens)
|
||||
const data: MintingDetailsDataProps = {
|
||||
@ -54,10 +77,18 @@ export const MintingDetails = ({ onChange, numberOfTokens, uploadMethod }: Minti
|
||||
unitPrice: unitPriceState.value ? (Number(unitPriceState.value) * 1_000_000).toString() : '',
|
||||
perAddressLimit: perAddressLimitState.value,
|
||||
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||
paymentAddress: paymentAddressState.value.trim(),
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [numberOfTokens, numberOfTokensState.value, unitPriceState.value, perAddressLimitState.value, timestamp])
|
||||
}, [
|
||||
numberOfTokens,
|
||||
numberOfTokensState.value,
|
||||
unitPriceState.value,
|
||||
perAddressLimitState.value,
|
||||
timestamp,
|
||||
paymentAddressState.value,
|
||||
])
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -74,6 +105,7 @@ export const MintingDetails = ({ onChange, numberOfTokens, uploadMethod }: Minti
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<TextInput className="p-4 mt-5" {...paymentAddressState} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { AddressList } from 'components/forms/AddressList'
|
||||
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
|
||||
import { Conditional } from '../../Conditional'
|
||||
import { AddressInput, NumberInput } from '../../forms/FormInput'
|
||||
@ -22,6 +25,8 @@ export interface WhitelistDetailsDataProps {
|
||||
endTime?: string
|
||||
perAddressLimit?: number
|
||||
memberLimit?: number
|
||||
admins?: string[]
|
||||
adminsMutable?: boolean
|
||||
}
|
||||
|
||||
type WhitelistState = 'none' | 'existing' | 'new'
|
||||
@ -31,6 +36,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||
const [whitelistArray, setWhitelistArray] = useState<string[]>([])
|
||||
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
||||
|
||||
const whitelistAddressState = useInputState({
|
||||
id: 'whitelist-address',
|
||||
@ -63,6 +69,8 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
placeholder: '5',
|
||||
})
|
||||
|
||||
const addressListState = useAddressListState()
|
||||
|
||||
const whitelistFileOnChange = (data: string[]) => {
|
||||
setWhitelistArray(data)
|
||||
}
|
||||
@ -82,6 +90,14 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
endTime: endDate ? (endDate.getTime() * 1_000_000).toString() : '',
|
||||
perAddressLimit: perAddressLimitState.value,
|
||||
memberLimit: memberLimitState.value,
|
||||
admins: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
|
||||
),
|
||||
],
|
||||
adminsMutable,
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -94,6 +110,8 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
endDate,
|
||||
whitelistArray,
|
||||
whitelistState,
|
||||
addressListState.values,
|
||||
adminsMutable,
|
||||
])
|
||||
|
||||
return (
|
||||
@ -186,6 +204,28 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<div>
|
||||
<div className="mt-2 ml-3 w-[65%] form-control">
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Mutable Administrator Addresses</span>
|
||||
<input
|
||||
checked={adminsMutable}
|
||||
className={`toggle ${adminsMutable ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
onClick={() => setAdminsMutable(!adminsMutable)}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="my-4 ml-4">
|
||||
<AddressList
|
||||
entries={addressListState.entries}
|
||||
isRequired
|
||||
onAdd={addressListState.add}
|
||||
onChange={addressListState.update}
|
||||
onRemove={addressListState.remove}
|
||||
subtitle="The list of administrator addresses"
|
||||
title="Administrator Addresses"
|
||||
/>
|
||||
</div>
|
||||
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
</FormGroup>
|
||||
|
7
components/contracts/splits/ExecuteCombobox.hooks.ts
Normal file
7
components/contracts/splits/ExecuteCombobox.hooks.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { ExecuteListItem } from 'contracts/splits/messages/execute'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const useExecuteComboboxState = () => {
|
||||
const [value, setValue] = useState<ExecuteListItem | null>(null)
|
||||
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
|
||||
}
|
92
components/contracts/splits/ExecuteCombobox.tsx
Normal file
92
components/contracts/splits/ExecuteCombobox.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import { Combobox, Transition } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import type { ExecuteListItem } from 'contracts/splits/messages/execute'
|
||||
import { EXECUTE_LIST } from 'contracts/splits/messages/execute'
|
||||
import { matchSorter } from 'match-sorter'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
|
||||
|
||||
export interface ExecuteComboboxProps {
|
||||
value: ExecuteListItem | null
|
||||
onChange: (item: ExecuteListItem) => void
|
||||
}
|
||||
|
||||
export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => {
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const filtered =
|
||||
search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as={FormControl}
|
||||
htmlId="message-type"
|
||||
labelAs={Combobox.Label}
|
||||
onChange={onChange}
|
||||
subtitle="Contract execute message type"
|
||||
title="Message Type"
|
||||
value={value}
|
||||
>
|
||||
<div className="relative">
|
||||
<Combobox.Input
|
||||
className={clsx(
|
||||
'w-full bg-white/10 rounded border-2 border-white/20 form-input',
|
||||
'placeholder:text-white/50',
|
||||
'focus:ring focus:ring-plumbus-20',
|
||||
)}
|
||||
displayValue={(val?: ExecuteListItem) => val?.name ?? ''}
|
||||
id="message-type"
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder="Select message type"
|
||||
/>
|
||||
|
||||
<Combobox.Button
|
||||
className={clsx(
|
||||
'flex absolute inset-y-0 right-0 items-center p-4',
|
||||
'opacity-50 hover:opacity-100 active:opacity-100',
|
||||
)}
|
||||
>
|
||||
{({ open }) => <FaChevronDown aria-hidden="true" className={clsx('w-4 h-4', { 'rotate-180': open })} />}
|
||||
</Combobox.Button>
|
||||
|
||||
<Transition afterLeave={() => setSearch('')} as={Fragment}>
|
||||
<Combobox.Options
|
||||
className={clsx(
|
||||
'overflow-auto absolute z-10 mt-2 w-full max-h-[30vh]',
|
||||
'bg-stone-800/80 rounded shadow-lg backdrop-blur-sm',
|
||||
'divide-y divide-stone-500/50',
|
||||
)}
|
||||
>
|
||||
{filtered.length < 1 && (
|
||||
<span className="flex flex-col justify-center items-center p-4 text-sm text-center text-white/50">
|
||||
Message type not found.
|
||||
</span>
|
||||
)}
|
||||
{filtered.map((entry) => (
|
||||
<Combobox.Option
|
||||
key={entry.id}
|
||||
className={({ active }) =>
|
||||
clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': 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>
|
||||
)
|
||||
}
|
@ -27,5 +27,9 @@ export function useAddressListState() {
|
||||
})
|
||||
}
|
||||
|
||||
return { entries, values, add, update, remove }
|
||||
function reset() {
|
||||
setRecord({})
|
||||
}
|
||||
|
||||
return { entries, values, add, update, remove, reset }
|
||||
}
|
||||
|
33
components/forms/MemberAttributes.hooks.ts
Normal file
33
components/forms/MemberAttributes.hooks.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { uid } from 'utils/random'
|
||||
|
||||
import type { Attribute } from './MemberAttributes'
|
||||
|
||||
export function useMemberAttributesState() {
|
||||
const [record, setRecord] = useState<Record<string, Attribute>>(() => ({}))
|
||||
|
||||
const entries = useMemo(() => Object.entries(record), [record])
|
||||
const values = useMemo(() => Object.values(record), [record])
|
||||
|
||||
function add(attribute: Attribute = { address: '', weight: 0 }) {
|
||||
setRecord((prev) => ({ ...prev, [uid()]: attribute }))
|
||||
}
|
||||
|
||||
function update(key: string, attribute = record[key]) {
|
||||
setRecord((prev) => ({ ...prev, [key]: attribute }))
|
||||
}
|
||||
|
||||
function remove(key: string) {
|
||||
return setRecord((prev) => {
|
||||
const latest = { ...prev }
|
||||
delete latest[key]
|
||||
return latest
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
setRecord({})
|
||||
}
|
||||
|
||||
return { entries, values, add, update, remove, reset }
|
||||
}
|
94
components/forms/MemberAttributes.tsx
Normal file
94
components/forms/MemberAttributes.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||
import { useEffect, useId, useMemo } from 'react'
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa'
|
||||
|
||||
import { useInputState, useNumberInputState } from './FormInput.hooks'
|
||||
|
||||
export interface Attribute {
|
||||
address: string
|
||||
weight: number
|
||||
}
|
||||
|
||||
export interface MemberAttributesProps {
|
||||
title: string
|
||||
subtitle?: string
|
||||
isRequired?: boolean
|
||||
attributes: [string, Attribute][]
|
||||
onAdd: () => void
|
||||
onChange: (key: string, attribute: Attribute) => void
|
||||
onRemove: (key: string) => void
|
||||
}
|
||||
|
||||
export function MemberAttributes(props: MemberAttributesProps) {
|
||||
const { title, subtitle, isRequired, attributes, onAdd, onChange, onRemove } = props
|
||||
|
||||
return (
|
||||
<FormControl isRequired={isRequired} subtitle={subtitle} title={title}>
|
||||
{attributes.map(([id], i) => (
|
||||
<MemberAttribute
|
||||
key={`ma-${id}`}
|
||||
defaultAttribute={attributes[i][1]}
|
||||
id={id}
|
||||
isLast={i === attributes.length - 1}
|
||||
onAdd={onAdd}
|
||||
onChange={onChange}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
))}
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
export interface MemberAttributeProps {
|
||||
id: string
|
||||
isLast: boolean
|
||||
onAdd: MemberAttributesProps['onAdd']
|
||||
onChange: MemberAttributesProps['onChange']
|
||||
onRemove: MemberAttributesProps['onRemove']
|
||||
defaultAttribute: Attribute
|
||||
}
|
||||
|
||||
export function MemberAttribute({ id, isLast, onAdd, onChange, onRemove, defaultAttribute }: MemberAttributeProps) {
|
||||
const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast])
|
||||
|
||||
const htmlId = useId()
|
||||
|
||||
const addressState = useInputState({
|
||||
id: `ma-address-${htmlId}`,
|
||||
name: `ma-address-${htmlId}`,
|
||||
title: `Address`,
|
||||
defaultValue: defaultAttribute.address,
|
||||
})
|
||||
|
||||
const weightState = useNumberInputState({
|
||||
id: `ma-weight-${htmlId}`,
|
||||
name: `ma-weight-${htmlId}`,
|
||||
title: `Weight`,
|
||||
defaultValue: defaultAttribute.weight,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
onChange(id, { address: addressState.value, weight: weightState.value })
|
||||
}, [addressState.value, weightState.value, id])
|
||||
|
||||
return (
|
||||
<div className="grid relative lg:grid-cols-[70%_20%_10%] 2xl:space-x-2">
|
||||
<AddressInput {...addressState} />
|
||||
<NumberInput {...weightState} />
|
||||
|
||||
<div className="flex justify-end items-end pb-2 w-8">
|
||||
<button
|
||||
className="flex justify-center items-center p-2 bg-stargaze-80 hover:bg-plumbus-60 rounded-full"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
isLast ? onAdd() : onRemove(id)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Icon className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -17,6 +17,9 @@ import { Fragment, useEffect } from 'react'
|
||||
import type { State } from 'zustand'
|
||||
import create from 'zustand'
|
||||
|
||||
import type { UseSplitsContractProps } from '../contracts/splits/useContract'
|
||||
import { useSplitsContract } from '../contracts/splits/useContract'
|
||||
|
||||
/**
|
||||
* Contracts store type definitions
|
||||
*/
|
||||
@ -28,6 +31,7 @@ export interface ContractsStore extends State {
|
||||
vendingFactory: UseVendingFactoryContractProps | null
|
||||
baseFactory: UseBaseFactoryContractProps | null
|
||||
badgeHub: UseBadgeHubContractProps | null
|
||||
splits: UseSplitsContractProps | null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,6 +45,7 @@ export const defaultValues: ContractsStore = {
|
||||
vendingFactory: null,
|
||||
baseFactory: null,
|
||||
badgeHub: null,
|
||||
splits: null,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,6 +76,7 @@ const ContractsSubscription: VFC = () => {
|
||||
const vendingFactory = useVendingFactoryContract()
|
||||
const baseFactory = useBaseFactoryContract()
|
||||
const badgeHub = useBadgeHubContract()
|
||||
const splits = useSplitsContract()
|
||||
|
||||
useEffect(() => {
|
||||
useContracts.setState({
|
||||
@ -81,8 +87,9 @@ const ContractsSubscription: VFC = () => {
|
||||
vendingFactory,
|
||||
baseFactory,
|
||||
badgeHub,
|
||||
splits,
|
||||
})
|
||||
}, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub])
|
||||
}, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub, splits])
|
||||
|
||||
return null
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import { coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { BASE_FACTORY_ADDRESS } from 'utils/constants'
|
||||
|
||||
import { BASE_FACTORY_UPDATABLE_ADDRESS } from '../../utils/constants'
|
||||
|
||||
export interface CreateBaseMinterResponse {
|
||||
readonly baseMinterAddress: string
|
||||
readonly sg721Address: string
|
||||
@ -21,11 +23,12 @@ export interface BaseFactoryInstance {
|
||||
senderAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
) => Promise<CreateBaseMinterResponse>
|
||||
}
|
||||
|
||||
export interface BaseFactoryMessages {
|
||||
createBaseMinter: (msg: Record<string, unknown>) => CreateBaseMinterMessage
|
||||
createBaseMinter: (msg: Record<string, unknown>, updatable?: boolean) => CreateBaseMinterMessage
|
||||
}
|
||||
|
||||
export interface CreateBaseMinterMessage {
|
||||
@ -56,8 +59,16 @@ export const baseFactory = (client: SigningCosmWasmClient, txSigner: string): Ba
|
||||
senderAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
): Promise<CreateBaseMinterResponse> => {
|
||||
const result = await client.execute(senderAddress, BASE_FACTORY_ADDRESS, msg, 'auto', '', funds)
|
||||
const result = await client.execute(
|
||||
senderAddress,
|
||||
updatable ? BASE_FACTORY_UPDATABLE_ADDRESS : BASE_FACTORY_ADDRESS,
|
||||
msg,
|
||||
'auto',
|
||||
'',
|
||||
funds,
|
||||
)
|
||||
|
||||
return {
|
||||
baseMinterAddress: result.logs[0].events[5].attributes[0].value,
|
||||
@ -75,12 +86,12 @@ export const baseFactory = (client: SigningCosmWasmClient, txSigner: string): Ba
|
||||
}
|
||||
|
||||
const messages = (contractAddress: string) => {
|
||||
const createBaseMinter = (msg: Record<string, unknown>): CreateBaseMinterMessage => {
|
||||
const createBaseMinter = (msg: Record<string, unknown>, updatable?: boolean): CreateBaseMinterMessage => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg,
|
||||
funds: [coin('1000000000', 'ustars')],
|
||||
funds: [coin(updatable ? '3000000000' : '1000000000', 'ustars')],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ export interface DispatchExecuteArgs {
|
||||
txSigner: string
|
||||
msg: Record<string, unknown>
|
||||
funds: Coin[]
|
||||
updatable?: boolean
|
||||
}
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@ -17,12 +18,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
if (!messages) {
|
||||
throw new Error('cannot dispatch execute, messages is not defined')
|
||||
}
|
||||
return messages.createBaseMinter(txSigner, args.msg, args.funds)
|
||||
return messages.createBaseMinter(txSigner, args.msg, args.funds, args.updatable)
|
||||
}
|
||||
|
||||
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages } = useBaseFactoryContract()
|
||||
const { contract } = args
|
||||
return messages(contract)?.createBaseMinter(args.msg)
|
||||
return messages(contract)?.createBaseMinter(args.msg, args.updatable)
|
||||
}
|
||||
|
@ -86,6 +86,9 @@ export interface SG721Instance {
|
||||
burn: (tokenId: string) => Promise<string>
|
||||
batchBurn: (tokenIds: string) => Promise<string>
|
||||
batchTransfer: (recipient: string, tokenIds: string) => Promise<string>
|
||||
updateTokenMetadata: (tokenId: string, tokenURI: string) => Promise<string>
|
||||
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string) => Promise<string>
|
||||
freezeTokenMetadata: () => Promise<string>
|
||||
}
|
||||
|
||||
export interface Sg721Messages {
|
||||
@ -101,6 +104,9 @@ export interface Sg721Messages {
|
||||
batchTransfer: (recipient: string, tokenIds: string) => BatchTransferMessage
|
||||
updateCollectionInfo: (collectionInfo: CollectionInfo) => UpdateCollectionInfoMessage
|
||||
freezeCollectionInfo: () => FreezeCollectionInfoMessage
|
||||
updateTokenMetadata: (tokenId: string, tokenURI: string) => UpdateTokenMetadataMessage
|
||||
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string) => BatchUpdateTokenMetadataMessage
|
||||
freezeTokenMetadata: () => FreezeTokenMetadataMessage
|
||||
}
|
||||
|
||||
export interface TransferNFTMessage {
|
||||
@ -215,6 +221,32 @@ export interface BatchTransferMessage {
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateTokenMetadataMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_token_metadata: {
|
||||
token_id: string
|
||||
token_uri: string
|
||||
}
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface BatchUpdateTokenMetadataMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: Record<string, unknown>[]
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface FreezeTokenMetadataMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: { freeze_token_metadata: Record<string, never> }
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateCollectionInfoMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
@ -570,6 +602,65 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const batchUpdateTokenMetadata = async (tokenIds: string, baseURI: string): Promise<string> => {
|
||||
const executeContractMsgs: MsgExecuteContractEncodeObject[] = []
|
||||
if (tokenIds.includes(':')) {
|
||||
const [start, end] = tokenIds.split(':').map(Number)
|
||||
for (let i = start; i <= end; i++) {
|
||||
const msg = {
|
||||
update_token_metadata: { token_id: i.toString(), token_uri: `${baseURI}/${i}` },
|
||||
}
|
||||
const executeContractMsg: MsgExecuteContractEncodeObject = {
|
||||
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
|
||||
value: MsgExecuteContract.fromPartial({
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: toUtf8(JSON.stringify(msg)),
|
||||
}),
|
||||
}
|
||||
|
||||
executeContractMsgs.push(executeContractMsg)
|
||||
}
|
||||
} else {
|
||||
const tokenNumbers = tokenIds.split(',').map(Number)
|
||||
for (let i = 0; i < tokenNumbers.length; i++) {
|
||||
const msg = {
|
||||
update_token_metadata: { token_id: tokenNumbers[i].toString(), token_uri: `${baseURI}/${tokenNumbers[i]}` },
|
||||
}
|
||||
const executeContractMsg: MsgExecuteContractEncodeObject = {
|
||||
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
|
||||
value: MsgExecuteContract.fromPartial({
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: toUtf8(JSON.stringify(msg)),
|
||||
}),
|
||||
}
|
||||
|
||||
executeContractMsgs.push(executeContractMsg)
|
||||
}
|
||||
}
|
||||
|
||||
const res = await client.signAndBroadcast(txSigner, executeContractMsgs, 'auto', 'batch update metadata')
|
||||
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const updateTokenMetadata = async (tokenId: string, tokenURI: string): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
update_token_metadata: {
|
||||
token_id: tokenId,
|
||||
token_uri: tokenURI,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
'',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const freezeCollectionInfo = async (): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
@ -583,6 +674,19 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const freezeTokenMetadata = async (): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
freeze_token_metadata: {},
|
||||
},
|
||||
'auto',
|
||||
'',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
return {
|
||||
contractAddress,
|
||||
ownerOf,
|
||||
@ -609,6 +713,9 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
batchTransfer,
|
||||
updateCollectionInfo,
|
||||
freezeCollectionInfo,
|
||||
updateTokenMetadata,
|
||||
batchUpdateTokenMetadata,
|
||||
freezeTokenMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,6 +911,58 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const batchUpdateTokenMetadata = (tokenIds: string, baseURI: string): BatchUpdateTokenMetadataMessage => {
|
||||
const msg: Record<string, unknown>[] = []
|
||||
if (tokenIds.includes(':')) {
|
||||
const [start, end] = tokenIds.split(':').map(Number)
|
||||
for (let i = start; i <= end; i++) {
|
||||
msg.push({
|
||||
update_token_metadata: { token_id: i.toString(), token_uri: `${baseURI}/${i}` },
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const tokenNumbers = tokenIds.split(',').map(Number)
|
||||
for (let i = 0; i < tokenNumbers.length; i++) {
|
||||
msg.push({
|
||||
update_token_metadata: { token_id: tokenNumbers[i].toString(), token_uri: `${baseURI}/${tokenNumbers[i]}` },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg,
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const updateTokenMetadata = (tokenId: string, tokenURI: string) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_token_metadata: {
|
||||
token_id: tokenId,
|
||||
token_uri: tokenURI,
|
||||
},
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const freezeTokenMetadata = () => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
freeze_token_metadata: {},
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const updateCollectionInfo = (collectionInfo: CollectionInfo) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
@ -814,6 +973,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const freezeCollectionInfo = () => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
@ -838,6 +998,9 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
batchTransfer,
|
||||
updateCollectionInfo,
|
||||
freezeCollectionInfo,
|
||||
updateTokenMetadata,
|
||||
batchUpdateTokenMetadata,
|
||||
freezeTokenMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ export const EXECUTE_TYPES = [
|
||||
'revoke_all',
|
||||
'mint',
|
||||
'burn',
|
||||
'update_token_metadata',
|
||||
'freeze_token_metadata',
|
||||
] as const
|
||||
|
||||
export interface ExecuteListItem {
|
||||
@ -61,6 +63,16 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||
name: 'Burn',
|
||||
description: `Burn a token transaction sender has access to`,
|
||||
},
|
||||
{
|
||||
id: 'update_token_metadata',
|
||||
name: 'Update Token Metadata',
|
||||
description: `Update the metadata URI for a token`,
|
||||
},
|
||||
{
|
||||
id: 'freeze_token_metadata',
|
||||
name: 'Freeze Token Metadata',
|
||||
description: `Render the metadata for tokens no longer updatable`,
|
||||
},
|
||||
]
|
||||
|
||||
export interface DispatchExecuteProps {
|
||||
@ -84,6 +96,8 @@ export type DispatchExecuteArgs = {
|
||||
| { type: Select<'revoke_all'>; operator: string }
|
||||
| { type: Select<'mint'>; recipient: string; tokenId: string; tokenURI?: string }
|
||||
| { type: Select<'burn'>; tokenId: string }
|
||||
| { type: Select<'update_token_metadata'>; tokenId: string; tokenURI: string }
|
||||
| { type: Select<'freeze_token_metadata'> }
|
||||
)
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@ -116,6 +130,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'burn': {
|
||||
return messages.burn(args.tokenId)
|
||||
}
|
||||
case 'update_token_metadata': {
|
||||
return messages.updateTokenMetadata(args.tokenId, args.tokenURI)
|
||||
}
|
||||
case 'freeze_token_metadata': {
|
||||
return messages.freezeTokenMetadata()
|
||||
}
|
||||
default: {
|
||||
throw new Error('unknown execute type')
|
||||
}
|
||||
@ -151,6 +171,12 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'burn': {
|
||||
return messages(contract)?.burn(args.tokenId)
|
||||
}
|
||||
case 'update_token_metadata': {
|
||||
return messages(contract)?.updateTokenMetadata(args.tokenId, args.tokenURI)
|
||||
}
|
||||
case 'freeze_token_metadata': {
|
||||
return messages(contract)?.freezeTokenMetadata()
|
||||
}
|
||||
default: {
|
||||
return {}
|
||||
}
|
||||
|
191
contracts/splits/contract.ts
Normal file
191
contracts/splits/contract.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
|
||||
export interface InstantiateResponse {
|
||||
readonly contractAddress: string
|
||||
readonly transactionHash: string
|
||||
}
|
||||
|
||||
export interface MigrateResponse {
|
||||
readonly transactionHash: string
|
||||
readonly logs: readonly logs.Log[]
|
||||
}
|
||||
|
||||
export interface SplitsInstance {
|
||||
readonly contractAddress: string
|
||||
//Query
|
||||
getAdmin: () => Promise<string>
|
||||
getMemberWeight: (member: string) => Promise<string>
|
||||
listMembers: (startAfter?: string, limit?: number) => Promise<string[]>
|
||||
getGroup: () => Promise<string>
|
||||
|
||||
//Execute
|
||||
updateAdmin: (admin: string) => Promise<string>
|
||||
distribute: () => Promise<string>
|
||||
}
|
||||
|
||||
export interface SplitsMessages {
|
||||
updateAdmin: (admin: string) => UpdateAdminMessage
|
||||
distribute: () => DistributeMessage
|
||||
}
|
||||
|
||||
export interface UpdateAdminMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_admin: { admin: string }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface DistributeMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: { distribute: Record<string, never> }
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface SplitsContract {
|
||||
instantiate: (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
) => Promise<InstantiateResponse>
|
||||
|
||||
use: (contractAddress: string) => SplitsInstance
|
||||
|
||||
migrate: (
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
) => Promise<MigrateResponse>
|
||||
|
||||
messages: (contractAddress: string) => SplitsMessages
|
||||
}
|
||||
|
||||
export const Splits = (client: SigningCosmWasmClient, txSigner: string): SplitsContract => {
|
||||
const use = (contractAddress: string): SplitsInstance => {
|
||||
///QUERY
|
||||
const listMembers = async (startAfter?: string, limit?: number): Promise<string[]> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
list_members: { start_after: startAfter ? startAfter : undefined, limit },
|
||||
})
|
||||
}
|
||||
|
||||
const getMemberWeight = async (address: string): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
member: { address },
|
||||
})
|
||||
}
|
||||
|
||||
const getAdmin = async (): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
admin: {},
|
||||
})
|
||||
}
|
||||
|
||||
const getGroup = async (): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
group: {},
|
||||
})
|
||||
}
|
||||
/// EXECUTE
|
||||
const updateAdmin = async (admin: string): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
update_admin: {
|
||||
admin,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const distribute = async (): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
distribute: {},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
return {
|
||||
contractAddress,
|
||||
updateAdmin,
|
||||
distribute,
|
||||
getMemberWeight,
|
||||
getAdmin,
|
||||
listMembers,
|
||||
getGroup,
|
||||
}
|
||||
}
|
||||
|
||||
const instantiate = async (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
): Promise<InstantiateResponse> => {
|
||||
const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
|
||||
admin,
|
||||
})
|
||||
|
||||
return {
|
||||
contractAddress: result.contractAddress,
|
||||
transactionHash: result.transactionHash,
|
||||
}
|
||||
}
|
||||
|
||||
const migrate = async (
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
): Promise<MigrateResponse> => {
|
||||
const result = await client.migrate(senderAddress, contractAddress, codeId, migrateMsg, 'auto')
|
||||
return {
|
||||
transactionHash: result.transactionHash,
|
||||
logs: result.logs,
|
||||
}
|
||||
}
|
||||
|
||||
const messages = (contractAddress: string) => {
|
||||
const updateAdmin = (admin: string) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_admin: { admin },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const distribute = () => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
distribute: {},
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updateAdmin,
|
||||
distribute,
|
||||
}
|
||||
}
|
||||
|
||||
return { use, instantiate, migrate, messages }
|
||||
}
|
2
contracts/splits/index.ts
Normal file
2
contracts/splits/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './contract'
|
||||
export * from './useContract'
|
77
contracts/splits/messages/execute.ts
Normal file
77
contracts/splits/messages/execute.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import type { SplitsInstance } from '../index'
|
||||
import { useSplitsContract } from '../index'
|
||||
|
||||
export type ExecuteType = typeof EXECUTE_TYPES[number]
|
||||
|
||||
export const EXECUTE_TYPES = ['update_admin', 'distribute'] as const
|
||||
|
||||
export interface ExecuteListItem {
|
||||
id: ExecuteType
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||
{
|
||||
id: 'update_admin',
|
||||
name: 'Update Admin',
|
||||
description: `Update the splits contract admin`,
|
||||
},
|
||||
{
|
||||
id: 'distribute',
|
||||
name: 'Distribute',
|
||||
description: `Distribute the revenue to the group members`,
|
||||
},
|
||||
]
|
||||
|
||||
export interface DispatchExecuteProps {
|
||||
type: ExecuteType
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
type Select<T extends ExecuteType> = T
|
||||
|
||||
/** @see {@link SplitsInstance} */
|
||||
export type DispatchExecuteArgs = {
|
||||
contract: string
|
||||
messages?: SplitsInstance
|
||||
} & ({ type: Select<'update_admin'>; admin: string } | { type: Select<'distribute'> | undefined })
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
const { messages } = args
|
||||
if (!messages) {
|
||||
throw new Error('Cannot dispatch execute, messages are not defined')
|
||||
}
|
||||
switch (args.type) {
|
||||
case 'update_admin': {
|
||||
return messages.updateAdmin(args.admin)
|
||||
}
|
||||
case 'distribute': {
|
||||
return messages.distribute()
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown execution type')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages } = useSplitsContract()
|
||||
const { contract } = args
|
||||
switch (args.type) {
|
||||
case 'update_admin': {
|
||||
return messages(contract)?.updateAdmin(args.admin)
|
||||
}
|
||||
case 'distribute': {
|
||||
return messages(contract)?.distribute()
|
||||
}
|
||||
default: {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
|
||||
return arr.some((val) => type === val)
|
||||
}
|
43
contracts/splits/messages/query.ts
Normal file
43
contracts/splits/messages/query.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import type { SplitsInstance } from '../contract'
|
||||
|
||||
export type QueryType = typeof QUERY_TYPES[number]
|
||||
|
||||
export const QUERY_TYPES = ['admin', 'group', 'member', 'list_members'] as const
|
||||
|
||||
export interface QueryListItem {
|
||||
id: QueryType
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const QUERY_LIST: QueryListItem[] = [
|
||||
{ id: 'list_members', name: 'Query Members', description: 'View the group members' },
|
||||
{ id: 'member', name: 'Query Member Weight', description: 'Query the weight of a member in the group' },
|
||||
{ id: 'admin', name: 'Query Admin', description: 'View the splits contract admin' },
|
||||
{ id: 'group', name: 'Query Group Contract Address', description: 'View the group contract address' },
|
||||
]
|
||||
|
||||
export interface DispatchQueryProps {
|
||||
messages: SplitsInstance | undefined
|
||||
type: QueryType
|
||||
address: string
|
||||
startAfter?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||
const { messages, type, address, startAfter, limit } = props
|
||||
switch (type) {
|
||||
case 'list_members':
|
||||
return messages?.listMembers(startAfter, limit)
|
||||
case 'admin':
|
||||
return messages?.getAdmin()
|
||||
case 'member':
|
||||
return messages?.getMemberWeight(address)
|
||||
case 'group':
|
||||
return messages?.getGroup()
|
||||
default: {
|
||||
throw new Error('unknown query type')
|
||||
}
|
||||
}
|
||||
}
|
93
contracts/splits/useContract.ts
Normal file
93
contracts/splits/useContract.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import type { InstantiateResponse, MigrateResponse, SplitsContract, SplitsInstance, SplitsMessages } from './contract'
|
||||
import { Splits as initContract } from './contract'
|
||||
|
||||
export interface UseSplitsContractProps {
|
||||
instantiate: (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
) => Promise<InstantiateResponse>
|
||||
|
||||
migrate: (contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>) => Promise<MigrateResponse>
|
||||
|
||||
use: (customAddress?: string) => SplitsInstance | undefined
|
||||
|
||||
updateContractAddress: (contractAddress: string) => void
|
||||
|
||||
messages: (contractAddress: string) => SplitsMessages | undefined
|
||||
}
|
||||
|
||||
export function useSplitsContract(): UseSplitsContractProps {
|
||||
const wallet = useWallet()
|
||||
|
||||
const [address, setAddress] = useState<string>('')
|
||||
const [splits, setSplits] = useState<SplitsContract>()
|
||||
|
||||
useEffect(() => {
|
||||
setAddress(localStorage.getItem('contract_address') || '')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const splitsContract = initContract(wallet.getClient(), wallet.address)
|
||||
setSplits(splitsContract)
|
||||
}, [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 (!splits) {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
splits.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
})
|
||||
},
|
||||
[splits],
|
||||
)
|
||||
|
||||
const migrate = useCallback(
|
||||
(contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>): Promise<MigrateResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!splits) {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
splits.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
|
||||
})
|
||||
},
|
||||
[splits, wallet],
|
||||
)
|
||||
|
||||
const use = useCallback(
|
||||
(customAddress = ''): SplitsInstance | undefined => {
|
||||
return splits?.use(address || customAddress)
|
||||
},
|
||||
[splits, address],
|
||||
)
|
||||
|
||||
const messages = useCallback(
|
||||
(customAddress = ''): SplitsMessages | undefined => {
|
||||
return splits?.messages(address || customAddress)
|
||||
},
|
||||
[splits, address],
|
||||
)
|
||||
|
||||
return {
|
||||
instantiate,
|
||||
migrate,
|
||||
use,
|
||||
updateContractAddress,
|
||||
messages,
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import { coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { VENDING_FACTORY_ADDRESS } from 'utils/constants'
|
||||
|
||||
import { VENDING_FACTORY_UPDATABLE_ADDRESS } from '../../utils/constants'
|
||||
|
||||
export interface CreateVendingMinterResponse {
|
||||
readonly vendingMinterAddress: string
|
||||
readonly sg721Address: string
|
||||
@ -21,11 +23,12 @@ export interface VendingFactoryInstance {
|
||||
senderAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
) => Promise<CreateVendingMinterResponse>
|
||||
}
|
||||
|
||||
export interface VendingFactoryMessages {
|
||||
createVendingMinter: (msg: Record<string, unknown>) => CreateVendingMinterMessage
|
||||
createVendingMinter: (msg: Record<string, unknown>, updatable?: boolean) => CreateVendingMinterMessage
|
||||
}
|
||||
|
||||
export interface CreateVendingMinterMessage {
|
||||
@ -50,8 +53,16 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
|
||||
senderAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
funds: Coin[],
|
||||
updatable?: boolean,
|
||||
): Promise<CreateVendingMinterResponse> => {
|
||||
const result = await client.execute(senderAddress, VENDING_FACTORY_ADDRESS, msg, 'auto', '', funds)
|
||||
const result = await client.execute(
|
||||
senderAddress,
|
||||
updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS,
|
||||
msg,
|
||||
'auto',
|
||||
'',
|
||||
funds,
|
||||
)
|
||||
|
||||
return {
|
||||
vendingMinterAddress: result.logs[0].events[5].attributes[0].value,
|
||||
@ -68,12 +79,12 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
|
||||
}
|
||||
|
||||
const messages = (contractAddress: string) => {
|
||||
const createVendingMinter = (msg: Record<string, unknown>): CreateVendingMinterMessage => {
|
||||
const createVendingMinter = (msg: Record<string, unknown>, updatable?: boolean): CreateVendingMinterMessage => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg,
|
||||
funds: [coin('3000000000', 'ustars')],
|
||||
funds: [coin(updatable ? '5000000000' : '3000000000', 'ustars')],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ export interface DispatchExecuteArgs {
|
||||
txSigner: string
|
||||
msg: Record<string, unknown>
|
||||
funds: Coin[]
|
||||
updatable?: boolean
|
||||
}
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@ -17,12 +18,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
if (!messages) {
|
||||
throw new Error('cannot dispatch execute, messages is not defined')
|
||||
}
|
||||
return messages.createVendingMinter(txSigner, args.msg, args.funds)
|
||||
return messages.createVendingMinter(txSigner, args.msg, args.funds, args.updatable)
|
||||
}
|
||||
|
||||
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages } = useVendingFactoryContract()
|
||||
const { contract } = args
|
||||
return messages(contract)?.createVendingMinter(args.msg)
|
||||
return messages(contract)?.createVendingMinter(args.msg, args.updatable)
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||
{
|
||||
id: 'mint',
|
||||
name: 'Mint',
|
||||
description: `Mint new tokens for a given address`,
|
||||
description: `Mint a new token`,
|
||||
},
|
||||
{
|
||||
id: 'purge',
|
||||
|
@ -24,6 +24,7 @@ export interface WhiteListInstance {
|
||||
isActive: () => Promise<boolean>
|
||||
members: (startAfter?: string, limit?: number) => Promise<string[]>
|
||||
hasMember: (member: string) => Promise<boolean>
|
||||
adminList: () => Promise<string[]>
|
||||
config: () => Promise<ConfigResponse>
|
||||
|
||||
//Execute
|
||||
@ -33,6 +34,8 @@ export interface WhiteListInstance {
|
||||
removeMembers: (memberList: string[]) => Promise<string>
|
||||
updatePerAddressLimit: (limit: number) => Promise<string>
|
||||
increaseMemberLimit: (limit: number) => Promise<string>
|
||||
updateAdmins: (admins: string[]) => Promise<string>
|
||||
freeze: () => Promise<string>
|
||||
}
|
||||
|
||||
export interface WhitelistMessages {
|
||||
@ -42,6 +45,8 @@ export interface WhitelistMessages {
|
||||
removeMembers: (memberList: string[]) => RemoveMembersMessage
|
||||
updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
|
||||
increaseMemberLimit: (limit: number) => IncreaseMemberLimitMessage
|
||||
updateAdmins: (admins: string[]) => UpdateAdminsMessage
|
||||
freeze: () => FreezeMessage
|
||||
}
|
||||
|
||||
export interface UpdateStartTimeMessage {
|
||||
@ -62,6 +67,21 @@ export interface UpdateEndTimeMessage {
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateAdminsMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_admins: { admins: string[] }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface FreezeMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: { freeze: Record<string, never> }
|
||||
funds: Coin[]
|
||||
}
|
||||
export interface AddMembersMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
@ -139,6 +159,12 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
})
|
||||
}
|
||||
|
||||
const adminList = async (): Promise<string[]> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
admin_list: {},
|
||||
})
|
||||
}
|
||||
|
||||
const config = async (): Promise<ConfigResponse> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
config: {},
|
||||
@ -170,6 +196,32 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const updateAdmins = async (admins: string[]): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
update_admins: {
|
||||
admins,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const freeze = async (): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
freeze: {},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const removeMembers = async (memberList: string[]): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
@ -199,6 +251,8 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
contractAddress,
|
||||
updateStartTime,
|
||||
updateEndTime,
|
||||
updateAdmins,
|
||||
freeze,
|
||||
addMembers,
|
||||
removeMembers,
|
||||
updatePerAddressLimit,
|
||||
@ -208,6 +262,7 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
isActive,
|
||||
members,
|
||||
hasMember,
|
||||
adminList,
|
||||
config,
|
||||
}
|
||||
}
|
||||
@ -263,6 +318,28 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
}
|
||||
}
|
||||
|
||||
const updateAdmins = (admins: string[]) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_admins: { admins },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const freeze = () => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
freeze: {},
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const removeMembers = (memberList: string[]) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
@ -299,10 +376,12 @@ export const WhiteList = (client: SigningCosmWasmClient, txSigner: string): Whit
|
||||
return {
|
||||
updateStartTime,
|
||||
updateEndTime,
|
||||
updateAdmins,
|
||||
addMembers,
|
||||
removeMembers,
|
||||
updatePerAddressLimit,
|
||||
increaseMemberLimit,
|
||||
freeze,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,12 @@ export type ExecuteType = typeof EXECUTE_TYPES[number]
|
||||
export const EXECUTE_TYPES = [
|
||||
'update_start_time',
|
||||
'update_end_time',
|
||||
'update_admins',
|
||||
'add_members',
|
||||
'remove_members',
|
||||
'update_per_address_limit',
|
||||
'increase_member_limit',
|
||||
'freeze',
|
||||
] as const
|
||||
|
||||
export interface ExecuteListItem {
|
||||
@ -29,6 +31,11 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||
name: 'Update End Time',
|
||||
description: `Update the end time of the whitelist`,
|
||||
},
|
||||
{
|
||||
id: 'update_admins',
|
||||
name: 'Update Admins',
|
||||
description: `Update the list of administrators for the whitelist`,
|
||||
},
|
||||
{
|
||||
id: 'add_members',
|
||||
name: 'Add Members',
|
||||
@ -49,6 +56,11 @@ export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||
name: 'Increase Member Limit',
|
||||
description: `Increase the member limit of the whitelist`,
|
||||
},
|
||||
{
|
||||
id: 'freeze',
|
||||
name: 'Freeze',
|
||||
description: `Freeze the current state of the contract admin list`,
|
||||
},
|
||||
]
|
||||
|
||||
export interface DispatchExecuteProps {
|
||||
@ -70,6 +82,8 @@ export type DispatchExecuteArgs = {
|
||||
| { type: Select<'remove_members'>; members: string[] }
|
||||
| { type: Select<'update_per_address_limit'>; limit: number }
|
||||
| { type: Select<'increase_member_limit'>; limit: number }
|
||||
| { type: Select<'update_admins'>; admins: string[] }
|
||||
| { type: Select<'freeze'> }
|
||||
)
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@ -84,6 +98,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'update_end_time': {
|
||||
return messages.updateEndTime(args.timestamp)
|
||||
}
|
||||
case 'update_admins': {
|
||||
return messages.updateAdmins(args.admins)
|
||||
}
|
||||
case 'add_members': {
|
||||
return messages.addMembers(args.members)
|
||||
}
|
||||
@ -96,6 +113,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'increase_member_limit': {
|
||||
return messages.increaseMemberLimit(args.limit)
|
||||
}
|
||||
case 'freeze': {
|
||||
return messages.freeze()
|
||||
}
|
||||
default: {
|
||||
throw new Error('unknown execute type')
|
||||
}
|
||||
@ -113,6 +133,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'update_end_time': {
|
||||
return messages(contract)?.updateEndTime(args.timestamp)
|
||||
}
|
||||
case 'update_admins': {
|
||||
return messages(contract)?.updateAdmins(args.admins)
|
||||
}
|
||||
case 'add_members': {
|
||||
return messages(contract)?.addMembers(args.members)
|
||||
}
|
||||
@ -125,6 +148,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'increase_member_limit': {
|
||||
return messages(contract)?.increaseMemberLimit(args.limit)
|
||||
}
|
||||
case 'freeze': {
|
||||
return messages(contract)?.freeze()
|
||||
}
|
||||
default: {
|
||||
return {}
|
||||
}
|
||||
|
@ -2,7 +2,15 @@ import type { WhiteListInstance } from '../contract'
|
||||
|
||||
export type QueryType = typeof QUERY_TYPES[number]
|
||||
|
||||
export const QUERY_TYPES = ['has_started', 'has_ended', 'is_active', 'members', 'has_member', 'config'] as const
|
||||
export const QUERY_TYPES = [
|
||||
'has_started',
|
||||
'has_ended',
|
||||
'is_active',
|
||||
'members',
|
||||
'admin_list',
|
||||
'has_member',
|
||||
'config',
|
||||
] as const
|
||||
|
||||
export interface QueryListItem {
|
||||
id: QueryType
|
||||
@ -15,6 +23,7 @@ export const QUERY_LIST: QueryListItem[] = [
|
||||
{ id: 'has_ended', name: 'Has Ended', description: 'Check if the whitelist minting has ended' },
|
||||
{ id: 'is_active', name: 'Is Active', description: 'Check if the whitelist minting is active' },
|
||||
{ id: 'members', name: 'Members', description: 'View the whitelist members' },
|
||||
{ id: 'admin_list', name: 'Admin List', description: 'View the whitelist admin list' },
|
||||
{ id: 'has_member', name: 'Has Member', description: 'Check if a member is in the whitelist' },
|
||||
{ id: 'config', name: 'Config', description: 'View the whitelist configuration' },
|
||||
]
|
||||
@ -36,6 +45,8 @@ export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||
return messages?.isActive()
|
||||
case 'members':
|
||||
return messages?.members()
|
||||
case 'admin_list':
|
||||
return messages?.adminList()
|
||||
case 'has_member':
|
||||
return messages?.hasMember(address)
|
||||
case 'config':
|
||||
|
5
env.d.ts
vendored
5
env.d.ts
vendored
@ -15,16 +15,21 @@ declare namespace NodeJS {
|
||||
readonly APP_VERSION: string
|
||||
|
||||
readonly NEXT_PUBLIC_SG721_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_SG721_UPDATABLE_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_VENDING_FACTORY_UPDATABLE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BASE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BASE_MINTER_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_BADGE_HUB_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_BADGE_HUB_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BADGE_NFT_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_BADGE_NFT_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_SPLITS_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_CW4_GROUP_CODE_ID: string
|
||||
|
||||
readonly NEXT_PUBLIC_PINATA_ENDPOINT_URL: string
|
||||
readonly NEXT_PUBLIC_API_URL: string
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stargaze-studio",
|
||||
"version": "0.4.8",
|
||||
"version": "0.5.2",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
@ -15,7 +15,7 @@ import { useDebounce } from 'utils/debounce'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
import type { MinterType } from '../../components/collections/actions/Combobox'
|
||||
import type { MinterType, Sg721Type } from '../../components/collections/actions/Combobox'
|
||||
|
||||
const CollectionActionsPage: NextPage = () => {
|
||||
const { baseMinter: baseMinterContract, vendingMinter: vendingMinterContract, sg721: sg721Contract } = useContracts()
|
||||
@ -23,6 +23,7 @@ const CollectionActionsPage: NextPage = () => {
|
||||
|
||||
const [action, setAction] = useState<boolean>(false)
|
||||
const [minterType, setMinterType] = useState<MinterType>('vending')
|
||||
const [sg721Type, setSg721Type] = useState<Sg721Type>('updatable')
|
||||
|
||||
const sg721ContractState = useInputState({
|
||||
id: 'sg721-contract-address',
|
||||
@ -39,6 +40,7 @@ const CollectionActionsPage: NextPage = () => {
|
||||
})
|
||||
|
||||
const debouncedMinterContractState = useDebounce(minterContractState.value, 300)
|
||||
const debouncedSg721ContractState = useDebounce(sg721ContractState.value, 300)
|
||||
|
||||
const vendingMinterMessages = useMemo(
|
||||
() => vendingMinterContract?.use(minterContractState.value),
|
||||
@ -109,10 +111,45 @@ const CollectionActionsPage: NextPage = () => {
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
setMinterType('vending')
|
||||
console.log('Unable to retrieve contract version')
|
||||
console.log('Unable to retrieve contract type. Defaulting to "vending".')
|
||||
})
|
||||
}, [debouncedMinterContractState, wallet.client])
|
||||
|
||||
useEffect(() => {
|
||||
async function getSg721ContractType() {
|
||||
if (wallet.client && debouncedSg721ContractState.length > 0) {
|
||||
const client = wallet.client
|
||||
const data = await toast.promise(
|
||||
client.queryContractRaw(
|
||||
debouncedSg721ContractState,
|
||||
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
|
||||
),
|
||||
{
|
||||
loading: 'Retrieving SG721 type...',
|
||||
error: 'SG721 type retrieval failed.',
|
||||
success: 'SG721 type retrieved.',
|
||||
},
|
||||
)
|
||||
const contract: string = JSON.parse(new TextDecoder().decode(data as Uint8Array)).contract
|
||||
console.log(contract)
|
||||
return contract
|
||||
}
|
||||
}
|
||||
void getSg721ContractType()
|
||||
.then((contract) => {
|
||||
if (contract?.includes('sg721-updatable')) {
|
||||
setSg721Type('updatable')
|
||||
} else {
|
||||
setSg721Type('base')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
setMinterType('base')
|
||||
console.log('Unable to retrieve contract type. Defaulting to "base".')
|
||||
})
|
||||
}, [debouncedSg721ContractState, wallet.client])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="Collection Actions" />
|
||||
@ -178,6 +215,7 @@ const CollectionActionsPage: NextPage = () => {
|
||||
minterType={minterType}
|
||||
sg721ContractAddress={sg721ContractState.value}
|
||||
sg721Messages={sg721Messages}
|
||||
sg721Type={sg721Type}
|
||||
vendingMinterMessages={vendingMinterMessages}
|
||||
/>
|
||||
)) || (
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
@ -39,11 +40,14 @@ import { upload } from 'services/upload'
|
||||
import { compareFileArrays } from 'utils/compareFileArrays'
|
||||
import {
|
||||
BASE_FACTORY_ADDRESS,
|
||||
BASE_FACTORY_UPDATABLE_ADDRESS,
|
||||
BLOCK_EXPLORER_URL,
|
||||
NETWORK,
|
||||
SG721_CODE_ID,
|
||||
SG721_UPDATABLE_CODE_ID,
|
||||
STARGAZE_URL,
|
||||
VENDING_FACTORY_ADDRESS,
|
||||
VENDING_FACTORY_UPDATABLE_ADDRESS,
|
||||
WHITELIST_CODE_ID,
|
||||
} from 'utils/constants'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
@ -378,6 +382,8 @@ const CollectionCreationPage: NextPage = () => {
|
||||
mint_price: coin(String(Number(whitelistDetails?.unitPrice)), 'ustars'),
|
||||
per_address_limit: whitelistDetails?.perAddressLimit,
|
||||
member_limit: whitelistDetails?.memberLimit,
|
||||
admins: whitelistDetails?.admins || [wallet.address],
|
||||
admins_mutable: whitelistDetails?.adminsMutable,
|
||||
}
|
||||
|
||||
const data = await whitelistContract.instantiate(
|
||||
@ -408,6 +414,7 @@ const CollectionCreationPage: NextPage = () => {
|
||||
base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}` : `${baseUri}`}`,
|
||||
start_time: mintingDetails?.startTime,
|
||||
num_tokens: mintingDetails?.numTokens,
|
||||
payment_address: mintingDetails?.paymentAddress ? mintingDetails.paymentAddress : undefined,
|
||||
mint_price: {
|
||||
amount: mintingDetails?.unitPrice,
|
||||
denom: 'ustars',
|
||||
@ -416,7 +423,7 @@ const CollectionCreationPage: NextPage = () => {
|
||||
whitelist,
|
||||
},
|
||||
collection_params: {
|
||||
code_id: SG721_CODE_ID,
|
||||
code_id: collectionDetails?.updatable ? SG721_UPDATABLE_CODE_ID : SG721_CODE_ID,
|
||||
name: collectionDetails?.name,
|
||||
symbol: collectionDetails?.symbol,
|
||||
info: {
|
||||
@ -437,11 +444,12 @@ const CollectionCreationPage: NextPage = () => {
|
||||
}
|
||||
|
||||
const payload: VendingFactoryDispatchExecuteArgs = {
|
||||
contract: VENDING_FACTORY_ADDRESS,
|
||||
contract: collectionDetails?.updatable ? VENDING_FACTORY_UPDATABLE_ADDRESS : VENDING_FACTORY_ADDRESS,
|
||||
messages: vendingFactoryMessages,
|
||||
txSigner: wallet.address,
|
||||
msg,
|
||||
funds: [coin('3000000000', 'ustars')],
|
||||
funds: [coin(collectionDetails?.updatable ? '5000000000' : '3000000000', 'ustars')],
|
||||
updatable: collectionDetails?.updatable,
|
||||
}
|
||||
const data = await vendingFactoryDispatchExecute(payload)
|
||||
setTransactionHash(data.transactionHash)
|
||||
@ -466,7 +474,7 @@ const CollectionCreationPage: NextPage = () => {
|
||||
create_minter: {
|
||||
init_msg: null,
|
||||
collection_params: {
|
||||
code_id: SG721_CODE_ID,
|
||||
code_id: collectionDetails?.updatable ? SG721_UPDATABLE_CODE_ID : SG721_CODE_ID,
|
||||
name: collectionDetails?.name,
|
||||
symbol: collectionDetails?.symbol,
|
||||
info: {
|
||||
@ -487,11 +495,12 @@ const CollectionCreationPage: NextPage = () => {
|
||||
}
|
||||
|
||||
const payload: BaseFactoryDispatchExecuteArgs = {
|
||||
contract: BASE_FACTORY_ADDRESS,
|
||||
contract: collectionDetails?.updatable ? BASE_FACTORY_UPDATABLE_ADDRESS : BASE_FACTORY_ADDRESS,
|
||||
messages: baseFactoryMessages,
|
||||
txSigner: wallet.address,
|
||||
msg,
|
||||
funds: [coin('1000000000', 'ustars')],
|
||||
funds: [coin(collectionDetails?.updatable ? '3000000000' : '1000000000', 'ustars')],
|
||||
updatable: collectionDetails?.updatable,
|
||||
}
|
||||
await baseFactoryDispatchExecute(payload)
|
||||
.then(async (data) => {
|
||||
@ -761,6 +770,11 @@ const CollectionCreationPage: NextPage = () => {
|
||||
)
|
||||
if (mintingDetails.startTime === '') throw new Error('Start time is required')
|
||||
if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time')
|
||||
if (
|
||||
mintingDetails.paymentAddress &&
|
||||
(!isValidAddress(mintingDetails.paymentAddress) || !mintingDetails.paymentAddress.startsWith('stars1'))
|
||||
)
|
||||
throw new Error('Invalid payment address')
|
||||
}
|
||||
|
||||
const checkWhitelistDetails = async () => {
|
||||
@ -806,8 +820,8 @@ const CollectionCreationPage: NextPage = () => {
|
||||
throw new Error('Per address limit is required')
|
||||
if (!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
|
||||
throw new Error('Member limit is required')
|
||||
if (Number(whitelistDetails.startTime) > Number(whitelistDetails.endTime))
|
||||
throw new Error('Whitelist start time cannot be later than whitelist end time')
|
||||
if (Number(whitelistDetails.startTime) >= Number(whitelistDetails.endTime))
|
||||
throw new Error('Whitelist start time cannot be equal to or later than the whitelist end time')
|
||||
if (Number(whitelistDetails.startTime) !== Number(mintingDetails?.startTime))
|
||||
throw new Error('Whitelist start time must be the same as the minting start time')
|
||||
if (whitelistDetails.perAddressLimit && mintingDetails?.numTokens) {
|
||||
@ -849,13 +863,30 @@ const CollectionCreationPage: NextPage = () => {
|
||||
const checkwalletBalance = () => {
|
||||
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
||||
if (minterType === 'vending' && whitelistDetails?.whitelistType === 'new' && whitelistDetails.memberLimit) {
|
||||
const amountNeeded = Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 + 3000000000
|
||||
const amountNeeded =
|
||||
Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 +
|
||||
(collectionDetails?.updatable ? 5000000000 : 3000000000)
|
||||
if (amountNeeded >= Number(wallet.balance[0].amount))
|
||||
throw new Error('Insufficient wallet balance to instantiate the required contracts.')
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||
amountNeeded / 1000000
|
||||
).toString()} STARS`,
|
||||
)
|
||||
} else {
|
||||
const amountNeeded = minterType === 'vending' ? 3000000000 : 1000000000
|
||||
const amountNeeded =
|
||||
minterType === 'vending'
|
||||
? collectionDetails?.updatable
|
||||
? 5000000000
|
||||
: 3000000000
|
||||
: collectionDetails?.updatable
|
||||
? 3000000000
|
||||
: 1000000000
|
||||
if (amountNeeded >= Number(wallet.balance[0].amount))
|
||||
throw new Error('Insufficient wallet balance to instantiate the required contracts.')
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||
amountNeeded / 1000000
|
||||
).toString()} STARS`,
|
||||
)
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
|
@ -58,6 +58,9 @@ const HomePage: NextPage = () => {
|
||||
Execute messages and run queries on the Badge Hub contract designed for event organizers.
|
||||
</HomeCard>
|
||||
</Conditional>
|
||||
<HomeCard className="p-4 -m-4 hover:bg-gray-500/10 rounded" link="/contracts/splits" title="Splits Contract">
|
||||
Execute messages and run queries on the Splits contract designed for revenue distribution.
|
||||
</HomeCard>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
@ -85,11 +85,19 @@ const Sg721ExecutePage: NextPage = () => {
|
||||
placeholder: 'ipfs://xyz...',
|
||||
})
|
||||
|
||||
const showTokenIdField = isEitherType(type, ['transfer_nft', 'send_nft', 'approve', 'revoke', 'mint', 'burn'])
|
||||
const showTokenIdField = isEitherType(type, [
|
||||
'transfer_nft',
|
||||
'send_nft',
|
||||
'approve',
|
||||
'revoke',
|
||||
'mint',
|
||||
'burn',
|
||||
'update_token_metadata',
|
||||
])
|
||||
const showRecipientField = isEitherType(type, ['transfer_nft', 'send_nft', 'approve', 'revoke', 'mint'])
|
||||
const showOperatorField = isEitherType(type, ['approve_all', 'revoke_all'])
|
||||
const showMessageField = type === 'send_nft'
|
||||
const showTokenURIField = type === 'mint'
|
||||
const showTokenURIField = isEitherType(type, ['mint', 'update_token_metadata'])
|
||||
|
||||
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
||||
const payload: DispatchExecuteArgs = {
|
||||
@ -99,7 +107,7 @@ const Sg721ExecutePage: NextPage = () => {
|
||||
recipient: resolvedRecipientAddress,
|
||||
operator: resolvedOperatorAddress,
|
||||
type,
|
||||
tokenURI: tokenURIState.value,
|
||||
tokenURI: tokenURIState.value.trim(),
|
||||
msg: parseJson(messageState.value) || {},
|
||||
}
|
||||
const { isLoading, mutate } = useMutation(
|
||||
|
135
pages/contracts/splits/execute.tsx
Normal file
135
pages/contracts/splits/execute.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
import { Button } from 'components/Button'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||
import { ExecuteCombobox } from 'components/contracts/splits/ExecuteCombobox'
|
||||
import { useExecuteComboboxState } from 'components/contracts/splits/ExecuteCombobox.hooks'
|
||||
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 { splitsLinkTabs } from 'components/LinkTabs.data'
|
||||
import { TransactionHash } from 'components/TransactionHash'
|
||||
import { useContracts } from 'contexts/contracts'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { DispatchExecuteArgs } from 'contracts/splits/messages/execute'
|
||||
import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/splits/messages/execute'
|
||||
import type { NextPage } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import type { FormEvent } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
const SplitsExecutePage: NextPage = () => {
|
||||
const { splits: 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: 'Splits Address',
|
||||
subtitle: 'Address of the Splits contract',
|
||||
})
|
||||
const contractAddress = contractState.value
|
||||
|
||||
const adminAddressState = useInputState({
|
||||
id: 'admin-address',
|
||||
name: 'admin-address',
|
||||
title: 'Admin Address',
|
||||
subtitle: 'Address of the new administrator',
|
||||
})
|
||||
|
||||
const showAdminAddress = isEitherType(type, ['update_admin'])
|
||||
|
||||
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
||||
const payload: DispatchExecuteArgs = {
|
||||
contract: contractState.value,
|
||||
messages,
|
||||
type,
|
||||
admin: adminAddressState.value.trim(),
|
||||
}
|
||||
const { isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!type) {
|
||||
throw new Error('Please select message type!')
|
||||
}
|
||||
if (!wallet.initialized) {
|
||||
throw new Error('Please connect your wallet.')
|
||||
}
|
||||
const txHash = await toast.promise(dispatchExecute(payload), {
|
||||
error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
|
||||
loading: 'Executing message...',
|
||||
success: (tx) => `Transaction ${tx} success!`,
|
||||
})
|
||||
if (txHash) {
|
||||
setLastTx(txHash)
|
||||
}
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
toast.error(String(error), { style: { maxWidth: 'none' } })
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (contractAddress.length > 0) {
|
||||
void router.replace({ query: { contractAddress } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [contractAddress])
|
||||
useEffect(() => {
|
||||
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="Execute Splits Contract" />
|
||||
<ContractPageHeader
|
||||
description="Splits contract distributes funds to a cw4-group based on member weights."
|
||||
link={links.Documentation}
|
||||
title="Splits Contract"
|
||||
/>
|
||||
<LinkTabs activeIndex={2} data={splitsLinkTabs} />
|
||||
|
||||
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
|
||||
<div className="space-y-8">
|
||||
<AddressInput {...contractState} />
|
||||
<ExecuteCombobox {...comboboxState} />
|
||||
<Conditional test={showAdminAddress}>
|
||||
<AddressInput {...adminAddressState} />
|
||||
</Conditional>
|
||||
</div>
|
||||
<div className="space-y-8">
|
||||
<div className="relative">
|
||||
<Button className="absolute top-0 right-0" isLoading={isLoading} rightIcon={<FaArrowRight />} type="submit">
|
||||
Execute
|
||||
</Button>
|
||||
<FormControl subtitle="View execution transaction hash" title="Transaction Hash">
|
||||
<TransactionHash hash={lastTx} />
|
||||
</FormControl>
|
||||
</div>
|
||||
<FormControl subtitle="View current message to be sent" title="Payload Preview">
|
||||
<JsonPreview content={previewExecutePayload(payload)} isCopyable />
|
||||
</FormControl>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default withMetadata(SplitsExecutePage, { center: false })
|
1
pages/contracts/splits/index.tsx
Normal file
1
pages/contracts/splits/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './instantiate'
|
237
pages/contracts/splits/instantiate.tsx
Normal file
237
pages/contracts/splits/instantiate.tsx
Normal file
@ -0,0 +1,237 @@
|
||||
import { toBase64, toUtf8 } from '@cosmjs/encoding'
|
||||
import { Alert } from 'components/Alert'
|
||||
import { Button } from 'components/Button'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||
import { AddressInput } from 'components/forms/FormInput'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { LinkTabs } from 'components/LinkTabs'
|
||||
import { splitsLinkTabs } 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, useEffect, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaAsterisk } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
import { useInputState } from '../../../components/forms/FormInput.hooks'
|
||||
import type { Attribute } from '../../../components/forms/MemberAttributes'
|
||||
import { MemberAttributes } from '../../../components/forms/MemberAttributes'
|
||||
import { useMemberAttributesState } from '../../../components/forms/MemberAttributes.hooks'
|
||||
import { CW4_GROUP_CODE_ID, SPLITS_CODE_ID } from '../../../utils/constants'
|
||||
import { resolveAddress } from '../../../utils/resolveAddress'
|
||||
|
||||
export type CW4Method = 'new' | 'existing'
|
||||
|
||||
const SplitsInstantiatePage: NextPage = () => {
|
||||
const wallet = useWallet()
|
||||
const { splits: contract } = useContracts()
|
||||
const [members, setMembers] = useState<Attribute[]>([])
|
||||
const [cw4Method, setCw4Method] = useState<CW4Method>('new')
|
||||
|
||||
const cw4GroupAddressState = useInputState({
|
||||
id: 'cw4-group-address',
|
||||
name: 'cw4-group-address',
|
||||
title: 'CW4 Group Address',
|
||||
subtitle: 'Address of the CW4 Group contract',
|
||||
placeholder: 'stars1...',
|
||||
})
|
||||
|
||||
const splitsAdminState = useInputState({
|
||||
id: 'splits-admin',
|
||||
name: 'splits-admin',
|
||||
title: 'Splits Contract Admin',
|
||||
subtitle: 'Address of the Splits Contract administrator',
|
||||
defaultValue: wallet.address,
|
||||
})
|
||||
|
||||
const cw4GroupAdminState = useInputState({
|
||||
id: 'cw4-group-admin',
|
||||
name: 'cw4-group-admin',
|
||||
title: 'CW4 Group Admin',
|
||||
subtitle: 'Address of the CW4 Group administrator',
|
||||
defaultValue: wallet.address,
|
||||
})
|
||||
|
||||
const memberListState = useMemberAttributesState()
|
||||
|
||||
useEffect(() => {
|
||||
memberListState.reset()
|
||||
memberListState.add({
|
||||
address: '',
|
||||
weight: 0,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const { data, isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent): Promise<InstantiateResponse | null> => {
|
||||
event.preventDefault()
|
||||
if (!contract) {
|
||||
throw new Error('Smart contract connection failed')
|
||||
}
|
||||
const msg =
|
||||
cw4Method === 'existing'
|
||||
? {
|
||||
admin: splitsAdminState.value ? splitsAdminState.value : undefined,
|
||||
group: { cw4_address: cw4GroupAddressState.value },
|
||||
}
|
||||
: {
|
||||
admin: splitsAdminState.value ? splitsAdminState.value : undefined,
|
||||
group: {
|
||||
cw4_instantiate: {
|
||||
code_id: CW4_GROUP_CODE_ID,
|
||||
label: 'cw4-group',
|
||||
msg: toBase64(
|
||||
toUtf8(
|
||||
JSON.stringify({
|
||||
admin: cw4GroupAdminState.value ? cw4GroupAdminState.value : undefined,
|
||||
members: [
|
||||
...new Set(
|
||||
members
|
||||
.filter(
|
||||
(member) =>
|
||||
member.address !== '' &&
|
||||
member.weight > 0 &&
|
||||
isValidAddress(member.address) &&
|
||||
member.address.startsWith('stars'),
|
||||
)
|
||||
.map((member) => ({ addr: member.address, weight: member.weight })),
|
||||
),
|
||||
],
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
return toast.promise(contract.instantiate(SPLITS_CODE_ID, msg, 'Stargaze Splits Contract', wallet.address), {
|
||||
loading: 'Instantiating contract...',
|
||||
error: 'Instantiation failed!',
|
||||
success: 'Instantiation success!',
|
||||
})
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
toast.error(String(error), { style: { maxWidth: 'none' } })
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const resolveMemberAddresses = () => {
|
||||
const tempMembers: Attribute[] = []
|
||||
memberListState.values.map(async (member) => {
|
||||
await resolveAddress(member.address.trim(), wallet).then((resolvedAddress) => {
|
||||
tempMembers.push({ address: resolvedAddress, weight: member.weight })
|
||||
})
|
||||
})
|
||||
setMembers(tempMembers)
|
||||
console.log('Members:', members)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
resolveMemberAddresses()
|
||||
}, [memberListState.values])
|
||||
|
||||
return (
|
||||
<form className="py-6 px-12 space-y-4" onSubmit={mutate}>
|
||||
<NextSeo title="Instantiate Splits Contract" />
|
||||
<ContractPageHeader
|
||||
description="Splits contract distributes funds to a cw4-group based on member weights."
|
||||
link={links.Documentation}
|
||||
title="Splits Contract"
|
||||
/>
|
||||
<LinkTabs activeIndex={0} data={splitsLinkTabs} />
|
||||
|
||||
<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>
|
||||
|
||||
<div className="justify-items-start mb-3 flex-column">
|
||||
<div className="flex">
|
||||
<div className="mt-3 ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={cw4Method === 'new'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio2"
|
||||
name="inlineRadioOptions2"
|
||||
onClick={() => {
|
||||
setCw4Method('new')
|
||||
}}
|
||||
type="radio"
|
||||
value="New"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio2"
|
||||
>
|
||||
New CW4 Group Contract
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-3 ml-2 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={cw4Method === 'existing'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio1"
|
||||
name="inlineRadioOptions1"
|
||||
onClick={() => {
|
||||
setCw4Method('existing')
|
||||
}}
|
||||
type="radio"
|
||||
value="Existing"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio1"
|
||||
>
|
||||
Use an existing CW4 Group Contract
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<AddressInput className="mt-2 ml-4 w-full" {...splitsAdminState} />
|
||||
<Conditional test={cw4Method === 'new'}>
|
||||
<AddressInput className="mt-2 ml-4 w-full" {...cw4GroupAdminState} />
|
||||
</Conditional>
|
||||
<Conditional test={cw4Method === 'existing'}>
|
||||
<AddressInput className="mt-2 ml-4 w-full" {...cw4GroupAddressState} />
|
||||
</Conditional>
|
||||
</div>
|
||||
<div>
|
||||
<Conditional test={cw4Method === 'new'}>
|
||||
<div className="ml-4 w-full">
|
||||
<MemberAttributes
|
||||
attributes={memberListState.entries}
|
||||
onAdd={memberListState.add}
|
||||
onChange={memberListState.update}
|
||||
onRemove={memberListState.remove}
|
||||
title="Members"
|
||||
/>
|
||||
</div>
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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(SplitsInstantiatePage, { center: false })
|
133
pages/contracts/splits/migrate.tsx
Normal file
133
pages/contracts/splits/migrate.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { Button } from 'components/Button'
|
||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||
import { useExecuteComboboxState } from 'components/contracts/splits/ExecuteCombobox.hooks'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { LinkTabs } from 'components/LinkTabs'
|
||||
import { splitsLinkTabs } from 'components/LinkTabs.data'
|
||||
import { TransactionHash } from 'components/TransactionHash'
|
||||
import { useContracts } from 'contexts/contracts'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { MigrateResponse } from 'contracts/splits'
|
||||
import type { NextPage } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import type { FormEvent } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
const SplitsMigratePage: NextPage = () => {
|
||||
const { splits: contract } = useContracts()
|
||||
const wallet = useWallet()
|
||||
|
||||
const [lastTx, setLastTx] = useState('')
|
||||
|
||||
const comboboxState = useExecuteComboboxState()
|
||||
const type = comboboxState.value?.id
|
||||
const codeIdState = useNumberInputState({
|
||||
id: 'code-id',
|
||||
name: 'code-id',
|
||||
title: 'Code ID',
|
||||
subtitle: 'Code ID of the New Splits contract',
|
||||
placeholder: '1',
|
||||
})
|
||||
|
||||
const contractState = useInputState({
|
||||
id: 'contract-address',
|
||||
name: 'contract-address',
|
||||
title: 'Splits Address',
|
||||
subtitle: 'Address of the Splits contract',
|
||||
})
|
||||
const contractAddress = contractState.value
|
||||
|
||||
const { data, isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent): Promise<MigrateResponse | null> => {
|
||||
event.preventDefault()
|
||||
if (!contract) {
|
||||
throw new Error('Smart contract connection failed')
|
||||
}
|
||||
if (!wallet.initialized) {
|
||||
throw new Error('Please connect your wallet.')
|
||||
}
|
||||
|
||||
const migrateMsg = {}
|
||||
|
||||
return toast.promise(contract.migrate(contractAddress, codeIdState.value, migrateMsg), {
|
||||
error: `Migration failed!`,
|
||||
loading: 'Executing message...',
|
||||
success: (tx) => {
|
||||
if (tx) {
|
||||
setLastTx(tx.transactionHash)
|
||||
}
|
||||
return `Transaction success!`
|
||||
},
|
||||
})
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
toast.error(String(error), { style: { maxWidth: 'none' } })
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (contractAddress.length > 0) {
|
||||
void router.replace({ query: { contractAddress } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [contractAddress])
|
||||
useEffect(() => {
|
||||
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="Migrate Splits Contract" />
|
||||
<ContractPageHeader
|
||||
description="Splits contract distributes funds to a cw4-group based on member weights."
|
||||
link={links.Documentation}
|
||||
title="Splits Contract"
|
||||
/>
|
||||
<LinkTabs activeIndex={3} data={splitsLinkTabs} />
|
||||
|
||||
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
|
||||
<div className="space-y-8">
|
||||
<AddressInput {...contractState} />
|
||||
<NumberInput isRequired {...codeIdState} />
|
||||
</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={{
|
||||
sender: wallet.address,
|
||||
contract: contractAddress,
|
||||
code_id: codeIdState.value,
|
||||
msg: {},
|
||||
}}
|
||||
isCopyable
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default withMetadata(SplitsMigratePage, { center: false })
|
156
pages/contracts/splits/query.tsx
Normal file
156
pages/contracts/splits/query.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import clsx from 'clsx'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { AddressInput, NumberInput, TextInput } from 'components/forms/FormInput'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { LinkTabs } from 'components/LinkTabs'
|
||||
import { splitsLinkTabs } from 'components/LinkTabs.data'
|
||||
import { useContracts } from 'contexts/contracts'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { QueryType } from 'contracts/splits/messages/query'
|
||||
import { dispatchQuery, QUERY_LIST } from 'contracts/splits/messages/query'
|
||||
import type { NextPage } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { useQuery } from 'react-query'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
import { resolveAddress } from 'utils/resolveAddress'
|
||||
|
||||
const SplitsQueryPage: NextPage = () => {
|
||||
const { splits: contract } = useContracts()
|
||||
const wallet = useWallet()
|
||||
|
||||
const contractState = useInputState({
|
||||
id: 'contract-address',
|
||||
name: 'contract-address',
|
||||
title: 'Splits Address',
|
||||
subtitle: 'Address of the Splits contract',
|
||||
})
|
||||
const contractAddress = contractState.value
|
||||
|
||||
const memberAddressState = useInputState({
|
||||
id: 'member-address',
|
||||
name: 'member-address',
|
||||
title: 'Member Address',
|
||||
subtitle: 'Member address to query the weight for',
|
||||
})
|
||||
|
||||
const memberAddress = memberAddressState.value
|
||||
|
||||
const startAfterStringState = useInputState({
|
||||
id: 'start-after-string',
|
||||
name: 'start-after-string',
|
||||
title: 'Start After (optional)',
|
||||
subtitle: 'The member address to start the pagination after',
|
||||
})
|
||||
|
||||
const paginationLimitState = useNumberInputState({
|
||||
id: 'pagination-limit',
|
||||
name: 'pagination-limit',
|
||||
title: 'Pagination Limit (optional)',
|
||||
subtitle: 'The number of items to return (max: 30)',
|
||||
defaultValue: 5,
|
||||
})
|
||||
|
||||
const [type, setType] = useState<QueryType>('list_members')
|
||||
|
||||
const { data: response } = useQuery(
|
||||
[
|
||||
contractAddress,
|
||||
type,
|
||||
contract,
|
||||
wallet,
|
||||
memberAddress,
|
||||
startAfterStringState.value,
|
||||
paginationLimitState.value,
|
||||
] as const,
|
||||
async ({ queryKey }) => {
|
||||
const [_contractAddress, _type, _contract, _wallet, _memberAddress, startAfter, limit] = queryKey
|
||||
const messages = contract?.use(contractAddress)
|
||||
const res = await resolveAddress(_memberAddress, wallet).then(async (resolvedAddress) => {
|
||||
const result = await dispatchQuery({
|
||||
messages,
|
||||
type,
|
||||
address: resolvedAddress,
|
||||
startAfter: startAfter.length > 0 ? startAfter : undefined,
|
||||
limit: limit > 0 ? limit : undefined,
|
||||
})
|
||||
return result
|
||||
})
|
||||
return res
|
||||
},
|
||||
{
|
||||
placeholderData: null,
|
||||
onError: (error: any) => {
|
||||
toast.error(error.message, { style: { maxWidth: 'none' } })
|
||||
},
|
||||
enabled: Boolean(contractAddress && contract && wallet),
|
||||
},
|
||||
)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (contractAddress.length > 0) {
|
||||
void router.replace({ query: { contractAddress } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [contractAddress])
|
||||
useEffect(() => {
|
||||
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="Query Splits Contract" />
|
||||
<ContractPageHeader
|
||||
description="Splits contract distributes funds to a cw4-group based on member weights."
|
||||
link={links.Documentation}
|
||||
title="Splits Contract"
|
||||
/>
|
||||
<LinkTabs activeIndex={1} data={splitsLinkTabs} />
|
||||
|
||||
<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',
|
||||
)}
|
||||
defaultValue="config"
|
||||
id="contract-query-type"
|
||||
name="query-type"
|
||||
onChange={(e) => setType(e.target.value as QueryType)}
|
||||
>
|
||||
{QUERY_LIST.map(({ id, name }) => (
|
||||
<option key={`query-${id}`} className="mt-2 text-lg bg-[#1A1A1A]" value={id}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormControl>
|
||||
<Conditional test={type === 'member'}>
|
||||
<AddressInput {...memberAddressState} />
|
||||
</Conditional>
|
||||
|
||||
<Conditional test={type === 'list_members'}>
|
||||
<TextInput {...startAfterStringState} />
|
||||
<NumberInput {...paginationLimitState} />
|
||||
</Conditional>
|
||||
</div>
|
||||
<JsonPreview content={contractAddress ? { type, response } : null} title="Query Response" />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default withMetadata(SplitsQueryPage, { center: false })
|
@ -64,6 +64,7 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
const showLimitState = isEitherType(type, ['update_per_address_limit', 'increase_member_limit'])
|
||||
const showTimestamp = isEitherType(type, ['update_start_time', 'update_end_time'])
|
||||
const showMemberList = isEitherType(type, ['add_members', 'remove_members'])
|
||||
const showAdminList = isEitherType(type, ['update_admins'])
|
||||
|
||||
const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
|
||||
const payload: DispatchExecuteArgs = {
|
||||
@ -80,6 +81,13 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
.concat(memberList),
|
||||
),
|
||||
],
|
||||
admins: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
|
||||
),
|
||||
] || [wallet.address],
|
||||
}
|
||||
const { isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent) => {
|
||||
@ -146,20 +154,22 @@ const WhitelistExecutePage: NextPage = () => {
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
<Conditional test={showMemberList}>
|
||||
<Conditional test={showMemberList || showAdminList}>
|
||||
<AddressList
|
||||
entries={addressListState.entries}
|
||||
isRequired
|
||||
onAdd={addressListState.add}
|
||||
onChange={addressListState.update}
|
||||
onRemove={addressListState.remove}
|
||||
subtitle="Enter the member addresses"
|
||||
subtitle={type === 'update_admins' ? 'Enter the admin addresses' : 'Enter the member addresses'}
|
||||
title="Addresses"
|
||||
/>
|
||||
<Alert className="mt-8" type="info">
|
||||
You may optionally choose a text file of additional member addresses.
|
||||
</Alert>
|
||||
<WhitelistUpload onChange={setMemberList} />
|
||||
<Conditional test={showMemberList}>
|
||||
<Alert className="mt-8" type="info">
|
||||
You may optionally choose a text file of additional member addresses.
|
||||
</Alert>
|
||||
<WhitelistUpload onChange={setMemberList} />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
</div>
|
||||
<div className="space-y-8">
|
||||
|
@ -5,6 +5,8 @@ import { Conditional } from 'components/Conditional'
|
||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { AddressList } from 'components/forms/AddressList'
|
||||
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||
import { NumberInput } from 'components/forms/FormInput'
|
||||
import { useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
@ -21,16 +23,19 @@ import { type FormEvent, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaAsterisk } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
import { WHITELIST_CODE_ID } from 'utils/constants'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
import { WHITELIST_CODE_ID } from '../../../utils/constants'
|
||||
|
||||
const WhitelistInstantiatePage: NextPage = () => {
|
||||
const wallet = useWallet()
|
||||
const { whitelist: contract } = useContracts()
|
||||
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
||||
|
||||
const [whitelistArray, setWhitelistArray] = useState<string[]>([])
|
||||
|
||||
@ -58,6 +63,8 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
placeholder: '5',
|
||||
})
|
||||
|
||||
const addressListState = useAddressListState()
|
||||
|
||||
const { data, isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent): Promise<InstantiateResponse | null> => {
|
||||
event.preventDefault()
|
||||
@ -79,6 +86,14 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
mint_price: coin(String(Number(unitPriceState.value) * 1000000), 'ustars'),
|
||||
per_address_limit: perAddressLimitState.value,
|
||||
member_limit: memberLimitState.value,
|
||||
admins: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
|
||||
),
|
||||
] || [wallet.address],
|
||||
admins_mutable: adminsMutable,
|
||||
}
|
||||
return toast.promise(
|
||||
contract.instantiate(WHITELIST_CODE_ID, msg, 'Stargaze Whitelist Contract', wallet.address),
|
||||
@ -119,6 +134,29 @@ const WhitelistInstantiatePage: NextPage = () => {
|
||||
<br />
|
||||
</Conditional>
|
||||
|
||||
<div className="mt-2 ml-3 w-full form-control">
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Mutable Administrator Addresses</span>
|
||||
<input
|
||||
checked={adminsMutable}
|
||||
className={`toggle ${adminsMutable ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
onClick={() => setAdminsMutable(!adminsMutable)}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="my-4 ml-4 w-1/2">
|
||||
<AddressList
|
||||
entries={addressListState.entries}
|
||||
isRequired
|
||||
onAdd={addressListState.add}
|
||||
onChange={addressListState.update}
|
||||
onRemove={addressListState.remove}
|
||||
subtitle="The list of administrator addresses"
|
||||
title="Administrator Addresses"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormGroup subtitle="Your whitelisted addresses" title="Whitelist File">
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
<Conditional test={whitelistArray.length > 0}>
|
||||
|
@ -108,7 +108,7 @@ const WhitelistQueryPage: NextPage = () => {
|
||||
onChange={(e) => setType(e.target.value as QueryType)}
|
||||
>
|
||||
{QUERY_LIST.map(({ id, name }) => (
|
||||
<option key={`query-${id}`} value={id}>
|
||||
<option key={`query-${id}`} className="mt-2 text-lg bg-[#1A1A1A]" value={id}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
|
@ -1,14 +1,19 @@
|
||||
export const SG721_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_CODE_ID, 10)
|
||||
export const SG721_UPDATABLE_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SG721_UPDATABLE_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 VENDING_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS
|
||||
export const BASE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_ADDRESS
|
||||
export const BASE_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS
|
||||
export const SG721_NAME_ADDRESS = process.env.NEXT_PUBLIC_SG721_NAME_ADDRESS
|
||||
export const BASE_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10)
|
||||
export const BADGE_HUB_CODE_ID = parseInt(process.env.NEXT_PUBLIC_BADGE_HUB_CODE_ID, 10)
|
||||
export const BADGE_HUB_ADDRESS = process.env.NEXT_PUBLIC_BADGE_HUB_ADDRESS
|
||||
export const BADGE_NFT_CODE_ID = parseInt(process.env.NEXT_PUBLIC_BADGE_NFT_CODE_ID, 10)
|
||||
export const BADGE_NFT_ADDRESS = process.env.NEXT_PUBLIC_BADGE_NFT_ADDRESS
|
||||
export const SPLITS_CODE_ID = parseInt(process.env.NEXT_PUBLIC_SPLITS_CODE_ID, 10)
|
||||
export const CW4_GROUP_CODE_ID = parseInt(process.env.NEXT_PUBLIC_CW4_GROUP_CODE_ID, 10)
|
||||
|
||||
export const PINATA_ENDPOINT_URL = process.env.NEXT_PUBLIC_PINATA_ENDPOINT_URL
|
||||
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK
|
||||
|
Loading…
Reference in New Issue
Block a user