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,