diff --git a/apps/marginfi-v2-trading/src/components/common/Header/Header.tsx b/apps/marginfi-v2-trading/src/components/common/Header/Header.tsx index ade8515142..541a93d2d1 100644 --- a/apps/marginfi-v2-trading/src/components/common/Header/Header.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Header/Header.tsx @@ -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"; @@ -108,13 +109,14 @@ export const Header = () => {
{!isMobile && (
- + {/* Create Pool } - /> + /> */}
)} { const [resetSearchResults, searchBanks] = useTradeStore((state) => [state.resetSearchResults, state.searchBanks]); const [isOpen, setIsOpen] = React.useState(false); - const [isReadOnlyMode, setIsReadOnlyMode] = React.useState(false); - const [createPoolState, setCreatePoolState] = React.useState(CreatePoolState.SEARCH); - const [isSearchingDasApi, setIsSearchingDasApi] = React.useState(false); + const [createPoolState, setCreatePoolState] = React.useState(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(null); - const [poolCreatedData, setPoolCreatedData] = React.useState(null); + const [poolData, setPoolData] = React.useState(null); const [isSubmitting, setIsSubmitting] = React.useState(false); - const fileInputRef = React.useRef(null); const { width, height } = useWindowSize(); const isMobile = useIsMobile(); - const form = useForm>({ - resolver: zodResolver(formSchema), - mode: "onChange", - }); - - const onSubmit = React.useCallback( - (values: z.infer) => { - // 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) => { - 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) => { - event.preventDefault(); - }; - - const handleFileChange = React.useCallback( - (event: React.ChangeEvent) => { - 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); @@ -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) { @@ -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 ( @@ -222,58 +128,30 @@ export const CreatePoolDialog = ({ trigger }: CreatePoolDialogProps) => { )} - {createPoolState === CreatePoolState.SEARCH && ( - - )} {createPoolState === CreatePoolState.MINT && ( { - setIsReadOnlyMode(false); - setIsTokenFetchingError(value); - }} - setCreatePoolState={setCreatePoolState} fetchTokenInfo={fetchTokenInfo} - reset={reset} + setIsOpen={setIsOpen} /> )} {createPoolState === CreatePoolState.FORM && ( )} {createPoolState === CreatePoolState.LOADING && ( - onCompletion(props)} - /> + )} {createPoolState === CreatePoolState.SUCCESS && ( - + )} diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolForm.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolForm.tsx index 271aaee5e1..ff6d274cc7 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolForm.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolForm.tsx @@ -1,54 +1,24 @@ import React from "react"; +import { IconChevronLeft } from "@tabler/icons-react"; -import Image from "next/image"; -import Link from "next/link"; +import { CreatePoolState } from "~/components/common/Pool/CreatePoolDialog"; -import { IconChevronLeft, IconUpload, IconInfoCircle, IconLoader2 } from "@tabler/icons-react"; -import { UseFormReturn } from "react-hook-form"; - -import { cn } from "@mrgnlabs/mrgn-utils"; - -import { Form, FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; +import { Loader } from "~/components/ui/loader"; +import { Label } from "~/components/ui/label"; -import { CreatePoolState, FormValues } from "~/components/common/Pool/CreatePoolDialog"; +import type { PoolData } from "../types"; type CreatePoolFormProps = { isTokenFetchingError: boolean; - isSubmitting: boolean; - isReadOnlyMode: boolean; - previewImage: string | null; - + poolData: PoolData | null; setCreatePoolState: React.Dispatch>; - setPreviewImage: React.Dispatch>; - - form: UseFormReturn; - handleFileClick: () => void; - handleDragOver: (e: React.DragEvent) => void; - handleFileDrop: (e: React.DragEvent) => void; - handleFileChange: (e: React.ChangeEvent) => void; - onSubmit: (data: FormValues) => void; reset: () => void; }; -export const CreatePoolForm = ({ - isTokenFetchingError, - isSubmitting, - isReadOnlyMode, - previewImage, - setCreatePoolState, - setPreviewImage, - form, - handleFileClick, - handleDragOver, - handleFileDrop, - handleFileChange, - onSubmit, - reset, -}: CreatePoolFormProps) => { - const fileInputRef = React.useRef(null); - +export const CreatePoolForm = ({ isTokenFetchingError, poolData, setCreatePoolState, reset }: CreatePoolFormProps) => { + if (!poolData) return null; return ( <>
-
- -
-
- {previewImage ? ( - Preview - ) : ( - <> - -

Image uploading not available

- {/*

Drag and drop your image here or click to select a file

*/} - - )} - -
-
- ( - -
- {formState.errors.mint && "*"}Mint address - - - -
-
- )} - /> - ( - -
- {formState.errors.name && "*"}Token name - - - -
-
- )} - /> - ( - -
- {formState.errors.symbol && "*"}Token symbol - - - -
-
- )} - /> - ( - -
- {formState.errors.decimals && "*"}Token decimals - - - -
-
- )} +
+
+
+ {poolData.icon && ( + // using img as source unknown and not whitelisted in next config + // eslint-disable-next-line @next/next/no-img-element + Preview - ( - -
- - {formState.errors.oracle && "*"}Oracle address - - {" "} - - more info - - - - - - -
-
- )} - /> - + )} + +
+
- - +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
); }; diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolLoading.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolLoading.tsx index 0ea79412b5..028d13adf3 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolLoading.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolLoading.tsx @@ -1,52 +1,57 @@ -import { Button } from "~/components/ui/button"; - -import { FormValues } from "~/components/common/Pool/CreatePoolDialog"; +import React from "react"; +import { IconLoader2, IconCheck, IconX } from "@tabler/icons-react"; +import { + Keypair, + PublicKey, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, + Signer, + Connection, +} from "@solana/web3.js"; +import BigNumber from "bignumber.js"; +import * as sb from "@switchboard-xyz/on-demand"; +import { CrossbarClient, decodeString } from "@switchboard-xyz/common"; +import * as anchor from "@coral-xyz/anchor"; -import { IconLoader2, IconCheck, IconConfetti, IconX } from "@tabler/icons-react"; import { BankConfigOpt, MarginfiClient, - MarginfiGroup, OperationalState, OracleSetup, RiskTier, getConfig, - makePriorityFeeIx, + makeBundleTipIx, } from "@mrgnlabs/marginfi-client-v2"; -import { cn } from "@mrgnlabs/mrgn-utils"; -import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; +import { cn, getBearerToken, getFeeAccount, createReferalTokenAccount } from "@mrgnlabs/mrgn-utils"; + +import { useUiStore, useTradeStore } from "~/store"; +import { Button } from "~/components/ui/button"; import { useConnection } from "~/hooks/use-connection"; -import { - Keypair, - Message, - PublicKey, - Transaction, - TransactionInstruction, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; -import BigNumber from "bignumber.js"; -import { createMarginfiGroup, createPermissionlessBank, createPoolLookupTable } from "~/utils"; -import { useUiStore } from "~/store"; -import React from "react"; +import { useWallet } from "~/components/wallet-v2"; +import { createPoolLookupTable } from "~/utils"; + +import { PoolData, CreatePoolState } from "../types"; +import { cp } from "fs"; +import { TokenData } from "~/types"; const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); const DEFAULT_USDC_BANK_CONFIG: BankConfigOpt = { - assetWeightInit: new BigNumber(1), - assetWeightMaint: new BigNumber(1), + assetWeightInit: new BigNumber(0.9), + assetWeightMaint: new BigNumber(0.95), liabilityWeightInit: new BigNumber(1.25), liabilityWeightMaint: new BigNumber(1.1), - depositLimit: new BigNumber(10000), //new BigNumber(200000000), - borrowLimit: new BigNumber(100), // new BigNumber(200000000), + depositLimit: new BigNumber(1000000).multipliedBy(1e6), // 1,000,000 USDC + borrowLimit: new BigNumber(250000).multipliedBy(1e6), // 250,000 USDC riskTier: RiskTier.Collateral, totalAssetValueInitLimit: new BigNumber(0), interestRateConfig: { // Curve Params - optimalUtilizationRate: new BigNumber(0.85), + optimalUtilizationRate: new BigNumber(0.8), plateauInterestRate: new BigNumber(0.1), maxInterestRate: new BigNumber(3), @@ -54,47 +59,51 @@ const DEFAULT_USDC_BANK_CONFIG: BankConfigOpt = { insuranceFeeFixedApr: new BigNumber(0), insuranceIrFee: new BigNumber(0), protocolFixedFeeApr: new BigNumber(0.01), - protocolIrFee: new BigNumber(0.05), + protocolIrFee: new BigNumber(0.3), }, operationalState: OperationalState.Operational, oracle: { - setup: OracleSetup.PythLegacy, - keys: [new PublicKey("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD")], + setup: OracleSetup.PythPushOracle, + keys: [ + new PublicKey("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD"), // feed id + new PublicKey("Dpw1EAVrSB1ibxiDQyTAW6Zip3J4Btk2x4SgApQCeFbX"), // oracle key + ], }, oracleMaxAge: 300, permissionlessBadDebtSettlement: null, }; const DEFAULT_TOKEN_BANK_CONFIG: BankConfigOpt = { - assetWeightInit: new BigNumber(0.5), - assetWeightMaint: new BigNumber(0.64), + assetWeightInit: new BigNumber(0.65), + assetWeightMaint: new BigNumber(0.8), liabilityWeightInit: new BigNumber(1.3), liabilityWeightMaint: new BigNumber(1.2), - depositLimit: new BigNumber(10000), - borrowLimit: new BigNumber(100), + // this will be overwritten based on oracle price + depositLimit: new BigNumber(0), // 1,000,000 / oracle price + borrowLimit: new BigNumber(0), // 250,000 / oracle price riskTier: RiskTier.Collateral, totalAssetValueInitLimit: new BigNumber(0), interestRateConfig: { // Curve Params optimalUtilizationRate: new BigNumber(0.8), - plateauInterestRate: new BigNumber(0.2), - maxInterestRate: new BigNumber(4), + plateauInterestRate: new BigNumber(0.1), + maxInterestRate: new BigNumber(3), // Fees insuranceFeeFixedApr: new BigNumber(0), insuranceIrFee: new BigNumber(0), protocolFixedFeeApr: new BigNumber(0.01), - protocolIrFee: new BigNumber(0.05), + protocolIrFee: new BigNumber(0.3), }, operationalState: OperationalState.Operational, oracle: { - setup: OracleSetup.PythLegacy, - keys: [], + setup: OracleSetup.SwitchboardV2, + keys: [new PublicKey("8pMJw6N3e1FDexoTMx1T1ComSB91tmQydFrmhmmnXZuV")], }, oracleMaxAge: null, permissionlessBadDebtSettlement: null, @@ -112,19 +121,9 @@ const iconMap: IconMap = { }; interface CreatePoolLoadingProps { - setIsOpen: React.Dispatch>; - setIsCompleted: ({ - tokenBankPk, - stableBankPk, - groupPk, - lutAddress, - }: { - tokenBankPk: PublicKey; - stableBankPk: PublicKey; - groupPk: PublicKey; - lutAddress: PublicKey; - }) => void; - poolCreatedData: FormValues | null; + poolData: PoolData | null; + setPoolData: React.Dispatch>; + setCreatePoolState: React.Dispatch>; } type PoolCreationState = { @@ -138,35 +137,31 @@ type PoolCreationState = { marginfiGroupPk?: PublicKey; tokenBankPk?: PublicKey; stableBankPk?: PublicKey; + oraclePk?: PublicKey; }; -export const CreatePoolLoading = ({ poolCreatedData, setIsOpen, setIsCompleted }: CreatePoolLoadingProps) => { +export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: CreatePoolLoadingProps) => { const { wallet } = useWallet(); const { connection } = useConnection(); + const [fetchTradeState] = useTradeStore((state) => [state.fetchTradeState]); const [priorityFee] = useUiStore((state) => [state.priorityFee]); const [activeStep, setActiveStep] = React.useState(0); const [status, setStatus] = React.useState("default"); const steps = React.useMemo( () => [ - { label: "Step 1", description: "Creating new marginfi group" }, - { label: "Step 2", description: "Configuring USDC bank" }, - { label: "Step 3", description: `Configuring ${poolCreatedData?.symbol} bank` }, + { label: "Step 1", description: "Setting up a switchboard oracle" }, + { label: "Step 2", description: "Generating transactions" }, + { label: "Step 3", description: "Executing transactions" }, + { label: "Step 4", description: "Finalizing pool" }, ], - [poolCreatedData] + [] ); const [poolCreation, setPoolCreation] = React.useState(); const initialized = React.useRef(false); - React.useEffect(() => { - if (!initialized.current) { - initialized.current = true; - createTransaction(); - } - }, []); - const initializeClient = React.useCallback(async () => { const config = getConfig(); @@ -176,86 +171,154 @@ export const CreatePoolLoading = ({ poolCreatedData, setIsOpen, setIsCompleted } return client; }, [connection, wallet]); - const createGroup = React.useCallback( - async (marginfiClient: MarginfiClient, lutIxs: TransactionInstruction[], seed?: Keypair) => { + const createOracleIx = React.useCallback( + async (tokenMint: PublicKey, symbol: string, client: MarginfiClient) => { + // get switchboard onDemand program id + try { - const marginfiGroup = await createMarginfiGroup({ marginfiClient, additionalIxs: lutIxs, seed }); + const programId = await sb.getProgramId(connection); + const provider = new anchor.AnchorProvider(connection, wallet, {}); + const crossbarClient = new CrossbarClient("https://crossbar.switchboard.xyz", /* verbose= */ true); + const idl = await anchor.Program.fetchIdl(programId, provider); - if (!marginfiGroup) throw new Error(); + if (!idl) return; - return marginfiGroup; - } catch (error) { - setStatus("error"); - } - }, - [setStatus] - ); + const onDemandProgram = new anchor.Program(idl, provider); - const createBank = React.useCallback( - async ( - marginfiClient: MarginfiClient, - bankConfig: BankConfigOpt, - mint: PublicKey, - group: PublicKey, - seed?: Keypair - ) => { - try { - setStatus("loading"); + const [pullFeed, feedSeed] = sb.PullFeed.generate(onDemandProgram); - const sig = await createPermissionlessBank({ - marginfiClient, - mint, - bankConfig, - group, - admin: wallet.publicKey, - seed, - priorityFee, + const feedPubkey = feedSeed.publicKey; + + const valueTask = sb.OracleJob.Task.create({ + valueTask: { + big: "1", + }, + }); + + const divideTask = sb.OracleJob.Task.create({ + divideTask: { + job: { + tasks: [ + { + jupiterSwapTask: { + inTokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC + outTokenAddress: tokenMint.toBase58(), // TOKEN + baseAmountString: "1", + }, + }, + ], + }, + }, }); - if (!sig) throw new Error(); + const multiplyTask = sb.OracleJob.Task.create({ + multiplyTask: { + job: { + tasks: [ + { + oracleTask: { + pythAddress: "Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD", // PYTH USDC oracle + pythAllowedConfidenceInterval: 5, + }, + }, + ], + }, + }, + }); + + const queueAccount = await sb.getDefaultQueue(connection.rpcEndpoint); + const queue = queueAccount.pubkey; + + const oracleJob = sb.OracleJob.create({ + tasks: [valueTask, divideTask, multiplyTask], + }); - return sig; + const feedHash = (await crossbarClient.store(queue.toString(), [oracleJob])).feedHash; + + const feedHashBuffer = decodeString(feedHash); + + if (!feedHashBuffer) return; + + const conf = { + name: `${symbol}/USD`, // the feed name (max 32 bytes) + queue: new PublicKey(queue), // the queue of oracles to bind to + maxVariance: 10.0, // allow 1% variance between submissions and jobs + minResponses: 1, // minimum number of responses of jobs to allow + numSignatures: 1, // number of signatures to fetch per update + minSampleSize: 1, // minimum number of responses to sample for a result + maxStaleness: 250, // maximum stale slots of responses to sample + feedHash: feedHashBuffer, + }; + + const pullFeedIx = await pullFeed.initIx(conf); + + if (!feedPubkey) throw new Error(); + console.log("feedPubkey", feedPubkey.toBase58()); + return { feedPubkey, pullFeedIx, feedSeed }; } catch (error) { setStatus("error"); + console.log("error creating oracle", error); } }, - [wallet.publicKey, priorityFee, setStatus] + [connection, wallet] ); - const createSeeds = React.useCallback(() => { - const tokenBankSeed = new Keypair(); - const stableBankSeed = new Keypair(); - const marginfiGroupSeed = new Keypair(); + const savePermissionlessPool = async (poolObject: { group: string; asset: string; quote: string; lut: string }) => { + try { + const token = await getBearerToken(); + + const response = await fetch("/api/pool/create", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(poolObject), + }); - return { tokenBankSeed, stableBankSeed, marginfiGroupSeed }; - }, []); + if (response.ok) { + const data = await response.json(); + console.log("Pool added:", data); + return data; + } else { + console.error("Failed to add pool"); + } + } catch (error) { + console.error("Error:", error); + } + }; + + const createPermissionlessBankBundle = React.useCallback(async () => { + try { + setActiveStep(0); - const createTransaction = React.useCallback(async () => { - if (!poolCreatedData) return; - setStatus("loading"); + if (!poolData) return; - let client = poolCreation?.marginfiClient; - let seeds = poolCreation?.seeds; - let group = poolCreation?.marginfiGroupPk; - let lutAddress = poolCreation?.lutAddress; + setStatus("loading"); - // create client - if (!client) client = await initializeClient(); + // create client + const client = await initializeClient(); - //create seeds - if (!seeds) seeds = createSeeds(); - setPoolCreation((state) => ({ ...state, seeds })); + // create seeds + const seeds = createSeeds(); - // create group & LUT - if (!group || !lutAddress) { - setActiveStep(0); - const oracleKeys = [new PublicKey(poolCreatedData.oracle), ...(DEFAULT_USDC_BANK_CONFIG.oracle?.keys ?? [])]; + // create bundle tip ix + const bundleTipIx = makeBundleTipIx(wallet.publicKey); + + // create oracle ix + const oracleCreation = await createOracleIx(poolData.mint, poolData.symbol, client); + + if (!oracleCreation) throw new Error("Oracle creation failed"); + + setActiveStep(1); + + // create group ix + const groupIxWrapped = await client.makeCreateMarginfiGroupIx(seeds.marginfiGroupSeed.publicKey); + + // create lut ix + const oracleKeys = [oracleCreation.feedPubkey, ...(DEFAULT_USDC_BANK_CONFIG.oracle?.keys ?? [])]; const bankKeys = [seeds.stableBankSeed.publicKey, seeds.tokenBankSeed.publicKey]; - const { - lutAddress: newLutAddress, - createLutIx, - extendLutIx, - } = await createPoolLookupTable({ + const { lutAddress, createLutIx, extendLutIx } = await createPoolLookupTable({ client, oracleKeys, bankKeys, @@ -263,62 +326,248 @@ export const CreatePoolLoading = ({ poolCreatedData, setIsOpen, setIsCompleted } walletKey: wallet.publicKey, }); - lutAddress = newLutAddress; - group = await createGroup(client, [createLutIx, extendLutIx], seeds.marginfiGroupSeed); - if (!group || !lutAddress) return; - } + // create bank ix wrapper (stable) + const stableBankIxWrapper = await client.group.makePoolAddBankIx( + client.program, + seeds.stableBankSeed.publicKey, + USDC_MINT, + DEFAULT_USDC_BANK_CONFIG, + { + admin: wallet.publicKey, + groupAddress: seeds.marginfiGroupSeed.publicKey, + } + ); - setPoolCreation((state) => ({ ...state, marginfiGroupPk: group, lutAddress })); + let tokenBankConfig = DEFAULT_TOKEN_BANK_CONFIG; - if (!poolCreation?.stableBankPk) { - setActiveStep(1); - const sig = await createBank(client, DEFAULT_USDC_BANK_CONFIG, USDC_MINT, group, seeds.stableBankSeed); - if (!sig) return; - setPoolCreation((state) => ({ ...state, stableBankPk: seeds.stableBankSeed.publicKey })); - } + const response = await fetch(`/api/birdeye/token?address=${poolData.mint.toBase58()}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); - if (!poolCreation?.tokenBankPk) { - setActiveStep(2); - const tokenMint = new PublicKey(poolCreatedData.mint); - // token bank - let tokenBankConfig = DEFAULT_TOKEN_BANK_CONFIG; + const responseBody = await response.json(); + + console.log("responseBody", responseBody); + + if (!responseBody) { + throw new Error("Failed to fetch token details"); + } - tokenBankConfig.borrowLimit = new BigNumber(100); // todo: update this according to price - tokenBankConfig.depositLimit = new BigNumber(10000); // todo: update this according to price + const tokenDetails = responseBody as TokenData; + + tokenBankConfig.borrowLimit = new BigNumber(Math.floor(100_000 / tokenDetails.price)).multipliedBy( + Math.pow(10, poolData.decimals) + ); + tokenBankConfig.depositLimit = new BigNumber(Math.floor(10_000 / tokenDetails.price)).multipliedBy( + Math.pow(10, poolData.decimals) + ); tokenBankConfig.oracle = { - setup: OracleSetup.PythLegacy, - keys: [new PublicKey(poolCreatedData.oracle)], + setup: OracleSetup.SwitchboardPull, + keys: [oracleCreation.feedPubkey], }; - const sig = await createBank(client, tokenBankConfig, tokenMint, group, seeds.tokenBankSeed); - if (!sig) return; - setPoolCreation((state) => ({ ...state, tokenBankPk: seeds.tokenBankSeed.publicKey })); - } + // create bank ix wrapper (token) + const tokenBankIxWrapper = await client.group.makePoolAddBankIx( + client.program, + seeds.tokenBankSeed.publicKey, + poolData.mint, + tokenBankConfig, + { + admin: wallet.publicKey, + groupAddress: seeds.marginfiGroupSeed.publicKey, + } + ); - setIsCompleted({ - groupPk: seeds.marginfiGroupSeed.publicKey, - stableBankPk: seeds.stableBankSeed.publicKey, - tokenBankPk: seeds.tokenBankSeed.publicKey, - lutAddress, - }); + // transactions + const transactions: VersionedTransaction[] = []; + const { + value: { blockhash }, + } = await connection.getLatestBlockhashAndContext(); + + // bundle tip & create oracle transaction + transactions.push( + await createTransaction( + [bundleTipIx, oracleCreation.pullFeedIx], + wallet.publicKey, + connection, + [oracleCreation.feedSeed], + blockhash + ) + ); + + const feeAccount = getFeeAccount(poolData.mint); + + if (!feeAccount) { + const feeAccountCreationLegacyTx = await createReferalTokenAccount(connection, wallet.publicKey, poolData.mint); + const feeAccountCreationMessage = new TransactionMessage({ + instructions: feeAccountCreationLegacyTx.instructions, + payerKey: wallet.publicKey, + recentBlockhash: blockhash, + }).compileToV0Message([]); + const feeAccountCreationTx = new VersionedTransaction(feeAccountCreationMessage); + transactions.push(feeAccountCreationTx); + } + + // create lut & create group transaction + transactions.push( + await createTransaction( + [createLutIx, extendLutIx, ...groupIxWrapped.instructions], + wallet.publicKey, + connection, + [seeds.marginfiGroupSeed], + blockhash + ) + ); + + // stable bank transaction + transactions.push( + await createTransaction( + [...stableBankIxWrapper.instructions], + wallet.publicKey, + connection, + [seeds.stableBankSeed, ...stableBankIxWrapper.keys], + blockhash + ) + ); + + // token bank transaction + transactions.push( + await createTransaction( + [...tokenBankIxWrapper.instructions], + wallet.publicKey, + connection, + [seeds.tokenBankSeed, ...tokenBankIxWrapper.keys], + blockhash + ) + ); + + setActiveStep(2); + + // transaction execution + const sigs = await client.processTransactions(transactions); + + if (!sigs) throw new Error("Transaction execution failed"); + + setActiveStep(3); + + try { + const response = await savePermissionlessPool({ + group: seeds.marginfiGroupSeed.publicKey.toBase58(), + asset: seeds.tokenBankSeed.publicKey.toBase58(), + quote: seeds.stableBankSeed.publicKey.toBase58(), + lut: lutAddress.toBase58(), + }); + console.log("Pool saved:", response); + } catch (error) { + console.error("Failed to save pool"); + console.error(error); + throw error; + } + + // update LUT GCP + // const cacheData = { + // groupAddress: seeds.marginfiGroupSeed.publicKey.toBase58(), + // lutAddress: lutAddress.toBase58(), + // usdcBankAddress: seeds.stableBankSeed.publicKey.toBase58(), + // tokenBankAddress: seeds.tokenBankSeed.publicKey.toBase58(), + // tokenName: poolData.name, + // tokenMint: poolData.mint, + // tokenSymbol: poolData.symbol, + // tokenImage: poolData.icon, + // tokenDecimals: poolData.decimals, + // }; + // console.log("cache data", cacheData); + // const lutUpdateRes = await fetch(`/api/pool/create`, { + // method: "POST", + // body: JSON.stringify(cacheData), + // headers: { + // "Content-Type": "application/json", + // }, + // }); + + // if (!lutUpdateRes.ok) { + // console.error("Failed to update GCP caches"); + // return; + // } + + setPoolData((state) => { + if (!state) return null; + return { ...state, group: seeds.marginfiGroupSeed.publicKey }; + }); + setCreatePoolState(CreatePoolState.SUCCESS); + fetchTradeState({ + connection, + wallet, + refresh: true, + }); + } catch (error) { + setStatus("error"); + console.error("Failed to create permissionless pool"); + console.error(error); + } }, [ - createBank, - createGroup, + connection, + createOracleIx, + fetchTradeState, initializeClient, - poolCreatedData, - poolCreation?.marginfiClient, - poolCreation?.marginfiGroupPk, - poolCreation?.stableBankPk, - poolCreation?.tokenBankPk, - setIsCompleted, - setStatus, + poolData, + setCreatePoolState, + setPoolData, + wallet, ]); + const createTransaction = async ( + ixs: TransactionInstruction[], + payerKey: PublicKey, + connection: Connection, + signers: Signer[], + blockhashArg?: string + ) => { + let blockhash = blockhashArg; + if (!blockhash) { + blockhash = (await connection.getLatestBlockhashAndContext()).value.blockhash; + } + + console.log("ixs", ixs); + console.log("payerKey", payerKey); + + const message = new TransactionMessage({ + instructions: ixs, + payerKey, + recentBlockhash: blockhash, + }); + + console.log("message", message); + + const transaction = new VersionedTransaction(message.compileToV0Message([])); + transaction.sign(signers); + + return transaction; + }; + + const createSeeds = () => { + const tokenBankSeed = new Keypair(); + const stableBankSeed = new Keypair(); + const marginfiGroupSeed = new Keypair(); + + return { tokenBankSeed, stableBankSeed, marginfiGroupSeed }; + }; + + React.useEffect(() => { + if (!initialized.current) { + initialized.current = true; + createPermissionlessBankBundle(); + // createTransaction(); + } + }, []); + return ( <>
-

Creating a new pool

-

Executing transactions to setup token banks.

+

Creating new pool

+

Executing transactions to configure new group and banks.

{steps.map((step, idx) => { @@ -339,7 +588,7 @@ export const CreatePoolLoading = ({ poolCreatedData, setIsOpen, setIsCompleted }
{step.description}
{showRetry && ( - )} diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolMint.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolMint.tsx index 6cf52312c2..c2c9191d80 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolMint.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolMint.tsx @@ -1,49 +1,81 @@ -import { IconChevronLeft, IconArrowRight, IconLoader2 } from "@tabler/icons-react"; +import React from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { IconArrowRight, IconLoader2 } from "@tabler/icons-react"; +import { PublicKey } from "@solana/web3.js"; -import { cn } from "@mrgnlabs/mrgn-utils"; +import { cn, getTokenImageURL } from "@mrgnlabs/mrgn-utils"; -import { CreatePoolState } from "~/components/common/Pool/CreatePoolDialog"; +import { useTradeStore } from "~/store"; +import { GroupData } from "~/store/tradeStore"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; type CreatePoolMintProps = { mintAddress: string; - isSearchingDasApi: boolean; - isTokenFetchingError: boolean; - + isSearchingToken: boolean; setMintAddress: React.Dispatch>; - setIsTokenFetchingError: React.Dispatch>; - setCreatePoolState: React.Dispatch>; - + setIsOpen: React.Dispatch>; fetchTokenInfo: () => void; - reset: () => void; }; export const CreatePoolMint = ({ mintAddress, - isSearchingDasApi, - isTokenFetchingError, + isSearchingToken, setMintAddress, - setIsTokenFetchingError, - setCreatePoolState, - reset, + setIsOpen, fetchTokenInfo, }: CreatePoolMintProps) => { + const [groupMap] = useTradeStore((state) => [state.groupMap]); + const [error, setError] = React.useState(null); + const [poolExists, setPoolExists] = React.useState(null); + + const verifyPublickey = (key: string, allowPDA: boolean = false) => { + try { + const _ = new PublicKey(key).toBytes(); + } catch (error) { + return false; + } + + if (!allowPDA && !PublicKey.isOnCurve(new PublicKey(key).toBytes())) { + return false; + } + + return true; + }; + + const onSubmit = React.useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + + if (!mintAddress.length) return; + + if (!verifyPublickey(mintAddress)) { + setError("Invalid mint address, please try again."); + return; + } + + const groups = [...groupMap.values()]; + + // check if mint address is in groupMap it will be found at entry.pool.token.mint + const group = groups.find((group: GroupData) => { + return group.pool.token.info.rawBank.mint.equals(new PublicKey(mintAddress)); + }); + + if (group) { + setPoolExists(group); + return; + } + + fetchTokenInfo(); + }, + [mintAddress, groupMap, fetchTokenInfo] + ); + return ( <> - -
+

Token mint address

Enter the mint address of the token you'd like to create a pool for. @@ -52,53 +84,68 @@ export const CreatePoolMint = ({

{ - e.preventDefault(); - fetchTokenInfo(); - }} + onSubmit={onSubmit} > setMintAddress(e.target.value)} + onChange={(e) => { + setMintAddress(e.target.value); + setError(null); + }} /> - {isTokenFetchingError ? ( -
-

Could not find token details, please enter manually.

- + + + + +
+
) : (
- -
)} diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolSearch.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolSearch.tsx deleted file mode 100644 index 0a9ba5239d..0000000000 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolSearch.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react"; - -import { IconPlus } from "@tabler/icons-react"; - -import { useTradeStore } from "~/store"; - -import { PoolSearch } from "~/components/common/Pool"; -import { CreatePoolState } from "~/components/common/Pool/CreatePoolDialog"; - -import { Button } from "~/components/ui/button"; - -type CreatePoolSearchProps = { - setIsOpen: React.Dispatch>; - setCreatePoolState: React.Dispatch>; - searchQuery: string; - setSearchQuery: React.Dispatch>; - debouncedSearchQuery: string; -}; - -export const CreatePoolSearch = ({ setIsOpen, setCreatePoolState }: CreatePoolSearchProps) => { - const [resetSearchResults] = useTradeStore((state) => [state.resetSearchResults]); - return ( - <> -
-

Search existing pools

-

Search for an existing pool before creating a new one.

-
-
- { - resetSearchResults(); - setIsOpen(false); - }} - additionalContent={ - - } - /> -
- - ); -}; diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolSuccess.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolSuccess.tsx index a203f565a5..5356442f84 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolSuccess.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/CreatePoolSuccess.tsx @@ -1,20 +1,19 @@ import Image from "next/image"; import Link from "next/link"; -import { IconConfetti } from "@tabler/icons-react"; - import { shortenAddress } from "@mrgnlabs/mrgn-common"; +import { IconConfetti, IconExternalLink } from "@tabler/icons-react"; import { Button } from "~/components/ui/button"; -import { FormValues } from "~/components/common/Pool/CreatePoolDialog"; +import type { PoolData } from "../types"; type CreatePoolSuccessProps = { setIsOpen: React.Dispatch>; - poolCreatedData: FormValues | null; + poolData: PoolData | null; }; -export const CreatePoolSuccess = ({ poolCreatedData, setIsOpen }: CreatePoolSuccessProps) => { +export const CreatePoolSuccess = ({ poolData, setIsOpen }: CreatePoolSuccessProps) => { return (
@@ -25,25 +24,28 @@ export const CreatePoolSuccess = ({ poolCreatedData, setIsOpen }: CreatePoolSucc Your pool has been created. It will be verified before it shows on mrgntrade.

- {poolCreatedData && ( + {poolData && (
{`${poolCreatedData.symbol}

- {poolCreatedData.name} ({poolCreatedData.symbol}) + {poolData.name} ({poolData.symbol})

- {shortenAddress(poolCreatedData.mint)} + + {shortenAddress(poolData.group || poolData.mint)} + +
)} diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/index.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/index.tsx index 54b768bc89..6da4724922 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/index.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/index.tsx @@ -1,5 +1,4 @@ export * from "./CreatePoolDialog"; -export * from "./components/CreatePoolSearch"; export * from "./components/CreatePoolMint"; export * from "./components/CreatePoolForm"; export * from "./components/CreatePoolSuccess"; diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/types.ts b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/types.ts index eee3c3026d..7d4f80e36b 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/types.ts +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/types.ts @@ -1,8 +1,6 @@ import { PublicKey } from "@solana/web3.js"; -import { z } from "zod"; export enum CreatePoolState { - SEARCH = "initial", MINT = "mint", FORM = "form", LOADING = "loading", @@ -10,45 +8,12 @@ export enum CreatePoolState { ERROR = "error", } -export function verifyPublickey(key: string, allowPDA: boolean = false) { - try { - const _ = new PublicKey(key).toBytes(); - } catch (error) { - return false; - } - - if (!allowPDA && !PublicKey.isOnCurve(new PublicKey(key).toBytes())) { - return false; - } - - return true; -} - -function superRefinePublickey(val: string, ctx: z.RefinementCtx) {} - -export const formSchema = z - .object({ - mint: z - .string({ - required_error: "You need to enter a valid token mint.", - }) - .superRefine(superRefinePublickey), - name: z.string(), - symbol: z.string(), - decimals: z.string().refine((value) => !isNaN(Number(value)), { - message: "Decimals must be a number", - }), - oracle: z - .string({ - required_error: "You need to enter a valid oracle public key.", - }) - .superRefine(superRefinePublickey), - imageUpload: typeof window !== "undefined" ? z.instanceof(File).optional() : z.any().optional(), - imageDownload: z.string().optional(), - }) - .refine((data) => data.imageUpload || data.imageDownload, { - message: "Token image must be provided", - path: ["imageUpload", "imageDownload"], - }); - -export type FormValues = z.infer; +export type PoolData = { + mint: PublicKey; + name: string; + symbol: string; + icon: string; + decimals: number; + quoteBank: "USDC" | "LST"; + group?: PublicKey; +}; diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/index.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/index.tsx index 0c152d618b..8a64b44b8a 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/index.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/index.tsx @@ -3,3 +3,4 @@ export * from "./CreatePoolDialog"; export * from "./PoolCard"; export * from "./PoolSearch/PoolSearch"; export * from "./PoolListItem"; +export * from "./CreatePoolSoon"; diff --git a/apps/marginfi-v2-trading/src/components/mobile/MobileNavbar/MobileNavbar.tsx b/apps/marginfi-v2-trading/src/components/mobile/MobileNavbar/MobileNavbar.tsx index a415ad0275..7072c8be09 100644 --- a/apps/marginfi-v2-trading/src/components/mobile/MobileNavbar/MobileNavbar.tsx +++ b/apps/marginfi-v2-trading/src/components/mobile/MobileNavbar/MobileNavbar.tsx @@ -8,7 +8,7 @@ import { cn } from "@mrgnlabs/mrgn-utils"; import { useUiStore } from "~/store"; import { useOs } from "~/hooks/use-os"; -import { CreatePoolSoon } from "~/components/common/Pool/CreatePoolSoon"; +import { CreatePoolDialog, CreatePoolSoon } from "~/components/common/Pool"; export interface NavLinkInfo { href: string; @@ -19,7 +19,8 @@ export interface NavLinkInfo { } const CreatePoolTrigger = (children: ReactNode) => { - return ; + return ; + // ; }; export const mobileLinks: NavLinkInfo[] = [ diff --git a/apps/marginfi-v2-trading/src/config/trade.ts b/apps/marginfi-v2-trading/src/config/trade.ts index 5e180b8bad..7c0ec3a663 100644 --- a/apps/marginfi-v2-trading/src/config/trade.ts +++ b/apps/marginfi-v2-trading/src/config/trade.ts @@ -1,8 +1,12 @@ +const invalidateCache = process.env.NEXT_PUBLIC_INVALIDATE_GCP_CACHE === "true" ? `?t=${Date.now()}` : ""; + export const TRADE_GROUPS_MAP = - process.env.NEXT_PUBLIC_GROUPS_MAP || "https://storage.googleapis.com/mrgn-public/mfi-trade-groups.json"; + (process.env.NEXT_PUBLIC_GROUPS_MAP || "https://storage.googleapis.com/mrgn-public/mfi-trade-groups.json") + + invalidateCache; export const TOKEN_METADATA_MAP = - process.env.NEXT_PUBLIC_TOKENS_MAP || - "https://storage.googleapis.com/mrgn-public/mfi-trade-token-metadata-cache.json"; + (process.env.NEXT_PUBLIC_TOKENS_MAP || + "https://storage.googleapis.com/mrgn-public/mfi-trade-token-metadata-cache.json") + invalidateCache; export const BANK_METADATA_MAP = - process.env.NEXT_PUBLIC_BANKS_MAP || "https://storage.googleapis.com/mrgn-public/mfi-trade-bank-metadata-cache.json"; + (process.env.NEXT_PUBLIC_BANKS_MAP || + "https://storage.googleapis.com/mrgn-public/mfi-trade-bank-metadata-cache.json") + invalidateCache; export const POOLS_PER_PAGE = 12; diff --git a/apps/marginfi-v2-trading/src/pages/api/birdeye/token.ts b/apps/marginfi-v2-trading/src/pages/api/birdeye/token.ts index 99c0fd2651..813aa0337a 100644 --- a/apps/marginfi-v2-trading/src/pages/api/birdeye/token.ts +++ b/apps/marginfi-v2-trading/src/pages/api/birdeye/token.ts @@ -2,6 +2,10 @@ import { NextApiRequest, NextApiResponse } from "next"; import type { TokenData } from "~/types"; +function cdnImageUrl(url: string) { + return `https://img.fotofolio.xyz/?url=${encodeURIComponent(url)}`; +} + export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { address } = req.query; if (!address) { @@ -33,7 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) address: data.address, name: data.name, symbol: data.symbol, - imageUrl: data.logoURI, + imageUrl: cdnImageUrl(data.logoURI), decimals: data.decimals, price: data.price, priceChange24h: data.priceChange24hPercent, diff --git a/apps/marginfi-v2-trading/src/pages/api/pool/auth.ts b/apps/marginfi-v2-trading/src/pages/api/pool/auth.ts new file mode 100644 index 0000000000..3b67779e7a --- /dev/null +++ b/apps/marginfi-v2-trading/src/pages/api/pool/auth.ts @@ -0,0 +1,31 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +const AUTH_URL = "http://202.8.10.73:3000/auth/jwt"; +const USERNAME = process.env.API_AUTH_USERNAME; +const PASSWORD = process.env.API_AUTH_PASSWORD; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const encodedCredentials = Buffer.from(`${USERNAME}:${PASSWORD}`).toString("base64"); + + const response = await fetch(AUTH_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Basic ${encodedCredentials}`, // Basic Auth Header + }, + body: JSON.stringify({}), + }); + + if (!response.ok) { + return res.status(response.status).json({ error: "Failed to authenticate" }); + } + + const data = await response.json(); + const token = data.jwt; + + res.status(200).json({ token }); + } catch (error) { + res.status(500).json({ error: "Internal Server Error" }); + } +} diff --git a/apps/marginfi-v2-trading/src/pages/api/pool/create.ts b/apps/marginfi-v2-trading/src/pages/api/pool/create.ts new file mode 100644 index 0000000000..99c6a72088 --- /dev/null +++ b/apps/marginfi-v2-trading/src/pages/api/pool/create.ts @@ -0,0 +1,29 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +const ARENA_URL = "http://202.8.10.73:3000/arena/add_pool"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === "POST") { + try { + const response = await fetch(ARENA_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `${req.headers.authorization || ""}`, + }, + body: JSON.stringify(req.body), + }); + + if (!response.ok) { + return res.status(response.status).json({ error: "Failed to add pool" }); + } + + const data = await response.json(); + res.status(200).json(data); + } catch (error) { + res.status(500).json({ error: "Internal Server Error" }); + } + } else { + res.status(405).json({ error: "Method Not Allowed" }); + } +} diff --git a/apps/marginfi-v2-trading/src/pages/api/pool.ts b/apps/marginfi-v2-trading/src/pages/api/pool/request.ts similarity index 100% rename from apps/marginfi-v2-trading/src/pages/api/pool.ts rename to apps/marginfi-v2-trading/src/pages/api/pool/request.ts diff --git a/apps/marginfi-v2-trading/src/store/tradeStore.ts b/apps/marginfi-v2-trading/src/store/tradeStore.ts index 983acb3b96..c57918959d 100644 --- a/apps/marginfi-v2-trading/src/store/tradeStore.ts +++ b/apps/marginfi-v2-trading/src/store/tradeStore.ts @@ -152,6 +152,7 @@ type TradeStoreState = { const { programId } = getConfig(); const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); +const LST_MINT = new PublicKey("LSTxxxnJzKDFSLr4dUkPcmCf5VyryEqzPLz5j4bpxFp"); let fuse: Fuse | null = null; @@ -604,8 +605,12 @@ async function getGroupData({ ); // change this logic when adding more collateral banks - const tokenBanks = extendedBankInfos.filter((bank) => !bank.info.rawBank.mint.equals(USDC_MINT)); - const collateralBanks = extendedBankInfos.filter((bank) => bank.info.rawBank.mint.equals(USDC_MINT)); + const tokenBanks = extendedBankInfos.filter( + (bank) => !bank.info.rawBank.mint.equals(USDC_MINT) && !bank.info.rawBank.mint.equals(LST_MINT) + ); + const collateralBanks = extendedBankInfos.filter( + (bank) => bank.info.rawBank.mint.equals(USDC_MINT) || bank.info.rawBank.mint.equals(LST_MINT) + ); if (tokenBanks.length > 1) console.error("Inconsitency in token banks!"); diff --git a/apps/marginfi-v2-ui/src/utils/mintUtils.ts b/apps/marginfi-v2-ui/src/utils/mintUtils.ts index 61e16dd8d3..65b11d9fad 100644 --- a/apps/marginfi-v2-ui/src/utils/mintUtils.ts +++ b/apps/marginfi-v2-ui/src/utils/mintUtils.ts @@ -86,7 +86,6 @@ export async function fetchMintOverview(mint: string): Promise { }); const responseBody = await response.json(); - console.log({ responseBody }); if (responseBody.success) { const volume = responseBody.data.v24h; const volumeUsd = responseBody.data.v24hUSD; diff --git a/packages/marginfi-client-v2/src/client.ts b/packages/marginfi-client-v2/src/client.ts index 25ad3793c6..47a422d601 100644 --- a/packages/marginfi-client-v2/src/client.ts +++ b/packages/marginfi-client-v2/src/client.ts @@ -680,6 +680,8 @@ class MarginfiClient { const keypair = seed ?? Keypair.generate(); + const bundleTipIx = makeBundleTipIx(admin); + const priorityFeeIx = priorityFee ? makePriorityFeeIx(priorityFee) : []; const bankIxs = await this.group.makePoolAddBankIx(this.program, keypair.publicKey, mint, bankConfig, { @@ -689,7 +691,7 @@ class MarginfiClient { const signers = [...bankIxs.keys, keypair]; - const tx = new Transaction().add(...priorityFeeIx, ...bankIxs.instructions); + const tx = new Transaction().add(bundleTipIx, ...priorityFeeIx, ...bankIxs.instructions); const sig = await this.processTransaction(tx, signers, opts); dbg("Created Marginfi group %s", sig); @@ -711,9 +713,11 @@ class MarginfiClient { const accountKeypair = seed ?? Keypair.generate(); + const bundleTipIx = makeBundleTipIx(this.provider.publicKey); + const ixs = await this.makeCreateMarginfiGroupIx(accountKeypair.publicKey); const signers = [...ixs.keys, accountKeypair]; - const tx = new Transaction().add(...ixs.instructions, ...(additionalIxs ?? [])); + const tx = new Transaction().add(bundleTipIx, ...ixs.instructions, ...(additionalIxs ?? [])); const sig = await this.processTransaction(tx, signers, opts); dbg("Created Marginfi group %s", sig); diff --git a/packages/marginfi-client-v2/src/models/group.ts b/packages/marginfi-client-v2/src/models/group.ts index e7266503c9..962ce1a63e 100644 --- a/packages/marginfi-client-v2/src/models/group.ts +++ b/packages/marginfi-client-v2/src/models/group.ts @@ -193,7 +193,8 @@ class MarginfiGroup { bankMint: bankMint, bank: bankPubkey, tokenProgram: TOKEN_PROGRAM_ID, - oracleKey: rawBankConfigCompact.oracleKey, + // if two oracle keys: first is feed id, second is oracle key + oracleKey: rawBankConfig.oracle?.keys[1] || rawBankConfigCompact.oracleKey, }, { bankConfig: rawBankConfigCompact, diff --git a/packages/mrgn-utils/package.json b/packages/mrgn-utils/package.json index d2a65a507e..7e9bb50595 100644 --- a/packages/mrgn-utils/package.json +++ b/packages/mrgn-utils/package.json @@ -16,6 +16,7 @@ "@coral-xyz/anchor": "^0.30.1", "@jup-ag/api": "^6.0.23", "@jup-ag/react-hook": "^6.0.0-beta.8", + "@jup-ag/referral-sdk": "^0.1.7", "@mrgnlabs/marginfi-client-v2": "*", "@mrgnlabs/mrgn-common": "*", "@mrgnlabs/marginfi-v2-ui-state": "*", diff --git a/packages/mrgn-utils/src/actions/flashloans/builders.ts b/packages/mrgn-utils/src/actions/flashloans/builders.ts index 1845baf438..dd9cb94a57 100644 --- a/packages/mrgn-utils/src/actions/flashloans/builders.ts +++ b/packages/mrgn-utils/src/actions/flashloans/builders.ts @@ -6,16 +6,11 @@ import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-clien import { ActiveBankInfo, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { LUT_PROGRAM_AUTHORITY_INDEX, nativeToUi, uiToNative } from "@mrgnlabs/mrgn-common"; -import { - deserializeInstruction, - getAdressLookupTableAccounts, - getFeeAccount, - getSwapQuoteWithRetry, - TOKEN_2022_MINTS, -} from "../helpers"; +import { deserializeInstruction, getAdressLookupTableAccounts, getSwapQuoteWithRetry } from "../helpers"; import { isWholePosition } from "../../mrgnUtils"; import { ActionMethod, LoopingObject, LoopingOptions, RepayWithCollatOptions } from "../types"; import { STATIC_SIMULATION_ERRORS } from "../../errors"; +import { TOKEN_2022_MINTS, getFeeAccount } from "../../jup-referral.utils"; import { calculateMaxRepayableCollateral, diff --git a/packages/mrgn-utils/src/actions/helpers.ts b/packages/mrgn-utils/src/actions/helpers.ts index 3d22489993..fcc061e659 100644 --- a/packages/mrgn-utils/src/actions/helpers.ts +++ b/packages/mrgn-utils/src/actions/helpers.ts @@ -1,8 +1,10 @@ import { PublicKey, TransactionInstruction, Connection, AddressLookupTableAccount } from "@solana/web3.js"; +import { createJupiterApiClient, QuoteGetRequest } from "@jup-ag/api"; import { WalletContextState } from "@solana/wallet-adapter-react"; -import { WalletContextStateOverride } from "../wallet"; import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { createJupiterApiClient, QuoteGetRequest } from "@jup-ag/api"; + +import { WalletContextStateOverride } from "../wallet"; +import { REFERRAL_ACCOUNT_PUBKEY, REFERRAL_PROGRAM_ID } from "../jup-referral.utils"; // ------------------------------------------------------------------// // Helpers // @@ -57,19 +59,6 @@ export async function getAdressLookupTableAccounts( }, new Array()); } -export const TOKEN_2022_MINTS = ["2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo"]; - -export const getFeeAccount = (mint: PublicKey) => { - const referralProgramPubkey = new PublicKey("REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3"); - const referralAccountPubkey = new PublicKey("Mm7HcujSK2JzPW4eX7g4oqTXbWYDuFxapNMHXe8yp1B"); - - const [feeAccount] = PublicKey.findProgramAddressSync( - [Buffer.from("referral_ata"), referralAccountPubkey.toBuffer(), mint.toBuffer()], - referralProgramPubkey - ); - return feeAccount.toBase58(); -}; - export const formatAmount = ( newAmount: string, maxAmount: number, diff --git a/packages/mrgn-utils/src/actions/individualFlows.ts b/packages/mrgn-utils/src/actions/individualFlows.ts index 2745fb7152..7dbb48f560 100644 --- a/packages/mrgn-utils/src/actions/individualFlows.ts +++ b/packages/mrgn-utils/src/actions/individualFlows.ts @@ -317,17 +317,6 @@ export async function repay({ } } -const getFeeAccount = async (mint: PublicKey) => { - const referralProgramPubkey = new PublicKey("REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3"); - const referralAccountPubkey = new PublicKey("Mm7HcujSK2JzPW4eX7g4oqTXbWYDuFxapNMHXe8yp1B"); - - const [feeAccount] = await PublicKey.findProgramAddressSync( - [Buffer.from("referral_ata"), referralAccountPubkey.toBuffer(), mint.toBuffer()], - referralProgramPubkey - ); - return feeAccount.toBase58(); -}; - export async function looping({ marginfiClient, marginfiAccount, diff --git a/packages/mrgn-utils/src/index.ts b/packages/mrgn-utils/src/index.ts index 4790cacb91..0c9f9d2a32 100644 --- a/packages/mrgn-utils/src/index.ts +++ b/packages/mrgn-utils/src/index.ts @@ -10,3 +10,5 @@ export * from "./lstUtils"; export * from "./analytics"; export * from "./hooks"; export * from "./lst-apy.utils"; +export * from "./token.utils"; +export * from "./jup-referral.utils"; diff --git a/packages/mrgn-utils/src/jup-referral.utils.ts b/packages/mrgn-utils/src/jup-referral.utils.ts new file mode 100644 index 0000000000..4dfc5da28a --- /dev/null +++ b/packages/mrgn-utils/src/jup-referral.utils.ts @@ -0,0 +1,29 @@ +import { ReferralProvider } from "@jup-ag/referral-sdk"; +import { Connection, PublicKey } from "@solana/web3.js"; + +export const TOKEN_2022_MINTS = ["2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo"]; +export const REFERRAL_PROGRAM_ID = new PublicKey("REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3"); +export const REFERRAL_ACCOUNT_PUBKEY = new PublicKey("Mm7HcujSK2JzPW4eX7g4oqTXbWYDuFxapNMHXe8yp1B"); + +export async function createReferalTokenAccount(connection: Connection, payer: PublicKey, mint: PublicKey) { + const provider = new ReferralProvider(connection); + + const { tx, referralTokenAccountPubKey } = await provider.initializeReferralTokenAccount({ + payerPubKey: payer, + referralAccountPubKey: REFERRAL_ACCOUNT_PUBKEY, + mint, + }); + + return tx; +} + +export const getFeeAccount = (mint: PublicKey) => { + const referralProgramPubkey = REFERRAL_PROGRAM_ID; + const referralAccountPubkey = REFERRAL_ACCOUNT_PUBKEY; + + const [feeAccount] = PublicKey.findProgramAddressSync( + [Buffer.from("referral_ata"), referralAccountPubkey.toBuffer(), mint.toBuffer()], + referralProgramPubkey + ); + return feeAccount.toBase58(); +}; diff --git a/packages/mrgn-utils/src/token.utils.ts b/packages/mrgn-utils/src/token.utils.ts new file mode 100644 index 0000000000..c8ac14644a --- /dev/null +++ b/packages/mrgn-utils/src/token.utils.ts @@ -0,0 +1,19 @@ +export async function getBearerToken() { + // Check if token exists in sessionStorage + const token = sessionStorage.getItem("jwtToken"); + if (token) { + return token; + } + + const response = await fetch("/api/pool/auth"); + if (response.ok) { + const data = await response.json(); + const newToken = data.token; + + sessionStorage.setItem("jwtToken", newToken); + + return newToken; + } else { + throw new Error("Failed to retrieve Bearer token"); + } +} diff --git a/turbo.json b/turbo.json index 0b52fdde32..439e879304 100644 --- a/turbo.json +++ b/turbo.json @@ -91,6 +91,9 @@ "NEXT_PUBLIC_TOKENS_MAP", "NEXT_RUNTIME", "PRIVATE_RPC_ENDPOINT_OVERRIDE", - "NEXT_PUBLIC_APP_ID" + "NEXT_PUBLIC_APP_ID", + "NEXT_PUBLIC_INVALIDATE_GCP_CACHE", + "API_AUTH_USERNAME", + "API_AUTH_PASSWORD" ] } diff --git a/yarn.lock b/yarn.lock index cab4a1477a..393e63e7c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2159,7 +2159,7 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@cykura/sdk-core@npm:@jup-ag/cykura-sdk-core@0.1.8": +"@cykura/sdk-core@npm:@jup-ag/cykura-sdk-core@0.1.8", "@jup-ag/cykura-sdk-core@0.1.8": version "0.1.8" resolved "https://registry.npmjs.org/@jup-ag/cykura-sdk-core/-/cykura-sdk-core-0.1.8.tgz" integrity sha512-bVtDA4oEuzj/amuTPVlk1OFpdlYKK6H9nKWg6Tv6mn6MydS/ArC2EY2zuMHtWP+1YJ5CAwxHL/7Kl1k+7XBSoQ== @@ -2247,7 +2247,7 @@ nanoid "^3.3.4" tweetnacl "^1.0.3" -"@dradex/idl@npm:@jup-ag/dradex-idl@0.2.1": +"@dradex/idl@npm:@jup-ag/dradex-idl@0.2.1", "@jup-ag/dradex-idl@0.2.1": version "0.2.1" resolved "https://registry.npmjs.org/@jup-ag/dradex-idl/-/dradex-idl-0.2.1.tgz" integrity sha512-CZ5GZTLExy1+fw/tFOo6C4AbU0o/PcqJxxQpDp5UkSJ0SXbz7ZGMz9DfKu+htJuAwxwGgS/rbQfeBoU9fhDXuQ== @@ -4686,18 +4686,6 @@ decimal.js "^10.3.1" jsbi "^4.3.0" -"@jup-ag/cykura-sdk-core@0.1.8": - version "0.1.8" - resolved "https://registry.npmjs.org/@jup-ag/cykura-sdk-core/-/cykura-sdk-core-0.1.8.tgz" - integrity sha512-bVtDA4oEuzj/amuTPVlk1OFpdlYKK6H9nKWg6Tv6mn6MydS/ArC2EY2zuMHtWP+1YJ5CAwxHL/7Kl1k+7XBSoQ== - dependencies: - "@project-serum/anchor" "^0.22.0" - big.js "^5.2.2" - decimal.js "^10.3.1" - jsbi "^4.1.0" - tiny-invariant "^1.1.0" - toformat "^2.0.0" - "@jup-ag/cykura-sdk@0.1.25": version "0.1.25" resolved "https://registry.npmjs.org/@jup-ag/cykura-sdk/-/cykura-sdk-0.1.25.tgz" @@ -4720,14 +4708,6 @@ bignumber.js "^9.1.0" bn.js "^5.2.0" -"@jup-ag/dradex-idl@0.2.1": - version "0.2.1" - resolved "https://registry.npmjs.org/@jup-ag/dradex-idl/-/dradex-idl-0.2.1.tgz" - integrity sha512-CZ5GZTLExy1+fw/tFOo6C4AbU0o/PcqJxxQpDp5UkSJ0SXbz7ZGMz9DfKu+htJuAwxwGgS/rbQfeBoU9fhDXuQ== - dependencies: - "@solana/buffer-layout" "4.0.0" - bn.js "5.2.1" - "@jup-ag/dradex-sdk@0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@jup-ag/dradex-sdk/-/dradex-sdk-0.2.3.tgz" @@ -4840,6 +4820,16 @@ promise-retry "2.0.1" superstruct "~1.0.3" +"@jup-ag/referral-sdk@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@jup-ag/referral-sdk/-/referral-sdk-0.1.7.tgz#c3e172e2b5e127d26a7b103bb2b6aa04679edaa1" + integrity sha512-Y+JdmeyTW6NkgbTUADHdGFVAtyKRnKTGjW0+aDehDHBR4m+xEV3l8cblQsNvRqkHmy6CySNm/nDE7btHKO75SA== + dependencies: + "@coral-xyz/anchor" "0.28.1-beta.2" + "@solana/spl-token" "0.3.8" + "@solana/web3.js" "^1.77.3" + lodash "^4.17.21" + "@jup-ag/whirlpools-sdk@0.7.2": version "0.7.2" resolved "https://registry.npmjs.org/@jup-ag/whirlpools-sdk/-/whirlpools-sdk-0.7.2.tgz" @@ -9309,6 +9299,15 @@ "@solana/buffer-layout-utils" "^0.2.0" buffer "^6.0.3" +"@solana/spl-token@0.3.8": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.8.tgz#8e9515ea876e40a4cc1040af865f61fc51d27edf" + integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + buffer "^6.0.3" + "@solana/spl-token@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.9.tgz" @@ -9329,7 +9328,7 @@ "@solana/spl-token-metadata" "^0.1.4" buffer "^6.0.3" -"@solana/spl-token@^0.2.0": +"@solana/spl-token@^0.2.0", "spl2@npm:@solana/spl-token@^0.2.0": version "0.2.0" resolved "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.2.0.tgz" integrity sha512-RWcn31OXtdqIxmkzQfB2R+WpsJOVS6rKuvpxJFjvik2LyODd+WN58ZP3Rpjpro03fscGAkzlFuP3r42doRJgyQ== @@ -9871,7 +9870,7 @@ rpc-websockets "^9.0.0" superstruct "^1.0.4" -"@solana/web3.js@^1.54.0", "@solana/web3.js@^1.93.0", "@solana/web3.js@^1.95.0": +"@solana/web3.js@^1.54.0", "@solana/web3.js@^1.77.3", "@solana/web3.js@^1.93.0", "@solana/web3.js@^1.95.0": version "1.95.3" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.3.tgz#70b5f4d76823f56b5af6403da51125fffeb65ff3" integrity sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og== @@ -27216,16 +27215,6 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -"spl2@npm:@solana/spl-token@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.2.0.tgz" - integrity sha512-RWcn31OXtdqIxmkzQfB2R+WpsJOVS6rKuvpxJFjvik2LyODd+WN58ZP3Rpjpro03fscGAkzlFuP3r42doRJgyQ== - dependencies: - "@solana/buffer-layout" "^4.0.0" - "@solana/buffer-layout-utils" "^0.2.0" - "@solana/web3.js" "^1.32.0" - start-server-and-test "^1.14.0" - split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz" @@ -27432,7 +27421,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -27450,15 +27439,6 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" @@ -27553,7 +27533,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -27567,13 +27547,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" @@ -29952,7 +29925,7 @@ workbox-window@6.6.1, workbox-window@^6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -29979,15 +29952,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"