Skip to content

Commit

Permalink
feat(launchpad): Add useCreateCollection.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
WaDadidou committed Jan 5, 2025
1 parent a8d6ccd commit 3344964
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 6 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"graphql-request": "^5",
"html-to-draftjs": "^1.5.0",
"immutable": "^4.0.0",
"keccak256": "^1.0.6",
"kubernetes-models": "^4.3.1",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
Expand Down
245 changes: 245 additions & 0 deletions packages/hooks/launchpad/useCreateCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import keccak256 from "keccak256"; // Tested and this lib is compatible with merkle tree libs from Rust and Go
import { MerkleTree } from "merkletreejs";
import { useCallback } from "react";
import { useSelector } from "react-redux";

import { useFeedbacks } from "@/context/FeedbacksProvider";
import {
Coin,
MintPeriod,
NftLaunchpadClient,
WhitelistInfo,
} from "@/contracts-clients/nft-launchpad";
import { PinataFileProps, useIpfs } from "@/hooks/useIpfs";
import { useSelectedNetworkId } from "@/hooks/useSelectedNetwork";
import useSelectedWallet from "@/hooks/useSelectedWallet";
import { getNetworkFeature, NetworkFeature } from "@/networks";
import { getKeplrSigningCosmWasmClient } from "@/networks/signer";
import { selectNFTStorageAPI } from "@/store/slices/settings";
import { generateIpfsKey } from "@/utils/ipfs";
import { LocalFileData } from "@/utils/types/files";
import {
CollectionAssetsMetadatasFormValues,
CollectionFormValues,
CollectionMintPeriodFormValues,
CollectionToSubmit,
} from "@/utils/types/launchpad";

