Skip to content

Commit

Permalink
Merge pull request #908 from mrgnlabs/feature/permless-pools
Browse files Browse the repository at this point in the history
feat(mfi-v2-trading): permissionless pools
  • Loading branch information
k0beLeenders authored Oct 21, 2024
2 parents e2ce633 + f4eb672 commit e18ef31
Show file tree
Hide file tree
Showing 29 changed files with 800 additions and 781 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useConnection } from "~/hooks/use-connection";
import { Wallet } from "~/components/wallet-v2";
import { CreatePoolScriptDialog } from "../Pool/CreatePoolScript";
import { CreatePoolSoon } from "../Pool/CreatePoolSoon";
import { CreatePoolDialog } from "../Pool/CreatePoolDialog";
import { Button } from "~/components/ui/button";
import { IconArena } from "~/components/ui/icons";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip";
Expand Down Expand Up @@ -108,13 +109,14 @@ export const Header = () => {
<div className="flex items-center gap-4">
{!isMobile && (
<div className="flex items-center">
<CreatePoolSoon
<CreatePoolSoon />
{/* <CreatePoolDialog
trigger={
<Button disabled={false}>
<IconPlus size={16} /> Create Pool
</Button>
}
/>
/> */}
</div>
)}
<Wallet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import React from "react";
import ReactDOM from "react-dom";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import Confetti from "react-confetti";
import { useWindowSize } from "@uidotdev/usehooks";
import { IconPlus } from "@tabler/icons-react";
Expand All @@ -14,17 +11,16 @@ import { useTradeStore } from "~/store";
import { useIsMobile } from "~/hooks/use-is-mobile";

import {
CreatePoolSearch,
CreatePoolMint,
CreatePoolForm,
CreatePoolSuccess,
CreatePoolState,
FormValues,
formSchema,
} from "~/components/common/Pool/CreatePoolDialog/";
import { Dialog, DialogContent, DialogTrigger } from "~/components/ui/dialog";
import { Button } from "~/components/ui/button";
import { TokenData } from "~/types";
import type { TokenData } from "~/types";

import type { PoolData } from "./types";
import { CreatePoolLoading } from "./components/CreatePoolLoading";

type CreatePoolDialogProps = {
Expand All @@ -34,85 +30,19 @@ type CreatePoolDialogProps = {
export const CreatePoolDialog = ({ trigger }: CreatePoolDialogProps) => {
const [resetSearchResults, searchBanks] = useTradeStore((state) => [state.resetSearchResults, state.searchBanks]);
const [isOpen, setIsOpen] = React.useState(false);
const [isReadOnlyMode, setIsReadOnlyMode] = React.useState<boolean>(false);
const [createPoolState, setCreatePoolState] = React.useState<CreatePoolState>(CreatePoolState.SEARCH);
const [isSearchingDasApi, setIsSearchingDasApi] = React.useState(false);
const [createPoolState, setCreatePoolState] = React.useState<CreatePoolState>(CreatePoolState.MINT);
const [isSearchingToken, setIsSearchingToken] = React.useState(false);
const [isTokenFetchingError, setIsTokenFetchingError] = React.useState(false);
const [searchQuery, setSearchQuery] = React.useState("");
const [mintAddress, setMintAddress] = React.useState("");
const [previewImage, setPreviewImage] = React.useState<string | null>(null);
const [poolCreatedData, setPoolCreatedData] = React.useState<FormValues | null>(null);
const [poolData, setPoolData] = React.useState<PoolData | null>(null);
const [isSubmitting, setIsSubmitting] = React.useState(false);
const fileInputRef = React.useRef<HTMLInputElement>(null);

const { width, height } = useWindowSize();
const isMobile = useIsMobile();

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
mode: "onChange",
});

const onSubmit = React.useCallback(
(values: z.infer<typeof formSchema>) => {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values);
setIsSubmitting(true);
setPoolCreatedData(values);

setCreatePoolState(CreatePoolState.LOADING);
setIsSubmitting(false);
},
[setIsSubmitting, setPoolCreatedData, setCreatePoolState]
);

const handleFileClick = React.useCallback(() => {
fileInputRef.current?.click();
}, [fileInputRef]);

const handleFileDrop = React.useCallback(
(event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
const file = event.dataTransfer.files[0];
if (file.type.startsWith("image/")) {
const reader = new FileReader();
reader.onloadend = () => {
setPreviewImage(reader.result as string);
form.setValue("imageUpload", file);
};
reader.readAsDataURL(file);
}
event.dataTransfer.clearData();
}
},
[form, setPreviewImage]
);

const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
};

const handleFileChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
const file = event.target.files[0];
if (file.type.startsWith("image/")) {
const reader = new FileReader();
reader.onloadend = () => {
setPreviewImage(reader.result as string);
form.setValue("imageUpload", file);
};
reader.readAsDataURL(file);
}
}
},
[form, setPreviewImage]
);

