diff --git a/public/widget/widget-execution-dark.png b/public/widget/widget-execution-dark.png
new file mode 100644
index 000000000..f12429d0b
Binary files /dev/null and b/public/widget/widget-execution-dark.png differ
diff --git a/public/widget/widget-execution-light.png b/public/widget/widget-execution-light.png
new file mode 100644
index 000000000..245167627
Binary files /dev/null and b/public/widget/widget-execution-light.png differ
diff --git a/public/widget/widget-quotes-dark.png b/public/widget/widget-quotes-dark.png
new file mode 100644
index 000000000..3ed1ece64
Binary files /dev/null and b/public/widget/widget-quotes-dark.png differ
diff --git a/public/widget/widget-quotes-light.png b/public/widget/widget-quotes-light.png
new file mode 100644
index 000000000..ee3dbbdf4
Binary files /dev/null and b/public/widget/widget-quotes-light.png differ
diff --git a/public/widget/widget-review-bridge-dark.png b/public/widget/widget-review-bridge-dark.png
new file mode 100644
index 000000000..854bc4c54
Binary files /dev/null and b/public/widget/widget-review-bridge-dark.png differ
diff --git a/public/widget/widget-review-bridge-light.png b/public/widget/widget-review-bridge-light.png
new file mode 100644
index 000000000..f3549a6bc
Binary files /dev/null and b/public/widget/widget-review-bridge-light.png differ
diff --git a/public/widget/widget-selection-dark.png b/public/widget/widget-selection-dark.png
new file mode 100644
index 000000000..ebe14a7d6
Binary files /dev/null and b/public/widget/widget-selection-dark.png differ
diff --git a/public/widget/widget-selection-light.png b/public/widget/widget-selection-light.png
new file mode 100644
index 000000000..9762f9a8a
Binary files /dev/null and b/public/widget/widget-selection-light.png differ
diff --git a/public/widget/widget-success-dark.png b/public/widget/widget-success-dark.png
new file mode 100644
index 000000000..8fa739505
Binary files /dev/null and b/public/widget/widget-success-dark.png differ
diff --git a/public/widget/widget-success-light.png b/public/widget/widget-success-light.png
new file mode 100644
index 000000000..03402ecf8
Binary files /dev/null and b/public/widget/widget-success-light.png differ
diff --git a/src/app/api/widget-execution/route.tsx b/src/app/api/widget-execution/route.tsx
new file mode 100644
index 000000000..820554fdb
--- /dev/null
+++ b/src/app/api/widget-execution/route.tsx
@@ -0,0 +1,102 @@
+/* eslint-disable @next/next/no-img-element */
+
+/**
+ * Image Generation of Widget for SEO pages
+ * Step 4 - Route execution
+ *
+ * Example:
+ * ```
+ * http://localhost:3000/api/widget-execution?fromToken=0x0000000000000000000000000000000000000000&fromChainId=137&toToken=0x0000000000000000000000000000000000000000&toChainId=42161&amount=10&&theme=light&isSwap=true
+ * ```
+ *
+ * @typedef {Object} SearchParams
+ * @property {string} fromToken - The token address to send from.
+ * @property {number} fromChainId - The chain ID to send from.
+ * @property {string} toToken - The token address to send to.
+ * @property {number} toChainId - The chain ID to send to.
+ * @property {number} amount - The amount of tokens.
+ * @property {number} [amountUSD] - The USD equivalent amount (optional).
+ * @property {boolean} [isSwap] - True if transaction is a swap, default and false if transaction is a bridge (optional).
+ * @property {'light'|'dark'} [theme] - The theme for the widget (optional).
+ * @property {'from'|'to'|'amount'} [highlighted] - The highlighted element (optional).
+ *
+ */
+
+import type { ChainId } from '@lifi/sdk';
+import { ImageResponse } from 'next/og';
+import type { CSSProperties } from 'react';
+import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGeneration.types';
+import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
+import { imageFrameStyles } from 'src/components/ImageGeneration/style';
+import WidgetExecutionImage from 'src/components/ImageGeneration/WidgetExecutionImage';
+import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
+import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
+import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
+
+const WIDGET_IMAGE_WIDTH = 416;
+const WIDGET_IMAGE_HEIGHT = 432;
+const WIDGET_IMAGE_SCALING_FACTOR = 2;
+
+export async function GET(request: Request) {
+ const {
+ fromChainId,
+ toChainId,
+ fromToken,
+ toToken,
+ isSwap,
+ theme,
+ amount,
+ highlighted,
+ } = parseSearchParams(request.url);
+
+ // Fetch data asynchronously before rendering
+ const fromTokenData = await fetchTokenData(fromChainId, fromToken);
+ const toTokenData = await fetchTokenData(toChainId, toToken);
+ const fromChain = await fetchChainData(fromChainId as unknown as ChainId);
+ const toChain = await fetchChainData(toChainId as unknown as ChainId);
+
+ const options = await imageResponseOptions({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ });
+
+ const imageFrameStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const imageStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ return new ImageResponse(
+ (
+
+
+
+
+ ),
+ options,
+ );
+}
diff --git a/src/app/api/widget-quotes/route.tsx b/src/app/api/widget-quotes/route.tsx
new file mode 100644
index 000000000..449498ca0
--- /dev/null
+++ b/src/app/api/widget-quotes/route.tsx
@@ -0,0 +1,110 @@
+/* eslint-disable @next/next/no-img-element */
+
+/**
+ * Image Generation of Widget for SEO pages
+ * Step 2 - Quotes
+ *
+ * Example:
+ * ```
+ * http://localhost:3000/api/widget-quotes?fromToken=0x0000000000000000000000000000000000000000&fromChainId=137&toToken=0x0000000000000000000000000000000000000000&toChainId=42161&amount=10&highlighted=0&theme=light
+ * ```
+ *
+ * @typedef {Object} SearchParams
+ * @property {string} fromToken - The token address to send from.
+ * @property {number} fromChainId - The chain ID to send from.
+ * @property {string} toToken - The token address to send to.
+ * @property {number} toChainId - The chain ID to send to.
+ * @property {number} amount - The amount of tokens.
+ * @property {number} [amountUSD] - The USD equivalent amount (optional).
+ * @property {boolean} [isSwap] - True if transaction is a swap, default and false if transaction is a bridge (optional).
+ * @property {'light'|'dark'} [theme] - The theme for the widget (optional).
+ * @property {'from'|'to'|'amount'|'0'|'1'|'2'} [highlighted] - The highlighted element, numbers refer to quote index (optional).
+ *
+ */
+import type { ChainId } from '@lifi/sdk';
+import { ImageResponse } from 'next/og';
+import type { CSSProperties } from 'react';
+import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGeneration.types';
+import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
+import { imageFrameStyles } from 'src/components/ImageGeneration/style';
+import WidgetQuoteImage from 'src/components/ImageGeneration/WidgetQuotesImage';
+import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
+import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
+import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
+
+const WIDGET_IMAGE_WIDTH = 856;
+const WIDGET_IMAGE_HEIGHT = 490; //376;
+const WIDGET_IMAGE_SCALING_FACTOR = 2;
+
+export async function GET(request: Request) {
+ const {
+ fromChainId,
+ toChainId,
+ fromToken,
+ toToken,
+ isSwap,
+ theme,
+ amount,
+ highlighted,
+ amountUSD,
+ } = parseSearchParams(request.url);
+
+ // Fetch data asynchronously before rendering
+ const fromTokenData = await fetchTokenData(fromChainId, fromToken);
+ const toTokenData = await fetchTokenData(toChainId, toToken);
+ const fromChain = await fetchChainData(fromChainId as unknown as ChainId);
+ const toChain = await fetchChainData(toChainId as unknown as ChainId);
+
+ const routeAmount =
+ (parseFloat(fromTokenData?.priceUSD || '0') * parseFloat(amount || '0')) /
+ parseFloat(toTokenData?.priceUSD || '0');
+
+ const options = await imageResponseOptions({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ });
+
+ const imageFrameStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const imageStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const ImageResp = new ImageResponse(
+ (
+
+
+
+
+ ),
+ options,
+ );
+ // console.timeEnd('start-time');
+ return ImageResp;
+}
diff --git a/src/app/api/widget-review/route.tsx b/src/app/api/widget-review/route.tsx
new file mode 100644
index 000000000..f4b599f7a
--- /dev/null
+++ b/src/app/api/widget-review/route.tsx
@@ -0,0 +1,101 @@
+/* eslint-disable @next/next/no-img-element */
+
+/**
+ * Image Generation of Widget for SEO pages
+ * Step 3 - Review quote
+ *
+ * Example:
+ * ```
+ * http://localhost:3000/api/widget-review?fromToken=0x0000000000000000000000000000000000000000&fromChainId=137&toToken=0x0000000000000000000000000000000000000000&toChainId=42161&amount=10&highlighted=amount&theme=dark
+ * ```
+ *
+ * @typedef {Object} SearchParams
+ * @property {string} fromToken - The token address to send from.
+ * @property {number} fromChainId - The chain ID to send from.
+ * @property {string} toToken - The token address to send to.
+ * @property {number} toChainId - The chain ID to send to.
+ * @property {number} amount - The amount of tokens.
+ * @property {boolean} [isSwap] - True if transaction is a swap, default and false if transaction is a bridge (optional).
+ * @property {'light'|'dark'} [theme] - The theme for the widget (optional).
+ * @property {'from'|'to'|'amount'} [highlighted] - The highlighted element (optional).
+ *
+ */
+
+import type { ChainId } from '@lifi/sdk';
+import { ImageResponse } from 'next/og';
+import type { CSSProperties } from 'react';
+import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGeneration.types';
+import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
+import { imageFrameStyles } from 'src/components/ImageGeneration/style';
+import WidgetReviewImage from 'src/components/ImageGeneration/WidgetReviewImage';
+import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
+import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
+import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
+
+const WIDGET_IMAGE_WIDTH = 416;
+const WIDGET_IMAGE_HEIGHT = 440;
+const WIDGET_IMAGE_SCALING_FACTOR = 2;
+
+export async function GET(request: Request) {
+ const {
+ fromChainId,
+ toChainId,
+ fromToken,
+ toToken,
+ isSwap,
+ theme,
+ amount,
+ highlighted,
+ } = parseSearchParams(request.url);
+
+ // Fetch data asynchronously before rendering
+ const fromTokenData = await fetchTokenData(fromChainId, fromToken);
+ const toTokenData = await fetchTokenData(toChainId, toToken);
+ const fromChain = await fetchChainData(fromChainId as unknown as ChainId);
+ const toChain = await fetchChainData(toChainId as unknown as ChainId);
+
+ const options = await imageResponseOptions({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ });
+
+ const imageFrameStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const imageStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ return new ImageResponse(
+ (
+
+
+
+
+ ),
+ options,
+ );
+}
diff --git a/src/app/api/widget-selection/route.tsx b/src/app/api/widget-selection/route.tsx
new file mode 100644
index 000000000..c9c5f7b89
--- /dev/null
+++ b/src/app/api/widget-selection/route.tsx
@@ -0,0 +1,101 @@
+/* eslint-disable @next/next/no-img-element */
+
+/**
+ * Image Generation of Widget for SEO pages
+ * Step 1 - Selecting Tokens
+ *
+ * Example:
+ * ```
+ * http://localhost:3000/api/widget-selection?fromToken=0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063&fromChainId=137&toToken=0xdAC17F958D2ee523a2206206994597C13D831ec7&toChainId=1&amount=3&theme=dark
+ * ```
+ *
+ * @typedef {Object} SearchParams
+ * @property {string} fromToken - The token address to send from.
+ * @property {number} fromChainId - The chain ID to send from.
+ * @property {string} toToken - The token address to send to.
+ * @property {number} toChainId - The chain ID to send to.
+ * @property {number} amount - The amount of tokens.
+ * @property {number} [amountUSD] - The USD equivalent amount (optional).
+ * @property {'light'|'dark'} [theme] - The theme for the widget (optional).
+ * @property {'from'|'to'|'amount'} [highlighted] - The highlighted element (optional).
+ *
+ */
+
+import type { ChainId } from '@lifi/sdk';
+import { ImageResponse } from 'next/og';
+import type { CSSProperties } from 'react';
+import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGeneration.types';
+import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
+import { imageFrameStyles } from 'src/components/ImageGeneration/style';
+import WidgetSelectionImage from 'src/components/ImageGeneration/WidgetSelectionImage';
+import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
+import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
+import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
+
+const WIDGET_IMAGE_WIDTH = 416;
+const WIDGET_IMAGE_HEIGHT = 496;
+const WIDGET_IMAGE_SCALING_FACTOR = 2;
+
+export async function GET(request: Request) {
+ const {
+ fromChainId,
+ toChainId,
+ fromToken,
+ toToken,
+ theme,
+ amount,
+ highlighted,
+ amountUSD,
+ } = parseSearchParams(request.url);
+
+ // Fetch data asynchronously before rendering
+ const fromTokenData = await fetchTokenData(fromChainId, fromToken);
+ const toTokenData = await fetchTokenData(toChainId, toToken);
+ const fromChain = await fetchChainData(fromChainId as unknown as ChainId);
+ const toChain = await fetchChainData(toChainId as unknown as ChainId);
+
+ const options = await imageResponseOptions({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ });
+
+ const imageFrameStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const imageStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ return new ImageResponse(
+ (
+
+
+
+
+ ),
+ options,
+ );
+}
diff --git a/src/app/api/widget-success/route.tsx b/src/app/api/widget-success/route.tsx
new file mode 100644
index 000000000..afdbf4121
--- /dev/null
+++ b/src/app/api/widget-success/route.tsx
@@ -0,0 +1,85 @@
+/* eslint-disable @next/next/no-img-element */
+
+/**
+ * Image Generation of Widget for SEO pages
+ * Step 5 - Route success
+ *
+ * Example:
+ * ```
+ * http://localhost:3000/api/widget-success?toToken=0x0000000000000000000000000000000000000000&toChainId=42161&amount=10&&theme=light&isSwap=true
+ * ```
+ *
+ * @typedef {Object} SearchParams
+ * @property {string} toToken - The token address to send to.
+ * @property {number} toChainId - The chain ID to send to.
+ * @property {number} amount - The amount of tokens.
+ * @property {boolean} [isSwap] - True if transaction is a swap, default and false if transaction is a bridge (optional).
+ * @property {'light'|'dark'} [theme] - The theme for the widget (optional).
+ *
+ */
+
+import type { ChainId } from '@lifi/sdk';
+import { ImageResponse } from 'next/og';
+import type { CSSProperties } from 'react';
+import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
+import { imageFrameStyles } from 'src/components/ImageGeneration/style';
+import WidgetSuccessImage from 'src/components/ImageGeneration/WidgetSuccessImage';
+import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
+import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
+import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
+
+const WIDGET_IMAGE_WIDTH = 416;
+const WIDGET_IMAGE_HEIGHT = 432;
+const WIDGET_IMAGE_SCALING_FACTOR = 2;
+
+export async function GET(request: Request) {
+ const { toChainId, toToken, theme, amount, isSwap } = parseSearchParams(
+ request.url,
+ );
+
+ // Fetch data asynchronously before rendering
+ const toTokenData = await fetchTokenData(toChainId, toToken);
+ const toChain = await fetchChainData(toChainId as unknown as ChainId);
+
+ const options = await imageResponseOptions({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ });
+
+ const imageFrameStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const imageStyle = imageFrameStyles({
+ width: WIDGET_IMAGE_WIDTH,
+ height: WIDGET_IMAGE_HEIGHT,
+ scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
+ }) as CSSProperties;
+
+ return new ImageResponse(
+ (
+
+
+
+
+ ),
+ options,
+ );
+}
diff --git a/src/components/AvatarBadge/NoMUI/AvatarBadgeNoMUI.tsx b/src/components/AvatarBadge/NoMUI/AvatarBadgeNoMUI.tsx
new file mode 100644
index 000000000..f40caffaa
--- /dev/null
+++ b/src/components/AvatarBadge/NoMUI/AvatarBadgeNoMUI.tsx
@@ -0,0 +1,83 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+
+interface BadgeOffsetProps {
+ x?: number;
+ y?: number;
+}
+
+type AvatarBadgeNoMUIProps = {
+ avatarSrc?: string;
+ badgeSrc?: string;
+ badgeOffset?: BadgeOffsetProps;
+ avatarSize: number;
+ badgeGap?: number;
+ badgeSize: number;
+ theme?: 'light' | 'dark';
+};
+
+export const AvatarBadgeNoMUI = ({
+ avatarSrc,
+ badgeSrc,
+ badgeOffset,
+ badgeGap,
+ avatarSize,
+ badgeSize,
+ theme,
+}: AvatarBadgeNoMUIProps) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/ImageGeneration/Field.tsx b/src/components/ImageGeneration/Field.tsx
new file mode 100644
index 000000000..7a711b728
--- /dev/null
+++ b/src/components/ImageGeneration/Field.tsx
@@ -0,0 +1,230 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import { getOffset, getWidth } from 'src/utils/image-generation/helpers';
+import { AvatarBadgeNoMUI } from '../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import { FieldSkeleton } from './FieldSkeleton';
+import type { ImageTheme } from './ImageGeneration.types';
+
+const Field = ({
+ sx,
+ token,
+ chain,
+ type,
+ amount = 0,
+ extendedHeight,
+ theme,
+ amountUSD,
+ routeAmount,
+ routeAmountUSD,
+ highlighted,
+ fullWidth,
+ showSkeletons,
+}: {
+ sx?: any;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ type:
+ | 'amount'
+ | 'token'
+ | 'quote'
+ | 'review'
+ | 'quote-amount'
+ | 'amount-selection'
+ | 'button'
+ | 'title'
+ | 'card-title'
+ | 'success';
+ amount?: number | null;
+ amountUSD?: number | null;
+ highlighted?: boolean | null;
+ routeAmount?: number | null;
+ routeAmountUSD?: number | null;
+ extendedHeight?: boolean;
+ fullWidth?: boolean;
+ showSkeletons?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+
+ const containerOffset = getOffset(type, extendedHeight);
+ const containerWidth = getWidth(type, fullWidth);
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 6,
+ });
+
+ return (
+
+
+ {type !== 'button' && (
+
+ {token && chain && (
+
+ )}
+ {type === 'token' && (
+
+ )}
+ {type !== 'token' && (
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+ {!showSkeletons && token ? (
+
+
+ ${(routeAmountUSD || amountUSD || amount || 0).toFixed(2)}
+
+ {type === 'review' && (
+
+ {`${token.symbol} on ${chain?.name}`}
+
+ )}
+
+ ) : (
+ <>
+ {type === 'review' && (
+
+ )}
+ {type === 'success' && (
+
+ )}
+ {type === 'quote' && (
+
+ )}
+ >
+ )}
+
+ )}
+
+ )}
+ {type === 'quote' && (
+
+ )}
+
+
+ );
+};
+
+export default Field;
diff --git a/src/components/ImageGeneration/FieldSkeleton.tsx b/src/components/ImageGeneration/FieldSkeleton.tsx
new file mode 100644
index 000000000..d868cc2f6
--- /dev/null
+++ b/src/components/ImageGeneration/FieldSkeleton.tsx
@@ -0,0 +1,26 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { CSSProperties } from 'react';
+
+export const FieldSkeleton = ({
+ width,
+ height,
+ sx,
+}: {
+ width: number;
+ height: number;
+ sx?: CSSProperties;
+}) => {
+ return (
+
+ );
+};
diff --git a/src/components/ImageGeneration/Fields/AmountField.tsx b/src/components/ImageGeneration/Fields/AmountField.tsx
new file mode 100644
index 000000000..52822c9e5
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/AmountField.tsx
@@ -0,0 +1,92 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import { FieldSkeleton } from '../FieldSkeleton';
+import type { ImageTheme } from '../ImageGeneration.types';
+import { amountTextStyles } from '../style';
+
+const AmountField = ({
+ sx,
+ token,
+ chain,
+ amount = 0,
+ extendedHeight,
+ theme,
+ routeAmount,
+ highlighted,
+ fullWidth,
+}: {
+ sx?: CSSProperties;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ amount?: number | null;
+ highlighted?: boolean | null;
+ routeAmount?: number | null;
+ extendedHeight?: boolean;
+ fullWidth?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 6,
+ });
+
+ const amountTextStyle = amountTextStyles(theme);
+
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+
+
+
+
+
+ );
+};
+
+export default AmountField;
diff --git a/src/components/ImageGeneration/Fields/AmountSelectionField.tsx b/src/components/ImageGeneration/Fields/AmountSelectionField.tsx
new file mode 100644
index 000000000..c0fdc2937
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/AmountSelectionField.tsx
@@ -0,0 +1,99 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import type { ImageTheme } from '../ImageGeneration.types';
+import {
+ amountContainerStyles,
+ amountTextStyles,
+ fieldContainerStyles,
+ tokenTextStyles,
+} from '../style';
+
+const Field = ({
+ sx,
+ token,
+ chain,
+ amount = 0,
+ theme,
+ amountUSD,
+ routeAmount,
+ routeAmountUSD,
+ highlighted,
+ fullWidth,
+ showSkeletons,
+}: {
+ sx?: any;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ amount?: number | null;
+ amountUSD?: number | null;
+ highlighted?: boolean | null;
+ routeAmount?: number | null;
+ routeAmountUSD?: number | null;
+ fullWidth?: boolean;
+ showSkeletons?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 6,
+ });
+ const fieldContainerStyle = fieldContainerStyles();
+ const amountTextStyle = amountTextStyles(theme);
+ const tokenTextStyle = tokenTextStyles('amount', theme);
+
+ const amountContainerStyle = amountContainerStyles() as CSSProperties;
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+ {!showSkeletons && token && (
+
+
+ ${(routeAmountUSD || amountUSD || amount || 0).toFixed(2)}
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default Field;
diff --git a/src/components/ImageGeneration/Fields/QuoteAmountField.tsx b/src/components/ImageGeneration/Fields/QuoteAmountField.tsx
new file mode 100644
index 000000000..78d0c449b
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/QuoteAmountField.tsx
@@ -0,0 +1,100 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import type { ImageTheme } from '../ImageGeneration.types';
+import {
+ amountContainerStyles,
+ amountTextStyles,
+ fieldContainerStyles,
+ tokenTextStyles,
+} from '../style';
+
+const QuoteAmountField = ({
+ sx,
+ token,
+ chain,
+ amount = 0,
+ extendedHeight,
+ theme,
+ amountUSD,
+ routeAmount,
+ routeAmountUSD,
+ highlighted,
+ fullWidth,
+ showSkeletons,
+}: {
+ sx?: any;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ amount?: number | null;
+ amountUSD?: number | null;
+ highlighted?: boolean | null;
+ routeAmount?: number | null;
+ routeAmountUSD?: number | null;
+ extendedHeight?: boolean;
+ fullWidth?: boolean;
+ showSkeletons?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 6,
+ });
+
+ const fieldContainerStyle = fieldContainerStyles(extendedHeight);
+ const tokenTextStyle = tokenTextStyles('amount', theme);
+ const amountTextStyle = amountTextStyles(theme);
+ const amountContainerStyle = amountContainerStyles() as CSSProperties;
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+ {!showSkeletons && token && (
+
+
+ ${(routeAmountUSD || amountUSD || amount || 0).toFixed(2)}
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default QuoteAmountField;
diff --git a/src/components/ImageGeneration/Fields/QuoteField.tsx b/src/components/ImageGeneration/Fields/QuoteField.tsx
new file mode 100644
index 000000000..80f00b866
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/QuoteField.tsx
@@ -0,0 +1,101 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import { FieldSkeleton } from '../FieldSkeleton';
+import type { ImageTheme } from '../ImageGeneration.types';
+import {
+ amountContainerStyles,
+ amountTextStyles,
+ fieldContainerStyles,
+ tokenTextStyles,
+} from '../style';
+
+const QuoteField = ({
+ sx,
+ token,
+ chain,
+ amount = 0,
+ extendedHeight,
+ theme,
+ amountUSD,
+ routeAmount,
+ routeAmountUSD,
+ highlighted,
+ fullWidth,
+ showSkeletons,
+}: {
+ sx?: any;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ amount: number | null;
+ amountUSD?: number | null;
+ highlighted?: boolean | null;
+ routeAmount?: number | null;
+ routeAmountUSD?: number | null;
+ extendedHeight?: boolean;
+ fullWidth?: boolean;
+ showSkeletons?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 5,
+ });
+
+ const fieldContainerStyle = fieldContainerStyles(extendedHeight);
+ const tokenTextStyle = tokenTextStyles('amount', theme);
+ const amountTextStyle = amountTextStyles(theme);
+ const amountContainerStyle = amountContainerStyles() as CSSProperties;
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+ {!showSkeletons && token ? (
+
+
+ ${(routeAmountUSD || amountUSD || amount || 0).toFixed(2)}
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default QuoteField;
diff --git a/src/components/ImageGeneration/Fields/ReviewField.tsx b/src/components/ImageGeneration/Fields/ReviewField.tsx
new file mode 100644
index 000000000..73a08c822
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/ReviewField.tsx
@@ -0,0 +1,115 @@
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import { FieldSkeleton } from '../FieldSkeleton';
+import type { ImageTheme } from '../ImageGeneration.types';
+
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import {
+ amountTextStyles,
+ fieldContainerStyles,
+ tokenTextStyles,
+} from '../style';
+
+const ReviewField = ({
+ sx,
+ token,
+ chain,
+ amount = 0,
+ amountUSD,
+ routeAmount,
+ routeAmountUSD,
+ highlighted,
+ fullWidth,
+ showSkeletons,
+ theme,
+}: {
+ sx?: any;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ amount?: number | null;
+ amountUSD?: number | null;
+ highlighted?: boolean | null;
+ routeAmount?: number | null;
+ routeAmountUSD?: number | null;
+ extendedHeight?: boolean;
+ fullWidth?: boolean;
+ showSkeletons?: boolean;
+}) => {
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 6,
+ });
+
+ const containerWidth = fullWidth ? 368 : 174; // Width based on fullWidth prop
+ const fieldContainerStyle = fieldContainerStyles();
+ const tokenAmountTextStyle = tokenTextStyles('amount', theme);
+ const tokenSymbolTextStyle = tokenTextStyles('symbol', theme);
+ const amountTextStyle = amountTextStyles(theme);
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+ {!showSkeletons && token ? (
+
+
+ ${(routeAmountUSD || amountUSD || amount || 0).toFixed(2)}
+
+
+ {`${token.symbol} on ${chain?.name}`}
+
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
+export default ReviewField;
diff --git a/src/components/ImageGeneration/Fields/SuccessField.tsx b/src/components/ImageGeneration/Fields/SuccessField.tsx
new file mode 100644
index 000000000..40c99392b
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/SuccessField.tsx
@@ -0,0 +1,76 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import { FieldSkeleton } from '../FieldSkeleton';
+import type { ImageTheme } from '../ImageGeneration.types';
+import {
+ amountContainerStyles,
+ amountTextStyles,
+ fieldContainerStyles,
+} from '../style';
+
+const SuccessField = ({
+ token,
+ chain,
+ amount = 0,
+ extendedHeight,
+ theme,
+ routeAmount,
+}: {
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ amount?: number | null;
+ routeAmount?: number | null;
+ extendedHeight?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 6,
+ });
+
+ const fieldContainerStyle = fieldContainerStyles(extendedHeight);
+ const amountTextStyle = amountTextStyles(theme);
+ const amountContainerStyle = amountContainerStyles() as CSSProperties;
+
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+
+
+
+
+
+ );
+};
+
+export default SuccessField;
diff --git a/src/components/ImageGeneration/Fields/TokenField.tsx b/src/components/ImageGeneration/Fields/TokenField.tsx
new file mode 100644
index 000000000..01e59bf4c
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/TokenField.tsx
@@ -0,0 +1,98 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import type { ImageTheme } from '../ImageGeneration.types';
+import { fieldContainerStyles } from '../style';
+
+const TokenField = ({
+ sx,
+ token,
+ chain,
+ theme,
+ highlighted,
+ fullWidth,
+}: {
+ sx?: any;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ highlighted?: boolean | null;
+ fullWidth?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+
+ const fieldContainerStyle = fieldContainerStyles();
+
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+
+
+ );
+};
+
+export default TokenField;
diff --git a/src/components/ImageGeneration/Fields/WidgetExpandedQuote.tsx b/src/components/ImageGeneration/Fields/WidgetExpandedQuote.tsx
new file mode 100644
index 000000000..521a4ae96
--- /dev/null
+++ b/src/components/ImageGeneration/Fields/WidgetExpandedQuote.tsx
@@ -0,0 +1,103 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import { decimalFormatter } from 'src/utils/formatNumbers';
+import { AvatarBadgeNoMUI } from '../../AvatarBadge/NoMUI/AvatarBadgeNoMUI';
+import { FieldSkeleton } from '../FieldSkeleton';
+import type { ImageTheme } from '../ImageGeneration.types';
+import {
+ amountContainerStyles,
+ amountTextStyles,
+ fieldContainerStyles,
+ tokenTextStyles,
+} from '../style';
+
+const WidgetExpandedQuote = ({
+ sx,
+ token,
+ chain,
+ amount = 0,
+ extendedHeight,
+ theme,
+ amountUSD,
+ routeAmount,
+ routeAmountUSD,
+ highlighted,
+ fullWidth,
+ showSkeletons,
+}: {
+ sx?: any;
+ token?: Token | null;
+ chain?: ExtendedChain | null;
+ theme?: ImageTheme;
+ amount?: number | null;
+ amountUSD?: number | null;
+ highlighted?: boolean | null;
+ routeAmount?: number | null;
+ routeAmountUSD?: number | null;
+ extendedHeight?: boolean;
+ fullWidth?: boolean;
+ showSkeletons?: boolean;
+}) => {
+ // Function to calculate top offset based on conditions
+
+ const formatAmount = decimalFormatter('en', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 6,
+ });
+ const fieldContainerStyle = fieldContainerStyles(
+ extendedHeight,
+ ) as CSSProperties;
+ const tokenTextStyle = tokenTextStyles('amount', theme) as CSSProperties;
+ const amountContainerStyle = amountContainerStyles() as CSSProperties;
+ const amountTextStyle = amountTextStyles(theme);
+
+ return (
+
+
+
+ {token && chain && (
+
+ )}
+
+
+ {formatAmount(routeAmount || amount || 0)}
+
+ {!showSkeletons && token ? (
+
+
+ ${(routeAmountUSD || amountUSD || amount || 0).toFixed(2)}
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default WidgetExpandedQuote;
diff --git a/src/components/ImageGeneration/ImageGeneration.types.ts b/src/components/ImageGeneration/ImageGeneration.types.ts
new file mode 100644
index 000000000..c76b8676f
--- /dev/null
+++ b/src/components/ImageGeneration/ImageGeneration.types.ts
@@ -0,0 +1,3 @@
+export type HighlightedAreas = 'from' | 'to' | 'amount';
+
+export type ImageTheme = 'light' | 'dark';
diff --git a/src/components/ImageGeneration/Labels/ButtonLabel.tsx b/src/components/ImageGeneration/Labels/ButtonLabel.tsx
new file mode 100644
index 000000000..b476bcf21
--- /dev/null
+++ b/src/components/ImageGeneration/Labels/ButtonLabel.tsx
@@ -0,0 +1,46 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+
+import type { CSSProperties } from 'react';
+import type { ImageTheme } from '../ImageGeneration.types';
+
+const ButtonLabel = ({
+ sx,
+ buttonLabel,
+ theme,
+ fullWidth,
+}: {
+ sx?: CSSProperties;
+ theme?: ImageTheme;
+ buttonLabel?: string;
+ fullWidth?: boolean;
+}) => {
+ return (
+ !!buttonLabel && (
+
+ )
+ );
+};
+
+export default ButtonLabel;
diff --git a/src/components/ImageGeneration/Labels/CardContent.tsx b/src/components/ImageGeneration/Labels/CardContent.tsx
new file mode 100644
index 000000000..372ffed26
--- /dev/null
+++ b/src/components/ImageGeneration/Labels/CardContent.tsx
@@ -0,0 +1,34 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+
+import type { CSSProperties } from 'react';
+import type { ImageTheme } from '../ImageGeneration.types';
+
+const CardContent = ({
+ sx,
+ cardContent,
+ theme,
+}: {
+ sx?: CSSProperties;
+ cardContent?: string;
+ theme?: ImageTheme;
+}) => {
+ return (
+ !!cardContent && (
+
+ {cardContent}
+
+ )
+ );
+};
+
+export default CardContent;
diff --git a/src/components/ImageGeneration/Labels/CardTitle.tsx b/src/components/ImageGeneration/Labels/CardTitle.tsx
new file mode 100644
index 000000000..9c39b2cc1
--- /dev/null
+++ b/src/components/ImageGeneration/Labels/CardTitle.tsx
@@ -0,0 +1,34 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+
+import type { CSSProperties } from 'react';
+import type { ImageTheme } from '../ImageGeneration.types';
+
+const CardTitle = ({
+ sx,
+ cardTitle,
+ theme,
+}: {
+ sx?: CSSProperties;
+ cardTitle?: string;
+ theme?: ImageTheme;
+}) => {
+ return (
+ !!cardTitle && (
+
+ {cardTitle}
+
+ )
+ );
+};
+
+export default CardTitle;
diff --git a/src/components/ImageGeneration/Labels/Label.tsx b/src/components/ImageGeneration/Labels/Label.tsx
new file mode 100644
index 000000000..d50fa6dab
--- /dev/null
+++ b/src/components/ImageGeneration/Labels/Label.tsx
@@ -0,0 +1,104 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+
+import type { CSSProperties } from 'react';
+import type { ImageTheme } from '../ImageGeneration.types';
+
+const Label = ({
+ sx,
+ buttonLabel,
+ cardTitle,
+ cardContent,
+ title,
+ theme,
+ fullWidth,
+}: {
+ sx?: CSSProperties;
+ cardTitle?: string;
+ cardContent?: string;
+ theme?: ImageTheme;
+ buttonLabel?: string;
+ title?: string;
+ fullWidth?: boolean;
+}) => {
+ return (
+ <>
+ {!!title && (
+
+ )}
+ {!!cardTitle && (
+
+ {cardTitle}
+
+ )}
+ {!!cardContent && (
+
+ {cardContent}
+
+ )}
+ {!!buttonLabel && (
+
+ )}
+ >
+ );
+};
+
+export default Label;
diff --git a/src/components/ImageGeneration/Labels/Title.tsx b/src/components/ImageGeneration/Labels/Title.tsx
new file mode 100644
index 000000000..f67e9c3d3
--- /dev/null
+++ b/src/components/ImageGeneration/Labels/Title.tsx
@@ -0,0 +1,46 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable @next/next/no-img-element */
+
+import type { CSSProperties } from 'react';
+import type { ImageTheme } from '../ImageGeneration.types';
+
+const Title = ({
+ sx,
+ title,
+ theme,
+ fullWidth,
+}: {
+ sx?: CSSProperties;
+ theme?: ImageTheme;
+ title: string;
+ fullWidth?: boolean;
+}) => {
+ return (
+ <>
+ {!!title && (
+
+ )}
+ >
+ );
+};
+
+export default Title;
diff --git a/src/components/ImageGeneration/WidgetExecutionImage.tsx b/src/components/ImageGeneration/WidgetExecutionImage.tsx
new file mode 100644
index 000000000..653fd19d2
--- /dev/null
+++ b/src/components/ImageGeneration/WidgetExecutionImage.tsx
@@ -0,0 +1,122 @@
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import ReviewField from './Fields/ReviewField';
+import { FieldSkeleton } from './FieldSkeleton';
+import type { HighlightedAreas, ImageTheme } from './ImageGeneration.types';
+import ButtonLabel from './Labels/ButtonLabel';
+import CardContent from './Labels/CardContent';
+import CardTitle from './Labels/CardTitle';
+import Title from './Labels/Title';
+import {
+ contentContainerStyles,
+ contentPositioningStyles,
+ pageStyles,
+} from './style';
+
+const SCALING_FACTOR = 2;
+
+interface WidgetReviewImageProps {
+ fromChain?: ExtendedChain | null;
+ toChain?: ExtendedChain | null;
+ fromToken?: Token | null;
+ toToken?: Token | null;
+ theme?: ImageTheme;
+ isSwap?: boolean;
+ amount?: string | null;
+ width: number;
+ height: number;
+ highlighted?: HighlightedAreas;
+}
+
+const WidgetExecutionImage = ({
+ fromChain,
+ toChain,
+ theme,
+ fromToken,
+ isSwap,
+ toToken,
+ amount,
+ width,
+ height,
+ highlighted,
+}: WidgetReviewImageProps) => {
+ const contentContainerStyle = contentContainerStyles({
+ height,
+ width,
+ scalingFactor: SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const contentPositioningStyle = contentPositioningStyles() as CSSProperties;
+ const pageStyle = pageStyles() as CSSProperties;
+
+ return (
+
+
+ {
+ // pages container -->
+ }
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+export default WidgetExecutionImage;
diff --git a/src/components/ImageGeneration/WidgetQuotesImage.tsx b/src/components/ImageGeneration/WidgetQuotesImage.tsx
new file mode 100644
index 000000000..63b22c3b2
--- /dev/null
+++ b/src/components/ImageGeneration/WidgetQuotesImage.tsx
@@ -0,0 +1,150 @@
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import QuoteAmountField from './Fields/QuoteAmountField';
+import QuoteField from './Fields/QuoteField';
+import TokenField from './Fields/TokenField';
+import type { HighlightedAreas, ImageTheme } from './ImageGeneration.types';
+import Label from './Labels/Label';
+import {
+ contentContainerStyles,
+ contentPositioningStyles,
+ pageStyles,
+} from './style';
+
+const SCALING_FACTOR = 2;
+
+interface WidgetQuoteImageProps {
+ fromChain?: ExtendedChain | null;
+ toChain?: ExtendedChain | null;
+ fromToken?: Token | null;
+ toToken?: Token | null;
+ routeAmount?: number | null;
+ amount?: string | null;
+ isSwap?: boolean;
+ amountUSD?: string | null;
+ width: number;
+ height: number;
+ highlighted?: HighlightedAreas;
+ theme?: ImageTheme | null;
+}
+
+const WidgetQuoteImage = ({
+ fromChain,
+ toChain,
+ fromToken,
+ toToken,
+ theme,
+ amount,
+ isSwap,
+ amountUSD,
+ routeAmount,
+ width,
+ height,
+ highlighted,
+}: WidgetQuoteImageProps) => {
+ const contentContainerStyle = contentContainerStyles({
+ height,
+ width,
+ scalingFactor: SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const contentPositioningStyle = contentPositioningStyles() as CSSProperties;
+ const pageStyle = pageStyles() as CSSProperties;
+
+ return (
+
+
+ {
+ // pages container -->
+ }
+
+
+
+
+ {Array(3)
+ .fill(0)
+ .map((_, index) => (
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default WidgetQuoteImage;
diff --git a/src/components/ImageGeneration/WidgetReviewImage.tsx b/src/components/ImageGeneration/WidgetReviewImage.tsx
new file mode 100644
index 000000000..811538c5a
--- /dev/null
+++ b/src/components/ImageGeneration/WidgetReviewImage.tsx
@@ -0,0 +1,115 @@
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import ReviewField from './Fields/ReviewField';
+import { FieldSkeleton } from './FieldSkeleton';
+import type { HighlightedAreas, ImageTheme } from './ImageGeneration.types';
+import ButtonLabel from './Labels/ButtonLabel';
+import CardTitle from './Labels/CardTitle';
+import Title from './Labels/Title';
+import {
+ contentContainerStyles,
+ contentPositioningStyles,
+ pageStyles,
+} from './style';
+
+const SCALING_FACTOR = 2;
+
+interface WidgetReviewImageProps {
+ fromChain?: ExtendedChain | null;
+ toChain?: ExtendedChain | null;
+ fromToken?: Token | null;
+ toToken?: Token | null;
+ theme?: ImageTheme;
+ isSwap?: boolean;
+ amount?: string | null;
+ width: number;
+ height: number;
+ highlighted?: HighlightedAreas;
+ sx?: CSSProperties;
+}
+
+const WidgetReviewImage = ({
+ fromChain,
+ toChain,
+ theme,
+ fromToken,
+ isSwap,
+ toToken,
+ amount,
+ width,
+ height,
+ highlighted,
+ sx,
+}: WidgetReviewImageProps) => {
+ const contentContainerStyle = contentContainerStyles({
+ height,
+ width,
+ scalingFactor: SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const contentPositioningStyle = contentPositioningStyles() as CSSProperties;
+ const pageStyle = pageStyles() as CSSProperties;
+
+ return (
+
+
+ {
+ // pages container -->
+ }
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default WidgetReviewImage;
diff --git a/src/components/ImageGeneration/WidgetSelectionImage.tsx b/src/components/ImageGeneration/WidgetSelectionImage.tsx
new file mode 100644
index 000000000..31aeeb941
--- /dev/null
+++ b/src/components/ImageGeneration/WidgetSelectionImage.tsx
@@ -0,0 +1,94 @@
+// WidgetImageSSR.tsx
+
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import AmountField from './Fields/AmountField';
+import TokenField from './Fields/TokenField';
+import type { HighlightedAreas, ImageTheme } from './ImageGeneration.types';
+import {
+ contentContainerStyles,
+ contentPositioningStyles,
+ pageStyles,
+} from './style';
+
+const SCALING_FACTOR = 2;
+
+interface WidgetImageSSRProps {
+ fromChain?: ExtendedChain | null;
+ toChain?: ExtendedChain | null;
+ fromToken?: Token | null;
+ toToken?: Token | null;
+ amount?: string | null;
+ amountUSD?: string | null;
+ width: number;
+ height: number;
+ theme?: ImageTheme | null;
+ highlighted?: HighlightedAreas;
+}
+
+const WidgetSelectionImage = ({
+ fromChain,
+ toChain,
+ fromToken,
+ toToken,
+ amount,
+ amountUSD,
+ width,
+ height,
+ theme,
+ highlighted,
+}: WidgetImageSSRProps) => {
+ const contentContainerStyle = contentContainerStyles({
+ height,
+ width,
+ scalingFactor: SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const contentPositioningStyle = contentPositioningStyles() as CSSProperties;
+ const pageStyle = pageStyles() as CSSProperties;
+
+ return (
+
+ );
+};
+
+export default WidgetSelectionImage;
diff --git a/src/components/ImageGeneration/WidgetSuccessImage.tsx b/src/components/ImageGeneration/WidgetSuccessImage.tsx
new file mode 100644
index 000000000..748a07c95
--- /dev/null
+++ b/src/components/ImageGeneration/WidgetSuccessImage.tsx
@@ -0,0 +1,75 @@
+import type { ExtendedChain, Token } from '@lifi/sdk';
+import type { CSSProperties } from 'react';
+import SuccessField from './Fields/SuccessField';
+import { FieldSkeleton } from './FieldSkeleton';
+import type { ImageTheme } from './ImageGeneration.types';
+import Title from './Labels/Title';
+import { contentContainerStyles, contentPositioningStyles } from './style';
+
+const SCALING_FACTOR = 2;
+
+interface WidgetReviewImageProps {
+ toChain?: ExtendedChain | null;
+ toToken?: Token | null;
+ theme?: ImageTheme;
+ isSwap?: boolean;
+ amount?: string | null;
+ width: number;
+ height: number;
+}
+
+const WidgetSuccessImage = ({
+ toChain,
+ theme,
+ isSwap,
+ toToken,
+ amount,
+ width,
+ height,
+}: WidgetReviewImageProps) => {
+ const contentContainerStyle = contentContainerStyles({
+ height,
+ width,
+ scalingFactor: SCALING_FACTOR,
+ }) as CSSProperties;
+
+ const contentPositioningStyle = contentPositioningStyles() as CSSProperties;
+
+ return (
+
+
+ {
+ // pages container -->
+ }
+
+
+
+
+
+
+
+
+ );
+};
+
+export default WidgetSuccessImage;
diff --git a/src/components/ImageGeneration/imageResponseOptions.ts b/src/components/ImageGeneration/imageResponseOptions.ts
new file mode 100644
index 000000000..14dfa8c47
--- /dev/null
+++ b/src/components/ImageGeneration/imageResponseOptions.ts
@@ -0,0 +1,57 @@
+import type { Font } from 'node_modules/next/dist/compiled/@vercel/og/satori';
+
+export const imageResponseOptions = async ({
+ width,
+ height,
+ scalingFactor,
+}: {
+ width: number;
+ height: number;
+ scalingFactor: number;
+}) => {
+ return {
+ headers: {
+ 'Cache-Control': `public, max-age=${60 * 60 * 1000 * 24}, immutable`,
+ },
+ width: width * scalingFactor,
+ height: height * scalingFactor,
+ fonts: await getInterFonts(), // Await properly within the async function
+ };
+};
+
+async function getInterFonts(): Promise {
+ // This is unfortunate but I can't figure out how to load local font files
+ // when deployed to vercel.
+ const [interRegular, interSemiBold, interBold] = await Promise.all([
+ fetch(`https://fonts.cdnfonts.com/s/19795/Inter-Regular.woff`).then((res) =>
+ res.arrayBuffer(),
+ ),
+ fetch(`https://fonts.cdnfonts.com/s/19795/Inter-SemiBold.woff`).then(
+ (res) => res.arrayBuffer(),
+ ),
+ fetch(`https://fonts.cdnfonts.com/s/19795/Inter-Bold.woff`).then((res) =>
+ res.arrayBuffer(),
+ ),
+ ]);
+
+ return [
+ {
+ name: 'Inter',
+ data: interRegular,
+ style: 'normal',
+ weight: 400,
+ },
+ {
+ name: 'Inter',
+ data: interSemiBold,
+ style: 'normal',
+ weight: 600,
+ },
+ {
+ name: 'Inter',
+ data: interBold,
+ style: 'normal',
+ weight: 700,
+ },
+ ];
+}
diff --git a/src/components/ImageGeneration/style.ts b/src/components/ImageGeneration/style.ts
new file mode 100644
index 000000000..ea5e276a5
--- /dev/null
+++ b/src/components/ImageGeneration/style.ts
@@ -0,0 +1,124 @@
+import type { ImageTheme } from './ImageGeneration.types';
+
+interface ContainerStylesProps {
+ width: number;
+ height: number;
+ scalingFactor: number;
+}
+
+export const imageFrameStyles = ({
+ height,
+ width,
+ scalingFactor,
+}: ContainerStylesProps) => {
+ return {
+ position: 'relative',
+ display: 'flex',
+ width: width * scalingFactor,
+ height: height * scalingFactor,
+ };
+};
+
+export const imageStyles = ({
+ height,
+ width,
+ scalingFactor,
+}: ContainerStylesProps) => {
+ return {
+ margin: 'auto',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ objectFit: 'cover',
+ width: width * scalingFactor,
+ height: height * scalingFactor,
+ };
+};
+
+export const contentContainerStyles = ({
+ height,
+ width,
+ scalingFactor,
+}: ContainerStylesProps) => {
+ return {
+ display: 'flex',
+ transformOrigin: 'top left',
+ position: 'relative',
+ transform: `scale(${scalingFactor})`,
+ height,
+ width,
+ };
+};
+
+export const contentPositioningStyles = () => {
+ return {
+ display: 'flex',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ };
+};
+
+export const pageStyles = () => {
+ return {
+ display: 'flex',
+ flexDirection: 'column',
+ margin: '0 24px',
+ };
+};
+
+export const fieldContainerStyles = (extendedHeight?: boolean) => {
+ return {
+ display: 'flex',
+ height: extendedHeight ? 149.6 : 104,
+ borderRadius: '12px',
+ borderWidth: '1px',
+ justifyContent: 'space-between',
+ borderStyle: 'solid',
+ };
+};
+
+export const amountContainerStyles = () => {
+ return {
+ display: 'flex',
+ flexDirection: 'column',
+ marginLeft: '16px',
+ width: '100%',
+ };
+};
+
+export const amountTextStyles = (theme?: ImageTheme) => {
+ return {
+ color: theme === 'dark' ? '#ffffff' : '#000000',
+ margin: 0,
+ fontSize: 24,
+ lineHeight: 1,
+ fontWeight: 600,
+ };
+};
+
+export const tokenTextStyles = (
+ type: 'amount' | 'symbol',
+ theme?: ImageTheme,
+) => {
+ let additionalStyles = {};
+ if (type === 'amount') {
+ additionalStyles = {
+ color: theme === 'dark' ? '#ffffff' : '#747474',
+ };
+ } else if (type === 'symbol') {
+ additionalStyles = {
+ color: theme === 'dark' ? 'rgb(187, 187, 187)' : '#747474',
+ marginLeft: 64,
+ };
+ }
+
+ return {
+ margin: 0,
+ fontSize: 12,
+ fontWeight: 500,
+ marginTop: 10,
+ letterSpacing: '0.00938em',
+ ...additionalStyles,
+ };
+};
diff --git a/src/components/Menus/WalletMenu/WalletCard.style.ts b/src/components/Menus/WalletMenu/WalletCard.style.ts
index 7bd599a5f..82a16b214 100644
--- a/src/components/Menus/WalletMenu/WalletCard.style.ts
+++ b/src/components/Menus/WalletMenu/WalletCard.style.ts
@@ -1,13 +1,10 @@
'use client';
-import { avatarMask32 } from '@/components/Mask.style';
+import { ButtonTransparent } from '@/components/Button';
import type { Breakpoint } from '@mui/material';
-import { alpha } from '@mui/material';
-import { Avatar, Badge, Container } from '@mui/material';
-import { styled } from '@mui/material/styles';
+import { alpha, Avatar, Badge, Container } from '@mui/material';
import type { ButtonProps as MuiButtonProps } from '@mui/material/Button/Button';
-import { getContrastAlphaColor } from '@/utils/colors';
-import { ButtonTransparent } from '@/components/Button';
+import { styled } from '@mui/material/styles';
export const WalletAvatar = styled(Avatar)(({ theme }) => ({
margin: 'auto',
diff --git a/src/components/Menus/WalletMenu/WalletMenu.style.ts b/src/components/Menus/WalletMenu/WalletMenu.style.ts
index ebbaf06ca..836ed4946 100644
--- a/src/components/Menus/WalletMenu/WalletMenu.style.ts
+++ b/src/components/Menus/WalletMenu/WalletMenu.style.ts
@@ -3,9 +3,7 @@
import { ButtonSecondary, ButtonTransparent } from '@/components/Button';
import { avatarMask32 } from '@/components/Mask.style';
import type { Breakpoint, ButtonProps } from '@mui/material';
-import { Box, Drawer } from '@mui/material';
-import { alpha } from '@mui/material';
-import { Avatar, Badge, Container } from '@mui/material';
+import { alpha, Avatar, Badge, Container, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
export interface WalletButtonProps extends ButtonProps {
diff --git a/src/components/Widgets/Widget.style.ts b/src/components/Widgets/Widget.style.ts
index 2c744013b..0c5f3ed84 100644
--- a/src/components/Widgets/Widget.style.ts
+++ b/src/components/Widgets/Widget.style.ts
@@ -38,7 +38,7 @@ export const WidgetWrapper = styled(Box, {
transitionDuration: '.3s',
transitionTimingFunction: 'ease-in-out',
marginTop: 0,
- maxHeight: '100%',
+ maxHeight: welcomeScreenClosed ? '100%' : '50vh',
...(!welcomeScreenClosed && {
cursor: 'pointer',
// add margin-top to widget-wrapper when welcome-screen is closed
@@ -70,7 +70,7 @@ export const WidgetWrapper = styled(Box, {
marginTop: !welcomeScreenClosed ? DEFAULT_WIDGET_TOP_OFFSET_VAR : 0,
[`@media screen and (min-height: 700px)`]: {
// set default widget height
- height: DEFAULT_WIDGET_HEIGHT,
+ height: welcomeScreenClosed ? DEFAULT_WIDGET_HEIGHT : '50vh',
marginTop: !welcomeScreenClosed
? // (mid viewheight - ≈ 2/3 of widget height - navbar height )
`calc( ${DEFAULT_WIDGET_TOP_OFFSET_VAR} - 40px )`
diff --git a/src/components/Widgets/WidgetEvents.tsx b/src/components/Widgets/WidgetEvents.tsx
index 55d745089..b24d8b9be 100644
--- a/src/components/Widgets/WidgetEvents.tsx
+++ b/src/components/Widgets/WidgetEvents.tsx
@@ -281,46 +281,46 @@ export function WidgetEvents() {
}
};
- const onTokenSearch = async ({ value, tokens }: TokenSearchProps) => {
- const lowercaseValue = value?.toLowerCase();
- const { isValid, addressType } = isValidEvmOrSvmAddress(lowercaseValue);
- const SearchNothingFound = tokens?.length > 0 ? false : true;
- const tokenAddress = tokens?.length > 0 ? tokens?.[0]?.address : '';
- const tokenName = tokens?.length > 0 ? tokens?.[0]?.name : '';
- const tokenSymbol = tokens?.length > 0 ? tokens?.[0]?.symbol : '';
- const tokenChainId = tokens?.length > 0 ? tokens?.[0]?.chainId : '';
+ // const onTokenSearch = async ({ value, tokens }: TokenSearchProps) => {
+ // const lowercaseValue = value?.toLowerCase();
+ // const { isValid, addressType } = isValidEvmOrSvmAddress(lowercaseValue);
+ // const SearchNothingFound = tokens?.length > 0 ? false : true;
+ // const tokenAddress = tokens?.length > 0 ? tokens?.[0]?.address : '';
+ // const tokenName = tokens?.length > 0 ? tokens?.[0]?.name : '';
+ // const tokenSymbol = tokens?.length > 0 ? tokens?.[0]?.symbol : '';
+ // const tokenChainId = tokens?.length > 0 ? tokens?.[0]?.chainId : '';
- trackEvent({
- category: TrackingCategory.WidgetEvent,
- action: TrackingAction.OnTokenSearch,
- label: `token_search`,
- data: {
- [TrackingEventParameter.SearchValue]: lowercaseValue,
- [TrackingEventParameter.SearchIsAddress]: isValid,
- [TrackingEventParameter.SearchAddressType]: addressType as string,
- [TrackingEventParameter.SearchNumberOfResult]: tokens?.length,
- [TrackingEventParameter.SearchNothingFound]: SearchNothingFound,
- [TrackingEventParameter.SearchFirstResultAddress]: tokenAddress,
- [TrackingEventParameter.SearchFirstResultName]: tokenName,
- [TrackingEventParameter.SearchFirstResultSymbol]: tokenSymbol,
- [TrackingEventParameter.SearchFirstResultChainId]: tokenChainId,
- },
- });
- };
+ // trackEvent({
+ // category: TrackingCategory.WidgetEvent,
+ // action: TrackingAction.OnTokenSearch,
+ // label: `token_search`,
+ // data: {
+ // [TrackingEventParameter.SearchValue]: lowercaseValue,
+ // [TrackingEventParameter.SearchIsAddress]: isValid,
+ // [TrackingEventParameter.SearchAddressType]: addressType as string,
+ // [TrackingEventParameter.SearchNumberOfResult]: tokens?.length,
+ // [TrackingEventParameter.SearchNothingFound]: SearchNothingFound,
+ // [TrackingEventParameter.SearchFirstResultAddress]: tokenAddress,
+ // [TrackingEventParameter.SearchFirstResultName]: tokenName,
+ // [TrackingEventParameter.SearchFirstResultSymbol]: tokenSymbol,
+ // [TrackingEventParameter.SearchFirstResultChainId]: tokenChainId,
+ // },
+ // });
+ // };
- const onRouteSelected = async ({ route, routes }: RouteSelectedProps) => {
- const position = routes.findIndex((elem: Route) => elem.id === route.id);
- const data = handleRouteEventDetails(route, {
- [TrackingEventParameter.RoutePosition]: position,
- });
+ // const onRouteSelected = async ({ route, routes }: RouteSelectedProps) => {
+ // const position = routes.findIndex((elem: Route) => elem.id === route.id);
+ // const data = handleRouteEventDetails(route, {
+ // [TrackingEventParameter.RoutePosition]: position,
+ // });
- trackEvent({
- category: TrackingCategory.WidgetEvent,
- action: TrackingAction.OnRouteSelected,
- label: `route_selected`,
- data,
- });
- };
+ // trackEvent({
+ // category: TrackingCategory.WidgetEvent,
+ // action: TrackingAction.OnRouteSelected,
+ // label: `route_selected`,
+ // data,
+ // });
+ // };
widgetEvents.on(WidgetEvent.RouteExecutionStarted, onRouteExecutionStarted);
widgetEvents.on(WidgetEvent.RouteExecutionUpdated, onRouteExecutionUpdated);
@@ -344,8 +344,8 @@ export function WidgetEvents() {
WidgetEvent.DestinationChainTokenSelected,
onDestinationChainTokenSelection,
);
- widgetEvents.on(WidgetEvent.RouteSelected, onRouteSelected);
- widgetEvents.on(WidgetEvent.TokenSearch, onTokenSearch);
+ // widgetEvents.on(WidgetEvent.RouteSelected, onRouteSelected);
+ // widgetEvents.on(WidgetEvent.TokenSearch, onTokenSearch);
// widgetEvents.on(WidgetEvent.WidgetExpanded, onWidgetExpanded);
diff --git a/src/utils/image-generation/fetchChainData.ts b/src/utils/image-generation/fetchChainData.ts
new file mode 100644
index 000000000..f1d3a61d4
--- /dev/null
+++ b/src/utils/image-generation/fetchChainData.ts
@@ -0,0 +1,20 @@
+import type { ChainId } from '@lifi/sdk';
+import { ChainType, getChains } from '@lifi/sdk';
+import { getChainById } from '../tokenAndChain';
+
+export async function fetchChainData(chainId: ChainId | null) {
+ if (!chainId) {
+ return null;
+ }
+ try {
+ const formattedChainId =
+ typeof chainId !== 'number' ? parseInt(chainId) : chainId;
+ const chainsData = await getChains({
+ chainTypes: [ChainType.EVM, ChainType.SVM],
+ });
+ return getChainById(chainsData, formattedChainId as ChainId);
+ } catch (error) {
+ console.error(`Error fetching chain data: ${error}`);
+ return null;
+ }
+}
diff --git a/src/utils/image-generation/fetchTokenData.ts b/src/utils/image-generation/fetchTokenData.ts
new file mode 100644
index 000000000..4f8438863
--- /dev/null
+++ b/src/utils/image-generation/fetchTokenData.ts
@@ -0,0 +1,17 @@
+import type { ChainId } from '@lifi/sdk';
+import { getToken } from '@lifi/sdk';
+
+export async function fetchTokenData(
+ chainId: string | null,
+ tokenAddress: string | null,
+) {
+ if (!chainId || !tokenAddress) {
+ return null;
+ }
+ try {
+ return await getToken(parseInt(chainId) as ChainId, tokenAddress);
+ } catch (error) {
+ console.error(`Error fetching token data: ${error}`);
+ return null;
+ }
+}
diff --git a/src/utils/image-generation/helpers.ts b/src/utils/image-generation/helpers.ts
new file mode 100644
index 000000000..a4898c530
--- /dev/null
+++ b/src/utils/image-generation/helpers.ts
@@ -0,0 +1,22 @@
+export const getOffset = (type?: string, extendedHeight?: boolean) => {
+ if (type === 'amount') {
+ return 46;
+ }
+ if (type === 'quote') {
+ if (extendedHeight) {
+ return 56;
+ }
+ return 16;
+ } else {
+ return 46;
+ }
+};
+export const getWidth = (type?: string, fullWidth?: boolean) => {
+ if (type === 'quote') {
+ return 315;
+ } else if (fullWidth) {
+ return 368;
+ } else {
+ return 174;
+ }
+};
diff --git a/src/utils/image-generation/parseSearchParams.ts b/src/utils/image-generation/parseSearchParams.ts
new file mode 100644
index 000000000..d6d5d28b5
--- /dev/null
+++ b/src/utils/image-generation/parseSearchParams.ts
@@ -0,0 +1,14 @@
+export function parseSearchParams(url: string) {
+ const searchParams = new URL(url).searchParams;
+ return {
+ amount: searchParams.get('amount'),
+ fromToken: searchParams.get('fromToken'),
+ fromChainId: searchParams.get('fromChainId'),
+ toToken: searchParams.get('toToken'),
+ toChainId: searchParams.get('toChainId'),
+ highlighted: searchParams.get('highlighted'),
+ theme: searchParams.get('theme'),
+ isSwap: searchParams.get('isSwap'),
+ amountUSD: searchParams.get('amountUSD'),
+ };
+}