export const useCreateCollection = () => {
// Since the Collection network is the selected network, we use useSelectedNetworkId (See LaunchpadBasic.tsx)
const selectedNetworkId = useSelectedNetworkId();
const selectedWallet = useSelectedWallet();
const { setToast } = useFeedbacks();
const userIPFSKey = useSelector(selectNFTStorageAPI);
const { pinataPinFileToIPFS, uploadFilesToPinata } = useIpfs();

// TODO: Uncomment when the NFT Launchpad MyCollections frontend and useCompleteCollection are merged
// const { completeCollection } = useCompleteCollection();

const createCollection = useCallback(
async (collectionFormValues: CollectionFormValues) => {
if (!selectedWallet) return false;
const userId = selectedWallet.userId;
const walletAddress = selectedWallet.address;

const signingComswasmClient =
await getKeplrSigningCosmWasmClient(selectedNetworkId);
const cosmwasmNftLaunchpadFeature = getNetworkFeature(
selectedNetworkId,
NetworkFeature.CosmWasmNFTLaunchpad,
);
if (!cosmwasmNftLaunchpadFeature) return false;

const nftLaunchpadContractClient = new NftLaunchpadClient(
signingComswasmClient,
walletAddress,
cosmwasmNftLaunchpadFeature.launchpadContractAddress,
);
const pinataJWTKey =
collectionFormValues.assetsMetadatas?.nftApiKey ||
userIPFSKey ||
(await generateIpfsKey(selectedNetworkId, userId));
if (!pinataJWTKey) {
console.error("Project creation error: No Pinata JWT");
setToast({
mode: "normal",
type: "error",
title: "Project creation error: No Pinata JWT",
});
return false;
}

try {
// ========== Cover image
const fileIpfsHash = await pinataPinFileToIPFS({
pinataJWTKey,
file: collectionFormValues.coverImage,
} as PinataFileProps);
if (!fileIpfsHash) {
console.error("Project creation error: Pin to Pinata failed");
setToast({
mode: "normal",
type: "error",
title: "Project creation error: Pin to Pinata failed",
});
return false;
}

// ========== Whitelists
const whitelistAddressesFilesToUpload: LocalFileData[] = [];
collectionFormValues.mintPeriods.forEach((mintPeriod) => {
if (mintPeriod.whitelistAddressesFile)
whitelistAddressesFilesToUpload.push(
mintPeriod.whitelistAddressesFile,
);
});
const remoteWhitelistAddressesFiles = await uploadFilesToPinata({
pinataJWTKey,
files: whitelistAddressesFilesToUpload,
});
const mint_periods: MintPeriod[] = collectionFormValues.mintPeriods.map(
(mintPeriod: CollectionMintPeriodFormValues, index) => {
let whitelist_info: WhitelistInfo | null = null;
if (
mintPeriod.whitelistAddresses?.length &&
remoteWhitelistAddressesFiles[index].url
) {
const addresses: string[] = mintPeriod.whitelistAddresses;
const leaves = addresses.map(keccak256);
const tree = new MerkleTree(leaves, keccak256);
const merkleRoot = tree.getRoot().toString("hex");
whitelist_info = {
addresses_count: addresses.length,
addresses_ipfs: remoteWhitelistAddressesFiles[index].url,
addresses_merkle_root: merkleRoot,
};
}
const price: Coin | null = mintPeriod.price
? {
amount: mintPeriod.price.amount || "0",
denom: mintPeriod.price.denom,
}
: null;
return {
price,
end_time: mintPeriod.endTime,
max_tokens: mintPeriod.maxTokens
? parseInt(mintPeriod.maxTokens, 10)
: 0,
limit_per_address: mintPeriod.perAddressLimit
? parseInt(mintPeriod.perAddressLimit, 10)
: 0,
start_time: mintPeriod.startTime,
whitelist_info,
};
},
);

const assetsMetadataFormsValues:
| CollectionAssetsMetadatasFormValues
| undefined
| null = collectionFormValues.assetsMetadatas;

// ========== Final collection
const collection: CollectionToSubmit = {
name: collectionFormValues.name,
desc: collectionFormValues.description,
symbol: collectionFormValues.symbol,
website_link: collectionFormValues.websiteLink,
contact_email: collectionFormValues.email,
project_type: collectionFormValues.projectTypes.join(),
tokens_count: assetsMetadataFormsValues?.assetsMetadatas?.length || 0,
reveal_time: collectionFormValues.revealTime,
team_desc: collectionFormValues.teamDescription,
partners: collectionFormValues.partnersDescription,
investment_desc: collectionFormValues.investDescription,
investment_link: collectionFormValues.investLink,
artwork_desc: collectionFormValues.artworkDescription,
cover_img_uri: "ipfs://" + fileIpfsHash,
is_applied_previously: collectionFormValues.isPreviouslyApplied,
is_project_derivative: collectionFormValues.isDerivativeProject,
is_ready_for_mint: collectionFormValues.isReadyForMint,
is_dox: collectionFormValues.isDox,
escrow_mint_proceeds_period: parseInt(
collectionFormValues.escrowMintProceedsPeriod,
10,
),
dao_whitelist_count: parseInt(
collectionFormValues.daoWhitelistCount,
10,
),
mint_periods,
royalty_address: collectionFormValues.royaltyAddress,
royalty_percentage: collectionFormValues.royaltyPercentage
? parseInt(collectionFormValues.royaltyPercentage, 10)
: null,
target_network: selectedNetworkId,
};
// TODO: Uncomment when the NFT Launchpad MyCollections frontend and useCompleteCollection are merged
// const collectionId = collectionFormValues.symbol;

// ========== Submit the collection through the contract
await nftLaunchpadContractClient.submitCollection({
collection,
});

// ========== Handle assets metadata
if (!assetsMetadataFormsValues?.assetsMetadatas?.length) {
setToast({
mode: "normal",
type: "success",
title: "Project submitted (Incomplete)",
message: "You will need to Complete the Project",
});
} else {
const isCompleteSuccess = false;
// TODO: Uncomment when the NFT Launchpad MyCollections frontend and useCompleteCollection are merged
// await completeCollection(
// collectionId,
// assetsMetadataFormsValues,
// );

if (!isCompleteSuccess) {
setToast({
mode: "normal",
type: "warning",
title: "Project submitted (Incomplete)",
message:
"Error during uploading the Assets.\nYou will need to Complete the Project",
});
} else {
setToast({
mode: "normal",
type: "success",
title: "Project submitted",
});
}
}

return true;
} catch (e: any) {
console.error("Error creating a NFT Collection in the Launchpad: ", e);
setToast({
mode: "normal",
type: "error",
title: "Error creating a NFT Collection in the Launchpad",
message: e.message,
});
}
},
[
pinataPinFileToIPFS,
selectedWallet,
userIPFSKey,
uploadFilesToPinata,
selectedNetworkId,
setToast,
// TODO: Uncomment when the NFT Launchpad MyCollections frontend and useCompleteCollection are merged
// completeCollection,
],
);

return {
createCollection,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PrimaryButton } from "@/components/buttons/PrimaryButton";
import { SecondaryButton } from "@/components/buttons/SecondaryButton";
import { SpacerColumn } from "@/components/spacer";
import { useFeedbacks } from "@/context/FeedbacksProvider";
import { useCreateCollection } from "@/hooks/launchpad/useCreateCollection";
import { useSelectedNetworkInfo } from "@/hooks/useSelectedNetwork";
import { NetworkFeature } from "@/networks";
import {
Expand Down Expand Up @@ -53,10 +54,7 @@ export const LaunchpadCreateScreen: ScreenFC<"LaunchpadCreate"> = () => {
},
resolver: zodResolver(ZodCollectionFormValues),
});

