From 26663e4f24d126a9cec03ad0c6e35f28a68c8a01 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 08:49:11 +0300 Subject: [PATCH 1/9] Update action list --- components/collections/actions/actions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/collections/actions/actions.ts b/components/collections/actions/actions.ts index 63ab38d..d20f9c1 100644 --- a/components/collections/actions/actions.ts +++ b/components/collections/actions/actions.ts @@ -31,6 +31,7 @@ export const ACTION_TYPES = [ 'batch_mint_for', 'shuffle', 'airdrop', + 'airdrop_specific', 'burn_remaining', 'update_token_metadata', 'batch_update_token_metadata', @@ -183,6 +184,11 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [ name: 'Airdrop Tokens', description: 'Airdrop tokens to given addresses', }, + { + id: 'airdrop_specific', + name: 'Airdrop Specific Tokens', + description: 'Airdrop specific tokens to given addresses', + }, { id: 'burn_remaining', name: 'Burn Remaining Tokens', From fddedd6679421210066799fc09a774cf74e2edbd Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:06:29 +0300 Subject: [PATCH 2/9] Token id compatibility for AirdropUpload.tsx --- components/AirdropUpload.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/AirdropUpload.tsx b/components/AirdropUpload.tsx index a73ffad..81711de 100644 --- a/components/AirdropUpload.tsx +++ b/components/AirdropUpload.tsx @@ -38,7 +38,7 @@ export const AirdropUpload = ({ onChange }: AirdropUploadProps) => { .then((res) => { const tokenUri = JSON.parse(new TextDecoder().decode(res as Uint8Array)).token_uri if (tokenUri && isValidAddress(tokenUri)) - resolvedAllocationData.push({ address: tokenUri, amount: data.amount }) + resolvedAllocationData.push({ address: tokenUri, amount: data.amount, tokenId: data.tokenId }) else toast.error(`Resolved address is empty or invalid for the name: ${data.address}`) }) .catch((e) => { @@ -80,8 +80,15 @@ export const AirdropUpload = ({ onChange }: AirdropUploadProps) => { .map((data) => ({ address: data.address.trim(), amount: data.amount, + tokenId: data.tokenId, })) - .concat(resolvedAllocationData), + .concat( + resolvedAllocationData.map((data) => ({ + address: data.address, + amount: data.amount, + tokenId: data.tokenId, + })), + ), ) }, ) From 893b5b89c3c85a1d9ed0b215820f351fb61b8c27 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:08:18 +0300 Subject: [PATCH 3/9] Update .csv to array logic --- utils/csvToArray.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/csvToArray.ts b/utils/csvToArray.ts index eb3e44c..def2bcd 100644 --- a/utils/csvToArray.ts +++ b/utils/csvToArray.ts @@ -5,11 +5,11 @@ export const csvToArray = (str: string, delimiter = ',') => { if (str.includes('\r')) newline = '\r' if (str.includes('\r\n')) newline = '\r\n' - const headers = str.slice(0, str.indexOf(newline)).split(delimiter) + const headers = str.trim().slice(0, str.indexOf(newline)).split(delimiter) if (headers.length !== 2) { throw new Error('Invalid accounts file') } - if (headers[0] !== 'address' || headers[1] !== 'amount') { + if (headers[0] !== 'address' || (headers[1] !== 'amount' && headers[1] !== 'tokenId')) { throw new Error('Invalid accounts file') } From 9742ec6d043a90bfb44051babad3fb426fd46333 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:08:47 +0300 Subject: [PATCH 4/9] Update .csv content validation --- utils/isValidAccountsFile.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/utils/isValidAccountsFile.ts b/utils/isValidAccountsFile.ts index 91c3ded..8eda11f 100644 --- a/utils/isValidAccountsFile.ts +++ b/utils/isValidAccountsFile.ts @@ -4,13 +4,15 @@ import { isValidAddress } from './isValidAddress' export interface AirdropAllocation { address: string - amount: string + amount?: string + tokenId?: string } export const isValidAccountsFile = (file: AirdropAllocation[]) => { let sumOfAmounts = 0 file.forEach((allocation) => { sumOfAmounts += Number(allocation.amount) + console.log('sum: ', sumOfAmounts) }) if (sumOfAmounts > 10000) { toast.error(`Accounts file must cover less than 10000 tokens`) @@ -28,10 +30,19 @@ export const isValidAccountsFile = (file: AirdropAllocation[]) => { if (!account.address.trim().startsWith('stars') && !account.address.trim().endsWith('.stars')) { return { address: false } } + + if (!account.amount && !account.tokenId) { + return { amount: false, tokenId: false } + } + // Check if amount is valid - if (!Number.isInteger(Number(account.amount)) || !(Number(account.amount) > 0)) { + if (account.amount && (!Number.isInteger(Number(account.amount)) || !(Number(account.amount) > 0))) { return { amount: false } } + // Check if tokenId is valid + if (account.tokenId && (!Number.isInteger(Number(account.tokenId)) || !(Number(account.tokenId) > 0))) { + return { tokenId: false } + } return null }) @@ -47,11 +58,22 @@ export const isValidAccountsFile = (file: AirdropAllocation[]) => { toast.error('Invalid address in file') return false } + + if (checks.filter((check) => check?.amount === false && check.tokenId === false).length > 0) { + toast.error('No amount or token ID found in the file. Please check the header.') + return false + } + if (checks.filter((check) => check?.amount === false).length > 0) { toast.error('Invalid amount in file. Amount must be a positive integer.') return false } + if (checks.filter((check) => check?.tokenId === false).length > 0) { + toast.error('Invalid token ID in file. Token ID must be a positive integer.') + return false + } + // if (duplicateCheck.length > 0) { // toast.error('The file contains duplicate addresses.') // return false From 1b6f4298435e67bac66b36f14e60e847e8b096c3 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:09:13 +0300 Subject: [PATCH 5/9] Update vending minter contract helpers --- components/collections/actions/actions.ts | 8 +++++ contracts/vendingMinter/contract.ts | 41 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/components/collections/actions/actions.ts b/components/collections/actions/actions.ts index d20f9c1..9b1a82d 100644 --- a/components/collections/actions/actions.ts +++ b/components/collections/actions/actions.ts @@ -5,6 +5,7 @@ import type { CollectionInfo, SG721Instance } from 'contracts/sg721' import { useSG721Contract } from 'contracts/sg721' import type { VendingMinterInstance } from 'contracts/vendingMinter' import { useVendingMinterContract } from 'contracts/vendingMinter' +import type { AirdropAllocation } from 'utils/isValidAccountsFile' import type { BaseMinterInstance } from '../../../contracts/baseMinter/contract' @@ -243,6 +244,7 @@ export interface DispatchExecuteArgs { limit: number tokenIds: string recipients: string[] + tokenRecipients: AirdropAllocation[] collectionInfo: CollectionInfo | undefined baseUri: string } @@ -325,6 +327,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => { case 'airdrop': { return vendingMinterMessages.airdrop(txSigner, args.recipients) } + case 'airdrop_specific': { + return vendingMinterMessages.airdropSpecificTokens(txSigner, args.tokenRecipients) + } case 'burn_remaining': { return vendingMinterMessages.burnRemaining(txSigner) } @@ -415,6 +420,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => { case 'airdrop': { return vendingMinterMessages(minterContract)?.airdrop(args.recipients) } + case 'airdrop_specific': { + return vendingMinterMessages(minterContract)?.airdropSpecificTokens(args.tokenRecipients) + } case 'burn_remaining': { return vendingMinterMessages(minterContract)?.burnRemaining() } diff --git a/contracts/vendingMinter/contract.ts b/contracts/vendingMinter/contract.ts index 8483fa3..2f5fcf0 100644 --- a/contracts/vendingMinter/contract.ts +++ b/contracts/vendingMinter/contract.ts @@ -5,6 +5,7 @@ import { coin } from '@cosmjs/proto-signing' import type { logs } from '@cosmjs/stargate' import type { Timestamp } from '@stargazezone/types/contracts/minter/shared-types' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import type { AirdropAllocation } from 'utils/isValidAccountsFile' export interface InstantiateResponse { readonly contractAddress: string @@ -47,6 +48,7 @@ export interface VendingMinterInstance { shuffle: (senderAddress: string) => Promise withdraw: (senderAddress: string) => Promise airdrop: (senderAddress: string, recipients: string[]) => Promise + airdropSpecificTokens: (senderAddress: string, tokenRecipients: AirdropAllocation[]) => Promise burnRemaining: (senderAddress: string) => Promise updateDiscountPrice: (senderAddress: string, price: string) => Promise removeDiscountPrice: (senderAddress: string) => Promise @@ -67,6 +69,7 @@ export interface VendingMinterMessages { shuffle: () => ShuffleMessage withdraw: () => WithdrawMessage airdrop: (recipients: string[]) => CustomMessage + airdropSpecificTokens: (recipients: AirdropAllocation[]) => CustomMessage burnRemaining: () => BurnRemainingMessage updateDiscountPrice: (price: string) => UpdateDiscountPriceMessage removeDiscountPrice: () => RemoveDiscountPriceMessage @@ -553,6 +556,29 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): return res.transactionHash } + const airdropSpecificTokens = async (senderAddress: string, recipients: AirdropAllocation[]): Promise => { + const executeContractMsgs: MsgExecuteContractEncodeObject[] = [] + for (let i = 0; i < recipients.length; i++) { + const msg = { + mint_for: { recipient: recipients[i].address, token_id: Number(recipients[i].tokenId) }, + } + 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', 'airdrop_specific_tokens') + + return res.transactionHash + } + const shuffle = async (senderAddress: string): Promise => { const res = await client.execute( senderAddress, @@ -617,6 +643,7 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): batchMintFor, batchMint, airdrop, + airdropSpecificTokens, shuffle, withdraw, burnRemaining, @@ -838,6 +865,19 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): } } + const airdropSpecificTokens = (recipients: AirdropAllocation[]): CustomMessage => { + const msg: Record[] = [] + for (let i = 0; i < recipients.length; i++) { + msg.push({ mint_for: { recipient: recipients[i].address, token_id: recipients[i].tokenId } }) + } + return { + sender: txSigner, + contract: contractAddress, + msg, + funds: [], + } + } + const shuffle = (): ShuffleMessage => { return { sender: txSigner, @@ -886,6 +926,7 @@ export const vendingMinter = (client: SigningCosmWasmClient, txSigner: string): batchMintFor, batchMint, airdrop, + airdropSpecificTokens, shuffle, withdraw, burnRemaining, From c041a53b67ac6b88307daaca2c1e2822f42cb66f Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:09:50 +0300 Subject: [PATCH 6/9] Update Collection Actions > Actions --- components/collections/actions/Action.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/collections/actions/Action.tsx b/components/collections/actions/Action.tsx index c628b30..ce95535 100644 --- a/components/collections/actions/Action.tsx +++ b/components/collections/actions/Action.tsx @@ -185,7 +185,7 @@ export const CollectionActions = ({ 'batch_transfer', 'batch_mint_for', ]) - const showAirdropFileField = type === 'airdrop' + const showAirdropFileField = isEitherType(type, ['airdrop', 'airdrop_specific']) const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price']) const showDescriptionField = type === 'update_collection_info' const showImageField = type === 'update_collection_info' @@ -211,6 +211,7 @@ export const CollectionActions = ({ sg721Messages, recipient: resolvedRecipientAddress, recipients: airdropArray, + tokenRecipients: airdropAllocationArray, txSigner: wallet.address, type, price: priceState.value.toString(), @@ -477,7 +478,9 @@ export const CollectionActions = ({ )} {showAirdropFileField && ( From 18f77f014ff3006b511f83faade497b4073ace64 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:10:42 +0300 Subject: [PATCH 7/9] Bump Studio version --- .env.example | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 866de6b..e0eba3e 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -APP_VERSION=0.6.3 +APP_VERSION=0.6.4 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS NEXT_PUBLIC_SG721_CODE_ID=2092 diff --git a/package.json b/package.json index daf9fbb..9853fe4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.6.3", + "version": "0.6.4", "workspaces": [ "packages/*" ], From be30701c8f6530a89445482ba95121cc85e8cc9f Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:19:53 +0300 Subject: [PATCH 8/9] Clean up --- utils/isValidAccountsFile.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/isValidAccountsFile.ts b/utils/isValidAccountsFile.ts index 8eda11f..28b7c03 100644 --- a/utils/isValidAccountsFile.ts +++ b/utils/isValidAccountsFile.ts @@ -12,7 +12,6 @@ export const isValidAccountsFile = (file: AirdropAllocation[]) => { let sumOfAmounts = 0 file.forEach((allocation) => { sumOfAmounts += Number(allocation.amount) - console.log('sum: ', sumOfAmounts) }) if (sumOfAmounts > 10000) { toast.error(`Accounts file must cover less than 10000 tokens`) From cb9f2bc1da2f2e59933f4d17247ec56d2aa12639 Mon Sep 17 00:00:00 2001 From: Serkan Reis Date: Thu, 8 Jun 2023 10:29:25 +0300 Subject: [PATCH 9/9] Update action description for airdropping specific tokens --- components/collections/actions/Action.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/collections/actions/Action.tsx b/components/collections/actions/Action.tsx index ce95535..7c509ee 100644 --- a/components/collections/actions/Action.tsx +++ b/components/collections/actions/Action.tsx @@ -478,7 +478,9 @@ export const CollectionActions = ({ )} {showAirdropFileField && (