const fetchTokenInfo = React.useCallback(async () => {
setIsSearchingDasApi(true);
setIsSearchingToken(true);

try {
const mint = new PublicKey(mintAddress);
Expand All @@ -123,50 +53,28 @@ export const CreatePoolDialog = ({ trigger }: CreatePoolDialogProps) => {
}

const tokenInfo = (await fetchTokenReq.json()) as TokenData;

if (!tokenInfo) {
throw new Error("Could not find token info");
}

form.setValue("mint", tokenInfo.address, { shouldValidate: true });
form.setValue("name", tokenInfo.name, { shouldValidate: true });
form.setValue("symbol", tokenInfo.symbol, { shouldValidate: true });
form.setValue("decimals", tokenInfo.decimals.toString(), { shouldValidate: true });
form.setValue("imageDownload", tokenInfo.imageUrl, { shouldValidate: true });
setPreviewImage(tokenInfo.imageUrl);
setIsReadOnlyMode(true);
setPoolData({
mint: new PublicKey(tokenInfo.address),
name: tokenInfo.name,
symbol: tokenInfo.symbol,
icon: tokenInfo.imageUrl,
decimals: tokenInfo.decimals,
quoteBank: "USDC",
});

setIsSearchingDasApi(false);
setIsSearchingToken(false);
setCreatePoolState(CreatePoolState.FORM);
} catch (e) {
console.error(e);
form.reset();
setIsReadOnlyMode(false);
setPoolData(null);
setIsTokenFetchingError(true);
setIsSearchingDasApi(false);
setIsSearchingToken(false);
}
}, [setCreatePoolState, form, mintAddress, setIsSearchingDasApi, setIsTokenFetchingError, setPreviewImage]);

const onCompletion = async (props: {
stableBankPk: PublicKey;
tokenBankPk: PublicKey;
groupPk: PublicKey;
lutAddress: PublicKey;
}) => {
const lutUpdateRes = await fetch(`/api/lut`, {
method: "POST",
body: JSON.stringify({
groupAddress: props.groupPk.toBase58(),
lutAddress: props.lutAddress.toBase58(),
}),
});

if (!lutUpdateRes.ok) {
console.error("Failed to update LUT");
}

setCreatePoolState(CreatePoolState.SUCCESS);
};
}, [mintAddress]);

React.useEffect(() => {
if (!searchQuery.length) {
Expand All @@ -177,19 +85,17 @@ export const CreatePoolDialog = ({ trigger }: CreatePoolDialogProps) => {
}, [searchBanks, resetSearchResults, searchQuery]);

const reset = React.useCallback(() => {
setPreviewImage("");
setIsSearchingDasApi(false);
setIsSearchingToken(false);
setIsTokenFetchingError(false);
setPoolCreatedData(null);
setPoolData(null);
setIsSubmitting(false);
form.reset();
}, [setIsTokenFetchingError, form]);
}, []);

React.useEffect(() => {
reset();
setSearchQuery("");
setMintAddress("");
setCreatePoolState(CreatePoolState.SEARCH);
setCreatePoolState(CreatePoolState.MINT);
}, [isOpen, reset, setSearchQuery, setMintAddress, setCreatePoolState]);

return (
Expand Down Expand Up @@ -222,58 +128,30 @@ export const CreatePoolDialog = ({ trigger }: CreatePoolDialogProps) => {
)}
</DialogTrigger>
<DialogContent className="w-full space-y-4 sm:max-w-4xl md:max-w-4xl z-[70]">
{createPoolState === CreatePoolState.SEARCH && (
<CreatePoolSearch
setIsOpen={setIsOpen}
setCreatePoolState={setCreatePoolState}
searchQuery={searchQuery}
debouncedSearchQuery={searchQuery}
setSearchQuery={setSearchQuery}
/>
)}
{createPoolState === CreatePoolState.MINT && (
<CreatePoolMint
mintAddress={mintAddress}
isTokenFetchingError={isTokenFetchingError}
isSearchingDasApi={isSearchingDasApi}
isSearchingToken={isSearchingToken}
setMintAddress={setMintAddress}
setIsTokenFetchingError={(value) => {
setIsReadOnlyMode(false);
setIsTokenFetchingError(value);
}}
setCreatePoolState={setCreatePoolState}
fetchTokenInfo={fetchTokenInfo}
reset={reset}
setIsOpen={setIsOpen}
/>
)}
{createPoolState === CreatePoolState.FORM && (
<CreatePoolForm
isReadOnlyMode={isReadOnlyMode}
isTokenFetchingError={isTokenFetchingError}
isSubmitting={isSubmitting}
previewImage={previewImage}
poolData={poolData}
setCreatePoolState={setCreatePoolState}
setPreviewImage={setPreviewImage}
form={form}
handleFileClick={handleFileClick}
handleDragOver={handleDragOver}
handleFileDrop={handleFileDrop}
handleFileChange={handleFileChange}
onSubmit={onSubmit}
reset={reset}
/>
)}

{createPoolState === CreatePoolState.LOADING && (
<CreatePoolLoading
poolCreatedData={poolCreatedData}
setIsOpen={setIsOpen}
setIsCompleted={(props) => onCompletion(props)}
/>
<CreatePoolLoading poolData={poolData} setPoolData={setPoolData} setCreatePoolState={setCreatePoolState} />
)}

{createPoolState === CreatePoolState.SUCCESS && (
<CreatePoolSuccess poolCreatedData={poolCreatedData} setIsOpen={setIsOpen} />
<CreatePoolSuccess poolData={poolData} setIsOpen={setIsOpen} />
)}
</DialogContent>
</Dialog>
Expand Down
Loading

0 comments on commit e18ef31

Please sign in to comment.