import { NFT_STORAGE_API_KEY } from "../../constants"; import { Alert, Button, FormControl, FormHelperText, FormLabel, Heading, HStack, Input, Stack, Image, Text, Switch, useRadioGroup, VStack, Flex, } from "@chakra-ui/react"; import { yupResolver } from "@hookform/resolvers/yup"; import { DataV2 } from "@metaplex-foundation/mpl-token-metadata"; import { NATIVE_MINT } from "@solana/spl-token"; import { useWallet } from "@solana/wallet-adapter-react"; import { Keypair, PublicKey } from "@solana/web3.js"; import { MarketplaceSdk } from "@strata-foundation/marketplace-sdk"; import { humanReadablePercentage, useCollective, useProvider, usePublicKey, useTokenMetadata, } from "@strata-foundation/react"; import { ICurveConfig, TimeCurveConfig, TimeDecayExponentialCurveConfig, } from "@strata-foundation/spl-token-bonding"; import { ITokenBondingSettings, SplTokenCollective, } from "@strata-foundation/spl-token-collective"; import { useRouter } from "next/router"; import React from "react"; import { useAsync, useAsyncCallback } from "react-async-hook"; import { FormProvider, useForm } from "react-hook-form"; import * as yup from "yup"; import { useMarketplaceSdk } from "../..//contexts/marketplaceSdkContext"; import { route, routes } from "../../utils/routes"; import { FormControlWithError } from "./FormControlWithError"; import { MintSelect } from "./MintSelect"; import { IMetadataFormProps, TokenMetadataInputs } from "./TokenMetadataInputs"; import { Disclosures, disclosuresSchema, IDisclosures } from "./Disclosures"; import { RadioCardWithAffordance } from "./RadioCard"; import { useWalletModal } from "@solana/wallet-adapter-react-ui"; type CurveType = "aggressive" | "stable" | "utility"; interface IFullyManagedForm extends IMetadataFormProps { mint: string; symbol: string; curveType: CurveType; isSocial: boolean; startingPrice: number; isAntiBot: boolean; sellBaseRoyaltyPercentage: number; buyBaseRoyaltyPercentage: number; sellTargetRoyaltyPercentage: number; buyTargetRoyaltyPercentage: number; disclosures: IDisclosures; } const validationSchema = yup.object({ mint: yup.string().required(), image: yup.mixed().required(), name: yup.string().required().min(2), description: yup.string().required().min(2), symbol: yup.string().required().min(2), startingPrice: yup.number().required().min(0), isAntiBot: yup.boolean(), isSocial: yup.boolean(), sellBaseRoyaltyPercentage: yup.number().required(), buyBaseRoyaltyPercentage: yup.number().required(), sellTargetRoyaltyPercentage: yup.number().required(), buyTargetRoyaltyPercentage: yup.number().required(), disclosures: disclosuresSchema, }); async function createFullyManaged( marketplaceSdk: MarketplaceSdk, values: IFullyManagedForm ): Promise { const mint = new PublicKey(values.mint); const tokenCollectiveSdk = marketplaceSdk.tokenCollectiveSdk; const tokenBondingSdk = tokenCollectiveSdk.splTokenBondingProgram; const targetMintKeypair = Keypair.generate(); let k = 0; switch (values.curveType) { case "utility": k = 0.5; break; case "stable": k = 1; break; case "aggressive": k = 2; break; } const c = values.startingPrice * (k + 1); let config: ICurveConfig = new TimeDecayExponentialCurveConfig({ c, k0: k, k1: k, d: 1, interval: 2 * 60 * 60, // 2 hours }); if (values.isAntiBot) { config = new TimeCurveConfig() .addCurve( 0, new TimeDecayExponentialCurveConfig({ c, k0: 0, k1: 0, d: 1, interval: 0, }) ) .addCurve( 30 * 60, // 30 minutes new TimeDecayExponentialCurveConfig({ c, k0: 0, k1: k, d: 0.5, interval: 1.5 * 60 * 60, // 1.5 hours }) ); } const curveOut = await tokenBondingSdk.initializeCurveInstructions({ config, }); const bondingOpts = { baseMint: mint, buyBaseRoyaltyPercentage: values.buyBaseRoyaltyPercentage, buyTargetRoyaltyPercentage: values.buyTargetRoyaltyPercentage, sellBaseRoyaltyPercentage: values.sellBaseRoyaltyPercentage, sellTargetRoyaltyPercentage: values.sellTargetRoyaltyPercentage, curve: curveOut.output.curve, targetMint: targetMintKeypair.publicKey, targetMintDecimals: 9, }; const uri = await tokenCollectiveSdk.splTokenMetadata.uploadMetadata({ provider: values.provider, name: values.name, symbol: values.symbol, description: values.description, image: values.image, mint: targetMintKeypair.publicKey, }); const metadata = new DataV2({ // Max name len 32 name: values.name.substring(0, 32), symbol: values.symbol.substring(0, 10), uri, sellerFeeBasisPoints: 0, creators: null, collection: null, uses: null, }); if (values.isSocial) { const bondingOut = await tokenCollectiveSdk.createSocialTokenInstructions({ mint, tokenBondingParams: bondingOpts, owner: tokenCollectiveSdk.wallet.publicKey, targetMintKeypair, metadata, }); await tokenCollectiveSdk.executeBig( Promise.resolve({ output: null, instructions: [curveOut.instructions, ...bondingOut.instructions], signers: [curveOut.signers, ...bondingOut.signers], }) ); } else { const metaOut = await marketplaceSdk.createMetadataForBondingInstructions({ targetMintKeypair, metadataUpdateAuthority: tokenCollectiveSdk.wallet.publicKey, metadata, decimals: bondingOpts.targetMintDecimals, }); const bondingOut = await tokenBondingSdk.createTokenBondingInstructions( bondingOpts ); await tokenBondingSdk.executeBig( Promise.resolve({ output: null, instructions: [ [...curveOut.instructions, ...metaOut.instructions], bondingOut.instructions, ], signers: [ [...curveOut.signers, ...metaOut.signers], bondingOut.signers, ], }) ); } return targetMintKeypair.publicKey; } export const FullyManagedForm: React.FC = () => { const formProps = useForm({ // @ts-ignore resolver: yupResolver(validationSchema), defaultValues: { disclosures: { acceptedFees: true, }, }, }); const { register, handleSubmit, setValue, formState: { errors, isSubmitting }, watch, } = formProps; const { connected, publicKey } = useWallet(); const { visible, setVisible } = useWalletModal(); const { awaitingApproval } = useProvider(); const { execute, loading, error } = useAsyncCallback(createFullyManaged); const { marketplaceSdk } = useMarketplaceSdk(); const router = useRouter(); function percentOr(percentu32: number | undefined, def: number) { return percentu32 ? Number(humanReadablePercentage(percentu32)) : def; } const onSubmit = async (values: IFullyManagedForm) => { const mintKey = await execute(marketplaceSdk!, values); router.push( route(routes.swap, { id: mintKey.toBase58(), }), undefined, { shallow: true } ); }; const { name = "", symbol = "", isSocial, mint, curveType } = watch(); const mintKey = usePublicKey(mint); const { result: collectiveKey } = useAsync( async (mint: string | undefined) => mint ? SplTokenCollective.collectiveKey(new PublicKey(mint)) : undefined, [mint] ); const { info: collective } = useCollective(collectiveKey && collectiveKey[0]); const tokenBondingSettings = collective?.config .claimedTokenBondingSettings as ITokenBondingSettings | undefined; const { metadata: baseMetadata, error: baseMetadataError, loading: baseMetadataLoading, } = useTokenMetadata(mintKey); const { getRootProps, getRadioProps } = useRadioGroup({ name: "curveType", onChange: (option) => setValue("curveType", option as CurveType), }); const group = getRootProps(); const curveOptions = [ { value: "aggressive", heading: "Aggressive", illustration: "/aggressive.svg", helpText: "A curve with high price sensitivity. The price raises quickly when people buy, and lowers quickly when they sell. This is best suited for speculative use cases.", }, { value: "stable", heading: "Stable", illustration: "/stable.svg", helpText: "A curve with medium price sensitivity. This curve changes price at a constant rate, achieving a balance between aggressive and utility curves.", }, { value: "utility", heading: "Utility", illustration: "/utility.svg", helpText: "A curve with a price sensitivity that starts high and lowers with purchases. This curve is best suited for utility use cases, as it rewards early adopters and scales the supply so that the token can be exchanged for goods/services.", }, ]; return ( {!connected && ( )}
{curveOptions.map( ({ value, heading, illustration, helpText }) => { const radio = getRadioProps({ value }); return ( {`${value}-illustration`} {heading} {helpText} ); } )} setValue("mint", s)} />{" "} Royalties {symbol || "Managed Token"} (Buy) {symbol || "Managed Token"} (Sell) {baseMetadata?.data.symbol || "Base Token"} (Buy) {baseMetadata?.data.symbol || "Base Token"} (Sell) A Percentage of coin buys/sales that will be sent to your wallet. We recommend to keep this less than a combined 10% for buys/sales. {error && ( {error.toString()} )}
); };