// TODO: Uncomment when the NFT Launchpad backend is merged
// const { createCollection } = useCreateCollection();

const { createCollection } = useCreateCollection();
const [selectedStepKey, setSelectedStepKey] =
useState<LaunchpadCreateStepKey>(1);
const [isLoading, setLoading] = useState(false);
Expand Down Expand Up @@ -88,8 +86,9 @@ export const LaunchpadCreateScreen: ScreenFC<"LaunchpadCreate"> = () => {
setLoading(true);
setLoadingFullScreen(true);
try {
// TODO: Uncomment when the NFT Launchpad backend is merged
// const success = await createCollection(collectionForm.getValues());
// TODO: Uncomment when the NFT Launchpad MyCollections frontend is merged
// const success =
await createCollection(collectionForm.getValues());
// TODO: Uncomment when the NFT Launchpad MyCollections frontend is merged
// if (success) navigation.navigate("LaunchpadMyCollections");
} catch (e) {
Expand Down
10 changes: 10 additions & 0 deletions packages/utils/types/launchpad.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";

import { CollectionProject } from "@/contracts-clients/nft-launchpad";
import { DEFAULT_FORM_ERRORS } from "@/utils/errors";
import { isIpfsPathValid } from "@/utils/ipfs";
import {
Expand Down Expand Up @@ -169,3 +170,12 @@ export type CollectionAssetsMetadataFormValues = z.infer<
export type CollectionAssetsMetadatasFormValues = z.infer<
typeof ZodCollectionAssetsMetadatasFormValues
>;

// ===== Shapes to build objects to sent to API
export type CollectionToSubmit = Omit<
CollectionProject,
"deployed_address" | "base_token_uri" | "owner"
>;

// ===== Shapes to build objects received from API
// ...Coming soon
44 changes: 44 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14765,6 +14765,29 @@ __metadata:
languageName: node
linkType: hard

"keccak256@npm:^1.0.6":
version: 1.0.6
resolution: "keccak256@npm:1.0.6"
dependencies:
bn.js: ^5.2.0
buffer: ^6.0.3
keccak: ^3.0.2
checksum: decafb4b37adcfa6d06b6a5d28546d0d7a9f01ccf4b8cc8963cf8188fcc79a230d7e22988e860813623c602d764259734423e38fd7b9aadfeb409d6928a1d4cf
languageName: node
linkType: hard

"keccak@npm:^3.0.2":
version: 3.0.4
resolution: "keccak@npm:3.0.4"
dependencies:
node-addon-api: ^2.0.0
node-gyp: latest
node-gyp-build: ^4.2.0
readable-stream: ^3.6.0
checksum: 2bf27b97b2f24225b1b44027de62be547f5c7326d87d249605665abd0c8c599d774671c35504c62c9b922cae02758504c6f76a73a84234d23af8a2211afaaa11
languageName: node
linkType: hard

"keyv@npm:^4.0.0, keyv@npm:^4.5.3":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
Expand Down Expand Up @@ -16329,6 +16352,15 @@ __metadata:
languageName: node
linkType: hard

"node-addon-api@npm:^2.0.0":
version: 2.0.2
resolution: "node-addon-api@npm:2.0.2"
dependencies:
node-gyp: latest
checksum: 31fb22d674648204f8dd94167eb5aac896c841b84a9210d614bf5d97c74ef059cc6326389cf0c54d2086e35312938401d4cc82e5fcd679202503eb8ac84814f8
languageName: node
linkType: hard

"node-dir@npm:^0.1.17":
version: 0.1.17
resolution: "node-dir@npm:0.1.17"
Expand Down Expand Up @@ -16359,6 +16391,17 @@ __metadata:
languageName: node
linkType: hard

"node-gyp-build@npm:^4.2.0":
version: 4.8.4
resolution: "node-gyp-build@npm:4.8.4"
bin:
node-gyp-build: bin.js
node-gyp-build-optional: optional.js
node-gyp-build-test: build-test.js
checksum: 8b81ca8ffd5fa257ad8d067896d07908a36918bc84fb04647af09d92f58310def2d2b8614d8606d129d9cd9b48890a5d2bec18abe7fcff54818f72bedd3a7d74
languageName: node
linkType: hard

"node-gyp-build@npm:^4.3.0":
version: 4.8.0
resolution: "node-gyp-build@npm:4.8.0"
Expand Down Expand Up @@ -20466,6 +20509,7 @@ __metadata:
graphql-request: ^5
html-to-draftjs: ^1.5.0
immutable: ^4.0.0
keccak256: ^1.0.6
kubernetes-models: ^4.3.1
leaflet: ^1.9.4
leaflet.markercluster: ^1.5.3
Expand Down

0 comments on commit 3344964

Please sign in to comment.