From 00960f429eb1fe8192d190079aefba71b496548f Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Tue, 16 Aug 2022 13:04:23 +0300 Subject: [PATCH] Add batch transfer to sg721 collection actions (#40) --- components/collections/actions/actions.ts | 13 ++++ contracts/sg721/contract.ts | 78 +++++++++++++++++++++++ pages/collections/actions.tsx | 8 +-- 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/components/collections/actions/actions.ts b/components/collections/actions/actions.ts index ef53243..0290cff 100644 --- a/components/collections/actions/actions.ts +++ b/components/collections/actions/actions.ts @@ -14,6 +14,7 @@ export const ACTION_TYPES = [ 'update_per_address_limit', 'withdraw', 'transfer', + 'batch_transfer', 'burn', 'batch_burn', 'shuffle', @@ -66,6 +67,11 @@ export const ACTION_LIST: ActionListItem[] = [ name: 'Transfer Tokens', description: `Transfer tokens from one address to another`, }, + { + id: 'batch_transfer', + name: 'Batch Transfer Tokens', + description: `Transfer a list of tokens to a recipient`, + }, { id: 'burn', name: 'Burn Token', @@ -108,6 +114,7 @@ export type DispatchExecuteArgs = { | { type: Select<'shuffle'> } | { type: Select<'withdraw'> } | { type: Select<'transfer'>; recipient: string; tokenId: number } + | { type: Select<'batch_transfer'>; recipient: string; tokenIds: string } | { type: Select<'burn'>; tokenId: number } | { type: Select<'batch_burn'>; tokenIds: string } ) @@ -145,6 +152,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { case 'transfer': { return sg721Messages.transferNft(args.recipient, args.tokenId.toString()) } + case 'batch_transfer': { + return sg721Messages.batchTransfer(args.recipient, args.tokenIds) + } case 'burn': { return sg721Messages.burn(args.tokenId.toString()) } @@ -191,6 +201,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { case 'transfer': { return sg721Messages(sg721Contract)?.transferNft(args.recipient, args.tokenId.toString()) } + case 'batch_transfer': { + return sg721Messages(sg721Contract)?.batchTransfer(args.recipient, args.tokenIds) + } case 'burn': { return sg721Messages(sg721Contract)?.burn(args.tokenId.toString()) } diff --git a/contracts/sg721/contract.ts b/contracts/sg721/contract.ts index 1953e8e..ba108f1 100644 --- a/contracts/sg721/contract.ts +++ b/contracts/sg721/contract.ts @@ -69,6 +69,7 @@ export interface SG721Instance { /// Burn an NFT the sender has access to burn: (tokenId: string) => Promise batchBurn: (tokenIds: string) => Promise + batchTransfer: (recipient: string, tokenIds: string) => Promise } export interface Sg721Messages { @@ -81,6 +82,7 @@ export interface Sg721Messages { mint: (tokenId: string, owner: string, tokenURI?: string) => MintMessage burn: (tokenId: string) => BurnMessage batchBurn: (tokenIds: string) => BatchBurnMessage + batchTransfer: (recipient: string, tokenIds: string) => BatchTransferMessage } export interface TransferNFTMessage { @@ -188,6 +190,13 @@ export interface BatchBurnMessage { funds: Coin[] } +export interface BatchTransferMessage { + sender: string + contract: string + msg: Record[] + funds: Coin[] +} + export interface SG721Contract { instantiate: ( senderAddress: string, @@ -461,6 +470,49 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con return res.transactionHash } + const batchTransfer = async (recipient: string, tokenIds: string): Promise => { + const executeContractMsgs: MsgExecuteContractEncodeObject[] = [] + if (tokenIds.includes(':')) { + const [start, end] = tokenIds.split(':').map(Number) + for (let i = start; i <= end; i++) { + const msg = { + transfer_nft: { recipient, 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 = { + transfer_nft: { recipient, 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 transfer') + + return res.transactionHash + } + return { contractAddress, ownerOf, @@ -484,6 +536,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con mint, burn, batchBurn, + batchTransfer, } } @@ -643,6 +696,30 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con } } + const batchTransfer = (recipient: string, tokenIds: string): BatchTransferMessage => { + const msg: Record[] = [] + if (tokenIds.includes(':')) { + const [start, end] = tokenIds.split(':').map(Number) + for (let i = start; i <= end; i++) { + msg.push({ + trasnfer_nft: { recipient, token_id: i.toString() }, + }) + } + } else { + const tokenNumbers = tokenIds.split(',').map(Number) + for (let i = 0; i < tokenNumbers.length; i++) { + msg.push({ trasnfer_nft: { recipient, token_id: tokenNumbers[i].toString() } }) + } + } + + return { + sender: txSigner, + contract: contractAddress, + msg, + funds: [], + } + } + return { transferNft, sendNft, @@ -653,6 +730,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con mint, burn, batchBurn, + batchTransfer, } } diff --git a/pages/collections/actions.tsx b/pages/collections/actions.tsx index 6634a1a..96fe901 100644 --- a/pages/collections/actions.tsx +++ b/pages/collections/actions.tsx @@ -73,9 +73,9 @@ const CollectionActionsPage: NextPage = () => { const tokenIdListState = useInputState({ id: 'token-id-list', name: 'tokenIdList', - title: 'The list of token IDs', + title: '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)', + '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({ @@ -97,8 +97,8 @@ const CollectionActionsPage: NextPage = () => { const showLimitField = type === 'update_per_address_limit' 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 showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer']) + const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer']) const minterMessages = useMemo( () => minterContract?.use(minterContractState.value),