Add burn & batch burn for sg721 (#39)

* Add burn & batch burn for sg721

* Add batch burn range

* Refactor contract messages logic

Co-authored-by: findolor <anakisci@gmail.com>
This commit is contained in:
Serkan Reis 2022-08-16 10:18:35 +03:00 committed by GitHub
parent 1624f0c332
commit aae2b14a01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 172 additions and 45 deletions

View File

@ -14,6 +14,8 @@ export const ACTION_TYPES = [
'update_per_address_limit',
'withdraw',
'transfer',
'burn',
'batch_burn',
'shuffle',
] as const
@ -64,6 +66,16 @@ export const ACTION_LIST: ActionListItem[] = [
name: 'Transfer Tokens',
description: `Transfer tokens from one address to another`,
},
{
id: 'burn',
name: 'Burn Token',
description: `Burn a specified token from the collection`,
},
{
id: 'batch_burn',
name: 'Batch Burn Tokens',
description: `Burn a list of tokens from the collection`,
},
{
id: 'shuffle',
name: 'Shuffle Tokens',
@ -96,6 +108,8 @@ export type DispatchExecuteArgs = {
| { type: Select<'shuffle'> }
| { type: Select<'withdraw'> }
| { type: Select<'transfer'>; recipient: string; tokenId: number }
| { type: Select<'burn'>; tokenId: number }
| { type: Select<'batch_burn'>; tokenIds: string }
)
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
@ -131,6 +145,12 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'transfer': {
return sg721Messages.transferNft(args.recipient, args.tokenId.toString())
}
case 'burn': {
return sg721Messages.burn(args.tokenId.toString())
}
case 'batch_burn': {
return sg721Messages.batchBurn(args.tokenIds)
}
default: {
throw new Error('Unknown action')
}
@ -145,32 +165,38 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
const { minterContract, sg721Contract } = args
switch (args.type) {
case 'mint_to': {
return minterMessages()?.mintTo(minterContract, args.recipient)
return minterMessages(minterContract)?.mintTo(args.recipient)
}
case 'mint_for': {
return minterMessages()?.mintFor(minterContract, args.recipient, args.tokenId)
return minterMessages(minterContract)?.mintFor(args.recipient, args.tokenId)
}
case 'batch_mint': {
return minterMessages()?.batchMint(minterContract, args.recipient, args.batchNumber)
return minterMessages(minterContract)?.batchMint(args.recipient, args.batchNumber)
}
case 'set_whitelist': {
return minterMessages()?.setWhitelist(minterContract, args.whitelist)
return minterMessages(minterContract)?.setWhitelist(args.whitelist)
}
case 'update_start_time': {
return minterMessages()?.updateStartTime(minterContract, args.startTime)
return minterMessages(minterContract)?.updateStartTime(args.startTime)
}
case 'update_per_address_limit': {
return minterMessages()?.updatePerAddressLimit(minterContract, args.limit)
return minterMessages(minterContract)?.updatePerAddressLimit(args.limit)
}
case 'shuffle': {
return minterMessages()?.shuffle(minterContract)
return minterMessages(minterContract)?.shuffle()
}
case 'withdraw': {
return minterMessages()?.withdraw(minterContract)
return minterMessages(minterContract)?.withdraw()
}
case 'transfer': {
return sg721Messages(sg721Contract)?.transferNft(args.recipient, args.tokenId.toString())
}
case 'burn': {
return sg721Messages(sg721Contract)?.burn(args.tokenId.toString())
}
case 'batch_burn': {
return sg721Messages(sg721Contract)?.batchBurn(args.tokenIds)
}
default: {
return {}
}

View File

@ -40,15 +40,15 @@ export interface MinterInstance {
}
export interface MinterMessages {
mint: (contractAddress: string, price: string) => MintMessage
setWhitelist: (contractAddress: string, whitelist: string) => SetWhitelistMessage
updateStartTime: (contractAddress: string, time: Timestamp) => UpdateStarTimeMessage
updatePerAddressLimit: (contractAddress: string, perAddressLimit: number) => UpdatePerAddressLimitMessage
mintTo: (contractAddress: string, recipient: string) => MintToMessage
mintFor: (contractAddress: string, recipient: string, tokenId: number) => MintForMessage
batchMint: (contractAddress: string, recipient: string, batchNumber: number) => BatchMintMessage
shuffle: (contractAddress: string) => ShuffleMessage
withdraw: (contractAddress: string) => WithdrawMessage
mint: (price: string) => MintMessage
setWhitelist: (whitelist: string) => SetWhitelistMessage
updateStartTime: (time: Timestamp) => UpdateStarTimeMessage
updatePerAddressLimit: (perAddressLimit: number) => UpdatePerAddressLimitMessage
mintTo: (recipient: string) => MintToMessage
mintFor: (recipient: string, tokenId: number) => MintForMessage
batchMint: (recipient: string, batchNumber: number) => BatchMintMessage
shuffle: () => ShuffleMessage
withdraw: () => WithdrawMessage
}
export interface MintMessage {
@ -151,7 +151,7 @@ export interface MinterContract {
use: (contractAddress: string) => MinterInstance
messages: () => MinterMessages
messages: (contractAddress: string) => MinterMessages
}
export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterContract => {
@ -365,8 +365,8 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const messages = () => {
const mint = (contractAddress: string, price: string): MintMessage => {
const messages = (contractAddress: string) => {
const mint = (price: string): MintMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -377,7 +377,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const setWhitelist = (contractAddress: string, whitelist: string): SetWhitelistMessage => {
const setWhitelist = (whitelist: string): SetWhitelistMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -390,7 +390,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const updateStartTime = (contractAddress: string, startTime: string): UpdateStarTimeMessage => {
const updateStartTime = (startTime: string): UpdateStarTimeMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -401,7 +401,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const updatePerAddressLimit = (contractAddress: string, limit: number): UpdatePerAddressLimitMessage => {
const updatePerAddressLimit = (limit: number): UpdatePerAddressLimitMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -414,7 +414,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const mintTo = (contractAddress: string, recipient: string): MintToMessage => {
const mintTo = (recipient: string): MintToMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -427,7 +427,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const mintFor = (contractAddress: string, recipient: string, tokenId: number): MintForMessage => {
const mintFor = (recipient: string, tokenId: number): MintForMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -441,7 +441,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const batchMint = (contractAddress: string, recipient: string, batchNumber: number): BatchMintMessage => {
const batchMint = (recipient: string, batchNumber: number): BatchMintMessage => {
const msg: Record<string, unknown>[] = []
for (let i = 0; i < batchNumber; i++) {
msg.push({ mint_to: { recipient } })
@ -454,7 +454,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const shuffle = (contractAddress: string): ShuffleMessage => {
const shuffle = (): ShuffleMessage => {
return {
sender: txSigner,
contract: contractAddress,
@ -465,7 +465,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
}
}
const withdraw = (contractAddress: string): WithdrawMessage => {
const withdraw = (): WithdrawMessage => {
return {
sender: txSigner,
contract: contractAddress,

View File

@ -35,7 +35,7 @@ export interface UseMinterContractProps {
use: (customAddress: string) => MinterInstance | undefined
updateContractAddress: (contractAddress: string) => void
getContractAddress: () => string | undefined
messages: () => MinterMessages | undefined
messages: (contractAddress: string) => MinterMessages | undefined
}
export function useMinterContract(): UseMinterContractProps {
@ -81,9 +81,12 @@ export function useMinterContract(): UseMinterContractProps {
return address
}
const messages = useCallback((): MinterMessages | undefined => {
return minter?.messages()
}, [minter])
const messages = useCallback(
(customAddress = ''): MinterMessages | undefined => {
return minter?.messages(address || customAddress)
},
[minter, address],
)
return {
instantiate,

View File

@ -1,7 +1,8 @@
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import type { MsgExecuteContractEncodeObject, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { toBase64, toUtf8 } from '@cosmjs/encoding'
import type { Coin } from '@cosmjs/stargate'
import { coin } from '@cosmjs/stargate'
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
export interface InstantiateResponse {
readonly contractAddress: string
@ -67,6 +68,7 @@ export interface SG721Instance {
/// Burn an NFT the sender has access to
burn: (tokenId: string) => Promise<string>
batchBurn: (tokenIds: string) => Promise<string>
}
export interface Sg721Messages {
@ -78,6 +80,7 @@ export interface Sg721Messages {
revokeAll: (operator: string) => RevokeAllMessage
mint: (tokenId: string, owner: string, tokenURI?: string) => MintMessage
burn: (tokenId: string) => BurnMessage
batchBurn: (tokenIds: string) => BatchBurnMessage
}
export interface TransferNFTMessage {
@ -178,6 +181,13 @@ export interface BurnMessage {
funds: Coin[]
}
export interface BatchBurnMessage {
sender: string
contract: string
msg: Record<string, unknown>[]
funds: Coin[]
}
export interface SG721Contract {
instantiate: (
senderAddress: string,
@ -408,6 +418,49 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
return res.transactionHash
}
const batchBurn = async (tokenIds: 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 = {
burn: { token_id: i.toString() },
}
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 = {
burn: { token_id: tokenNumbers[i].toString() },
}
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 burn')
return res.transactionHash
}
return {
contractAddress,
ownerOf,
@ -430,6 +483,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
revokeAll,
mint,
burn,
batchBurn,
}
}
@ -552,10 +606,10 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
}
}
const burn = (tokenId: string) => {
const burn = (contractAddr: string, tokenId: string) => {
return {
sender: txSigner,
contract: contractAddress,
contract: contractAddr,
msg: {
burn: {
token_id: tokenId,
@ -565,6 +619,30 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
}
}
const batchBurn = (contractAddr: string, tokenIds: string): BatchBurnMessage => {
const msg: Record<string, unknown>[] = []
if (tokenIds.includes(':')) {
const [start, end] = tokenIds.split(':').map(Number)
for (let i = start; i <= end; i++) {
msg.push({
burn: { token_id: i.toString() },
})
}
} else {
const tokenNumbers = tokenIds.split(',').map(Number)
for (let i = 0; i < tokenNumbers.length; i++) {
msg.push({ burn: { token_id: tokenNumbers[i].toString() } })
}
}
return {
sender: txSigner,
contract: contractAddr,
msg,
funds: [],
}
}
return {
transferNft,
sendNft,
@ -574,6 +652,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
revokeAll,
mint,
burn,
batchBurn,
}
}

View File

@ -149,7 +149,7 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
return messages(contract)?.mint(args.recipient, args.tokenId, args.tokenURI)
}
case 'burn': {
return messages(contract)?.burn(args.tokenId)
return messages(contract)?.burn(args.contract, args.tokenId)
}
default: {
return {}

View File

@ -62,9 +62,12 @@ export function useSG721Contract(): UseSG721ContractProps {
[SG721, address],
)
const messages = useCallback((): Sg721Messages | undefined => {
return SG721?.messages(address)
}, [SG721, address])
const messages = useCallback(
(customAddress = ''): Sg721Messages | undefined => {
return SG721?.messages(address || customAddress)
},
[SG721, address],
)
return {
instantiate,

View File

@ -58,9 +58,12 @@ export function useWhiteListContract(): UseWhiteListContractProps {
[whiteList, address],
)
const messages = useCallback((): WhitelistMessages | undefined => {
return whiteList?.messages(address)
}, [whiteList, address])
const messages = useCallback(
(customAddress = ''): WhitelistMessages | undefined => {
return whiteList?.messages(address || customAddress)
},
[whiteList, address],
)
return {
instantiate,

View File

@ -23,6 +23,8 @@ import { useMutation } from 'react-query'
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
import { TextInput } from '../../components/forms/FormInput'
const CollectionActionsPage: NextPage = () => {
const { minter: minterContract, sg721: sg721Contract } = useContracts()
const wallet = useWallet()
@ -68,6 +70,14 @@ const CollectionActionsPage: NextPage = () => {
subtitle: 'Enter the number of tokens to mint',
})
const tokenIdListState = useInputState({
id: 'token-id-list',
name: 'tokenIdList',
title: 'The list of token IDs',
subtitle:
'Specify individual token IDs separated by commas (e.g., 2, 4, 8) or a range of IDs separated by a colon (e.g, 8:13)',
})
const recipientState = useInputState({
id: 'recipient-address',
name: 'recipient',
@ -85,8 +95,9 @@ const CollectionActionsPage: NextPage = () => {
const showWhitelistField = type === 'set_whitelist'
const showDateField = type === 'update_start_time'
const showLimitField = type === 'update_per_address_limit'
const showTokenIdField = isEitherType(type, ['transfer', 'mint_for'])
const showTokenIdListField = type === 'batch_mint'
const showTokenIdField = isEitherType(type, ['transfer', 'mint_for', 'burn'])
const showNumberOfTokensField = type === 'batch_mint'
const showTokenIdListField = type === 'batch_burn'
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint'])
const minterMessages = useMemo(
@ -104,6 +115,7 @@ const CollectionActionsPage: NextPage = () => {
minterContract: minterContractState.value,
sg721Contract: sg721ContractState.value,
tokenId: tokenIdState.value,
tokenIds: tokenIdListState.value,
batchNumber: batchNumberState.value,
minterMessages,
sg721Messages,
@ -154,7 +166,8 @@ const CollectionActionsPage: NextPage = () => {
{showWhitelistField && <AddressInput {...whitelistState} />}
{showLimitField && <NumberInput {...limitState} />}
{showTokenIdField && <NumberInput {...tokenIdState} />}
{showTokenIdListField && <NumberInput {...batchNumberState} />}
{showTokenIdListField && <TextInput {...tokenIdListState} />}
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
<Conditional test={showDateField}>
<FormControl htmlId="start-date" subtitle="Start time for the minting" title="Start Time">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />