diff --git a/components/collections/actions/Action.tsx b/components/collections/actions/Action.tsx index 85738bc..ef0bc99 100644 --- a/components/collections/actions/Action.tsx +++ b/components/collections/actions/Action.tsx @@ -198,7 +198,12 @@ export const CollectionActions = ({ 'batch_transfer', 'batch_mint_for', ]) - const showAirdropFileField = isEitherType(type, ['airdrop', 'airdrop_open_edition', 'airdrop_specific']) + const showAirdropFileField = isEitherType(type, [ + 'airdrop', + 'airdrop_open_edition', + 'airdrop_specific', + 'batch_transfer_multi_address', + ]) const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price']) const showDescriptionField = type === 'update_collection_info' const showImageField = type === 'update_collection_info' @@ -499,7 +504,7 @@ export const CollectionActions = ({ } allocated for each address. Should start with the following header row: ${ type === 'airdrop' ? 'address,amount' : 'address,tokenId' }`} - title="Airdrop File" + title={`${type === 'batch_transfer_multi_address' ? 'Multi-Recipient Transfer File' : 'Airdrop File'}`} > diff --git a/components/collections/actions/actions.ts b/components/collections/actions/actions.ts index 2d7c850..d3e69d6 100644 --- a/components/collections/actions/actions.ts +++ b/components/collections/actions/actions.ts @@ -31,6 +31,7 @@ export const ACTION_TYPES = [ 'freeze_collection_info', 'transfer', 'batch_transfer', + 'batch_transfer_multi_address', 'burn', 'batch_burn', 'batch_mint_for', @@ -82,6 +83,11 @@ export const BASE_ACTION_LIST: ActionListItem[] = [ name: 'Batch Transfer Tokens', description: `Transfer a list of tokens to a recipient`, }, + { + id: 'batch_transfer_multi_address', + name: 'Transfer Tokens to Multiple Recipients', + description: `Transfer a list of tokens to multiple addresses`, + }, { id: 'burn', name: 'Burn Token', @@ -170,6 +176,11 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [ name: 'Batch Transfer Tokens', description: `Transfer a list of tokens to a recipient`, }, + { + id: 'batch_transfer_multi_address', + name: 'Transfer Tokens to Multiple Recipients', + description: `Transfer a list of tokens to multiple addresses`, + }, { id: 'burn', name: 'Burn Token', @@ -258,6 +269,11 @@ export const OPEN_EDITION_ACTION_LIST: ActionListItem[] = [ name: 'Batch Transfer Tokens', description: `Transfer a list of tokens to a recipient`, }, + { + id: 'batch_transfer_multi_address', + name: 'Transfer Tokens to Multiple Recipients', + description: `Transfer a list of tokens to multiple addresses`, + }, { id: 'burn', name: 'Burn Token', @@ -405,6 +421,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { case 'batch_transfer': { return sg721Messages.batchTransfer(args.recipient, args.tokenIds) } + case 'batch_transfer_multi_address': { + return sg721Messages.batchTransferMultiAddress(txSigner, args.tokenRecipients) + } case 'burn': { return sg721Messages.burn(args.tokenId.toString()) } @@ -512,6 +531,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { case 'batch_transfer': { return sg721Messages(sg721Contract)?.batchTransfer(args.recipient, args.tokenIds) } + case 'batch_transfer_multi_address': { + return sg721Messages(sg721Contract)?.batchTransferMultiAddress(args.tokenRecipients) + } case 'burn': { return sg721Messages(sg721Contract)?.burn(args.tokenId.toString()) } diff --git a/contracts/sg721/contract.ts b/contracts/sg721/contract.ts index 6963d13..0027df4 100644 --- a/contracts/sg721/contract.ts +++ b/contracts/sg721/contract.ts @@ -3,6 +3,7 @@ import { toBase64, toUtf8 } from '@cosmjs/encoding' import type { Coin, logs } from '@cosmjs/stargate' import { coin } from '@cosmjs/stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import type { AirdropAllocation } from 'utils/isValidAccountsFile' import type { RoyaltyInfo } from '../vendingMinter/contract' @@ -86,6 +87,7 @@ export interface SG721Instance { burn: (tokenId: string) => Promise batchBurn: (tokenIds: string) => Promise batchTransfer: (recipient: string, tokenIds: string) => Promise + batchTransferMultiAddress: (senderAddress: string, tokenRecipients: AirdropAllocation[]) => Promise updateTokenMetadata: (tokenId: string, tokenURI: string) => Promise batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string, jsonExtensions: boolean) => Promise freezeTokenMetadata: () => Promise @@ -103,6 +105,7 @@ export interface Sg721Messages { burn: (tokenId: string) => BurnMessage batchBurn: (tokenIds: string) => BatchBurnMessage batchTransfer: (recipient: string, tokenIds: string) => BatchTransferMessage + batchTransferMultiAddress: (tokenRecipients: AirdropAllocation[]) => BatchTransferMultiAddressMessage updateCollectionInfo: (collectionInfo: CollectionInfo) => UpdateCollectionInfoMessage freezeCollectionInfo: () => FreezeCollectionInfoMessage updateTokenMetadata: (tokenId: string, tokenURI: string) => UpdateTokenMetadataMessage @@ -227,6 +230,13 @@ export interface BatchTransferMessage { funds: Coin[] } +export interface BatchTransferMultiAddressMessage { + sender: string + contract: string + msg: Record[] + funds: Coin[] +} + export interface UpdateTokenMetadataMessage { sender: string contract: string @@ -601,6 +611,37 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con return res.transactionHash } + const batchTransferMultiAddress = async ( + senderAddress: string, + recipients: AirdropAllocation[], + ): Promise => { + const executeContractMsgs: MsgExecuteContractEncodeObject[] = [] + for (let i = 0; i < recipients.length; i++) { + const msg = { + transfer_nft: { recipient: recipients[i].address, token_id: recipients[i].tokenId as string }, + } + const executeContractMsg: MsgExecuteContractEncodeObject = { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: senderAddress, + contract: contractAddress, + msg: toUtf8(JSON.stringify(msg)), + }), + } + + executeContractMsgs.push(executeContractMsg) + } + + const res = await client.signAndBroadcast( + senderAddress, + executeContractMsgs, + 'auto', + 'batch transfer to multiple recipients', + ) + + return res.transactionHash + } + // eslint-disable-next-line @typescript-eslint/no-shadow const updateCollectionInfo = async (collectionInfo: CollectionInfo): Promise => { const res = await client.execute( @@ -748,6 +789,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con burn, batchBurn, batchTransfer, + batchTransferMultiAddress, updateCollectionInfo, freezeCollectionInfo, updateTokenMetadata, @@ -950,6 +992,19 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con } } + const batchTransferMultiAddress = (recipients: AirdropAllocation[]): BatchTransferMultiAddressMessage => { + const msg: Record[] = [] + for (let i = 0; i < recipients.length; i++) { + msg.push({ transfer_nft: { recipient: recipients[i].address, token_id: recipients[i].tokenId } }) + } + return { + sender: txSigner, + contract: contractAddress, + msg, + funds: [], + } + } + const batchUpdateTokenMetadata = ( tokenIds: string, baseURI: string, @@ -1055,6 +1110,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con burn, batchBurn, batchTransfer, + batchTransferMultiAddress, updateCollectionInfo, freezeCollectionInfo, updateTokenMetadata,