Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/chains/src/types/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SupportedLiquityV2,
TargetType,
} from "@metrom-xyz/sdk";
import { SupportedChain } from "@metrom-xyz/contracts";

export enum ProtocolType {
Dex = "dex",
Expand Down Expand Up @@ -34,6 +35,10 @@ export interface DexProtocol extends ProtocolBase<SupportedDex> {
supportsFetchAllPools: boolean;
}

export type WithChain<T extends ProtocolBase> = T & {
chainId: SupportedChain;
};

export interface LiquityV2Protocol extends ProtocolBase<SupportedLiquityV2> {
type: ProtocolType.LiquityV2;
debtToken: Erc20Token;
Expand Down
24 changes: 12 additions & 12 deletions packages/frontend/SUPPORTED_PROTOCOLS.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
||Telos|Gnosis|Sonic|Lens|Sei Network|LightLink|Swell|Base|Hemi|Taiko|Scroll|Lumia|
|---|---|---|---|---|---|---|---|---|---|---|---|---
|[Uniswap v3](https://app.uniswap.org/)|✅|✅|✅|✅|-|✅|-|✅|✅|✅|✅|-|
|[Swapr](https://swapr.eth.link/)|-|✅|-|-|-|-|-|-|-|-|-|-|
|[SilverSwap](https://silverswap.io/)|-|-|✅|-|-|-|-|-|-|-|-|-|
|[Carbon DeFi](https://carbondefi.xyz/)|-|-|-|-|✅|-|-|-|-|-|-|-|
|[Velodrome](https://velodrome.finance/)|-|-|-|-|-|-|✅|-|-|-|-|-|
|[Kim](https://www.kim.exchange)|-|-|-|-|-|-|-|✅|-|-|-|-|
|[BaseSwap](https://baseswap.fi/)|-|-|-|-|-|-|-|✅|-|-|-|-|
|[Unagi](https://unagiswap.xyz/)|-|-|-|-|-|-|-|-|-|✅|-|-|
|[Scribe](https://scribe.exchange)|-|-|-|-|-|-|-|-|-|-|✅|-|
|[Morphex](https://morphex.exchange/)|-|-|-|-|-|-|-|-|-|-|-|✅|
| | Telos | Gnosis | Sonic | Lens | Sei Network | LightLink | Swell | Base | Hemi | Taiko | Scroll | Lumia |
| --------------------------------------- | ----- | ------ | ----- | ---- | ----------- | --------- | ----- | ---- | ---- | ----- | ------ | ----- |
| [Uniswap v3](https://app.uniswap.org/) | ✅ | ✅ | ✅ | ✅ | - | ✅ | - | ✅ | ✅ | ✅ | ✅ | - |
| [Swapr](https://swapr.eth.link/) | - | ✅ | - | - | - | - | - | - | - | - | - | - |
| [SilverSwap](https://silverswap.io/) | - | - | ✅ | - | - | - | - | - | - | - | - | - |
| [Carbon DeFi](https://carbondefi.xyz/) | - | - | - | - | ✅ | - | - | - | - | - | - | - |
| [Velodrome](https://velodrome.finance/) | - | - | - | - | - | - | ✅ | - | - | - | - | - |
| [Kim](https://www.kim.exchange) | - | - | - | - | - | - | - | ✅ | - | - | - | - |
| [BaseSwap](https://baseswap.fi/) | - | - | - | - | - | - | - | ✅ | - | - | - | - |
| [Unagi](https://unagiswap.xyz/) | - | - | - | - | - | - | - | - | - | ✅ | - | - |
| [Scribe](https://scribe.exchange) | - | - | - | - | - | - | - | - | - | - | ✅ | - |
| [Morphex](https://morphex.exchange/) | - | - | - | - | - | - | - | - | - | - | - | ✅ |
26 changes: 24 additions & 2 deletions packages/frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,16 @@
"approve": "Approve {amount} {symbol} ({currentIndex}/{totalAmount})"
},
"preview": "Campaign preview"
},
"notifications": {
"setupFail": {
"title": "Setup failed",
"message": "Something went wrong"
},
"setupSuccess": {
"title": "Setup successful",
"message": "Campaign imported"
}
}
},
"campaignPreview": {
Expand Down Expand Up @@ -395,8 +405,10 @@
"allows": "Allows {count, plural, =0 {} =1 {# address} other {# adresses}}"
},
"errors": {
"specification": "An error occurred while processing the campaign specification"
"specification": "An error occurred while processing the campaign specification",
"setup": "An error occurred while processing the campaign setup"
},
"share": "Share campaign setup",
"deploy": "Launch campaign",
"connectWallet": "Connect wallet",
"congratulations": "Congratulations!",
Expand All @@ -408,7 +420,17 @@
}
},
"allCampaigns": "All campaigns",
"newCampaign": "New campaign"
"newCampaign": "New campaign",
"notifications": {
"linkCopied": {
"title": "Link copied to clipboard",
"message": "Expires in 1 day"
},
"linkError": {
"title": "Link generation failed",
"message": "Something went wrong"
}
}
},
"campaignDuration": {
"startDate": "Start date",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CreateCampaignForm } from "@/src/components/create-campaign/form";
import { routing, type Locale } from "@/src/i18n/routing";
import { CampaignType } from "@/src/types/campaign";
import { setRequestLocale } from "next-intl/server";
import { Suspense } from "react";

interface Params {
type: CampaignType;
Expand All @@ -26,7 +27,11 @@ export default async function CampaignFormPage({

setRequestLocale(locale);

return <CreateCampaignForm type={type} />;
return (
<Suspense>
<CreateCampaignForm type={type} />
</Suspense>
);
}

export async function generateStaticParams() {
Expand Down
6 changes: 4 additions & 2 deletions packages/frontend/src/components/campaigns/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ export function Campaigns() {
const params = new URLSearchParams(searchParams.toString());
if (!chain.query) params.delete("chain");
else params.set("chain", chain.query);
router.replace(`${pathname}?${params.toString()}`);
router.replace(`${pathname}?${params.toString()}`, {
scroll: false,
});
},
[pathname, router, searchParams],
);
Expand All @@ -329,7 +331,7 @@ export function Campaigns() {

const params = new URLSearchParams(searchParams.toString());
params.delete("chain");
router.replace(`${pathname}?${params.toString()}`);
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
}, [pathname, searchParams, router]);

function handlePreviousPage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ import {
AmmPoolLiquidityCampaignPreviewPayload,
type AmmPoolLiquidityCampaignPayloadPart,
type CampaignPreviewDistributables,
CampaignKind,
} from "@/src/types/campaign";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useChainId } from "wagmi";
import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useState,
} from "react";
import { useChainId, useSwitchChain } from "wagmi";
import {
AmmPoolLiquidityType,
DistributablesType,
Expand All @@ -28,8 +35,16 @@ import {
AMM_SUPPORTS_TOKENS_RATIO,
} from "@/src/commons";
import { WeightingStep } from "../../steps/weighting";
import { usePathname } from "@/i18n/routing";
import dayjs from "dayjs";
import { useCampaignSetup } from "@/src/hooks/useCampaignSetup";
import { toast } from "sonner";
import { SetupFail } from "../notifications/setup-fail";
import { SetupSuccess } from "../notifications/setup-success";
import { useRouter, useSearchParams } from "next/navigation";

import styles from "./styles.module.css";
import { decodeCampaignSetup } from "@/src/utils/campaign";

function validatePayload(
payload: AmmPoolLiquidityCampaignPayload,
Expand Down Expand Up @@ -88,11 +103,79 @@ export function AmmPoolLiquidityForm({
onPreviewClick,
}: AmmPoolLiquidityFormProps) {
const t = useTranslations("newCampaign");

const chainId = useChainId();
const router = useRouter();
const pathname = usePathname();
const { switchChainAsync, isPending: switchingChain } = useSwitchChain();
const searchParams = useSearchParams();
const {
loading: loadingSetup,
error: setupError,
setup,
} = useCampaignSetup({
hash: searchParams.get("setup"),
enabled: !!searchParams.get("setup"),
});

const [payload, setPayload] = useState(initialPayload);
const [errors, setErrors] = useState<CampaignPayloadErrors>({});

// This hook auto fills the form state when a campaign setup is available.
useLayoutEffect(() => {
// Remove the 'setup' parameter from the URL after parsing.
// This ensures the form behaves correctly after autocomplete completes.
const params = new URLSearchParams(searchParams.toString());

if (setupError) {
params.delete("setup");
router.replace(`${pathname}?${params.toString()}`, {
scroll: false,
});
}

if (!setup) return;

const autocompletePayload = async () => {
const decodedSetup = decodeCampaignSetup(setup);
if (decodedSetup.kind !== CampaignKind.AmmPoolLiquidity) return;

const payload = decodedSetup as AmmPoolLiquidityCampaignPayload;
if (!payload.dex?.chainId) return;

const { dex } = payload;

if (dex.chainId !== chainId)
await switchChainAsync({ chainId: dex.chainId });

params.delete("setup");
router.replace(`${pathname}?${params.toString()}`, {
scroll: false,
});

setPayload(payload);
};

autocompletePayload();
}, [
loadingSetup,
setupError,
searchParams,
chainId,
setup,
pathname,
router,
switchChainAsync,
]);

useEffect(() => {
if (!loadingSetup && setupError)
toast.custom((toastId) => <SetupFail toastId={toastId} />);

if (!loadingSetup && !setupError && !!setup)
toast.custom((toastId) => <SetupSuccess toastId={toastId} />);
}, [loadingSetup, setupError, setup]);

const previewPayload = useMemo(() => {
if (Object.values(errors).some((error) => !!error)) return null;
return validatePayload(payload);
Expand Down Expand Up @@ -155,36 +238,43 @@ export function AmmPoolLiquidityForm({
onPreviewClick(previewPayload);
}

const loading = loadingSetup || switchingChain;

return (
<div className={styles.root}>
<div className={styles.stepsWrapper}>
<DexStep
loading={loading}
disabled={unsupportedChain}
dex={payload.dex}
onDexChange={handlePayloadOnChange}
/>
<PoolStep
loading={loading}
disabled={!payload.dex || unsupportedChain}
dex={payload.dex}
pool={payload.pool}
onPoolChange={handlePayloadOnChange}
onError={handlePayloadOnError}
/>
<StartDateStep
loading={loading}
disabled={!payload.pool || unsupportedChain}
startDate={payload.startDate}
endDate={payload.endDate}
onStartDateChange={handlePayloadOnChange}
onError={handlePayloadOnError}
/>
<EndDateStep
loading={loading}
disabled={!payload.startDate || unsupportedChain}
startDate={payload.startDate}
endDate={payload.endDate}
onEndDateChange={handlePayloadOnChange}
onError={handlePayloadOnError}
/>
<RewardsStep
loading={loading}
disabled={!payload.endDate || unsupportedChain}
distributables={payload.distributables}
startDate={payload.startDate}
Expand All @@ -195,13 +285,15 @@ export function AmmPoolLiquidityForm({
{tokensRatioSupported && (
<WeightingStep
pool={payload.pool}
distributablesType={payload.distributables?.type}
disabled={noDistributables || unsupportedChain}
weighting={payload.weighting}
onWeightingChange={handlePayloadOnChange}
onError={handlePayloadOnError}
/>
)}
<KpiStep
loading={loading}
disabled={noDistributables || unsupportedChain}
pool={payload.pool}
distributables={
Expand All @@ -223,6 +315,7 @@ export function AmmPoolLiquidityForm({
payload.pool.liquidityType ===
AmmPoolLiquidityType.Concentrated && (
<RangeStep
autoCompleting={!!setup}
disabled={noDistributables || unsupportedChain}
distributablesType={payload.distributables?.type}
pool={payload.pool}
Expand All @@ -234,6 +327,7 @@ export function AmmPoolLiquidityForm({
/>
)}
<RestrictionsStep
loading={loading}
disabled={missingDistributables || unsupportedChain}
restrictions={payload.restrictions}
onRestrictionsChange={handlePayloadOnChange}
Expand All @@ -243,6 +337,7 @@ export function AmmPoolLiquidityForm({
<Button
icon={ArrowRightIcon}
iconPlacement="right"
loading={loading}
disabled={!previewPayload}
className={{ root: styles.button }}
onClick={handlePreviewOnClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function FormHeader({ type }: FormHeaderProps) {
const router = useRouter();

function handleBackOnClick() {
router.back();
router.push("/campaigns/create");
}

return (
Expand Down
16 changes: 8 additions & 8 deletions packages/frontend/src/components/create-campaign/form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ export function CreateCampaignForm<T extends CampaignType>({
const [view, setView] = useState(View.Form);
const [payload, setPayload] = useState<CampaignPreviewPayload | null>(null);

const unsupportedChain = useMemo(() => {
return (
isConnected &&
(!connectedChain ||
!chains.some((chain) => chain.id === selectedChain))
);
}, [chains, connectedChain, isConnected, selectedChain]);

function handlePreviewOnClick(payload: CampaignPreviewPayload | null) {
setPayload(payload);
setView(View.Preview);
Expand All @@ -62,14 +70,6 @@ export function CreateCampaignForm<T extends CampaignType>({
router.push("/campaigns/create");
}

const unsupportedChain = useMemo(() => {
return (
isConnected &&
(!connectedChain ||
!chains.some((chain) => chain.id === selectedChain))
);
}, [chains, connectedChain, isConnected, selectedChain]);

return (
<div className={styles.root}>
{dexesProtocols.length > 0 && liquityV2Protocols.length > 0 && (
Expand Down
Loading