+
+ );
+}
+export default ViewClaimSuccess;
diff --git a/components/apps/peanut/claim/useClaimLinkPeanut.tsx b/components/apps/peanut/claim/useClaimLinkPeanut.tsx
new file mode 100644
index 00000000..e5fffb38
--- /dev/null
+++ b/components/apps/peanut/claim/useClaimLinkPeanut.tsx
@@ -0,0 +1,124 @@
+import React, {createContext, type Dispatch, type SetStateAction, useEffect, useMemo,useState} from 'react';
+import {useUpdateEffect} from '@react-hookz/web';
+import {getLinkDetails} from '@squirrel-labs/peanut-sdk';
+import {scrollToTargetAdjusted} from '@utils/animations';
+import {HEADER_HEIGHT} from '@utils/constants';
+import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
+
+export enum Step {
+ LINKDETAILS = 'link_details',
+ CLAIMSUCCESS = 'claim_success'
+}
+
+export type TClaimLink = {
+ linkDetails: any;
+ set_linkDetails: Dispatch>;
+ currentStep: Step;
+ set_currentStep: Dispatch>;
+ claimTxHash: string;
+ set_claimTxHash: Dispatch>;
+ claimUrl: string;
+ set_claimUrl: Dispatch>;
+};
+
+const defaultProps: TClaimLink = {
+ linkDetails: {},
+ set_linkDetails: (): void => undefined,
+ currentStep: Step.LINKDETAILS,
+ set_currentStep: (): void => undefined,
+ claimTxHash: '',
+ set_claimTxHash: (): void => undefined,
+ claimUrl: '',
+ set_claimUrl: (): void => undefined
+};
+
+const ClaimLinkPeanutContext = createContext(defaultProps);
+export const ClaimLinkPeanutContextApp = ({children}: {children: React.ReactElement}): React.ReactElement => {
+ const {address, isActive, isWalletSafe, isWalletLedger, onConnect} = useWeb3();
+ const [currentStep, set_currentStep] = useState(Step.LINKDETAILS);
+ const [linkDetails, set_linkDetails] = useState({});
+ const [claimTxHash, set_claimTxHash] = useState('');
+ const [claimUrl, set_claimUrl] = useState('');
+
+ /**********************************************************************************************
+ ** This effect is used to directly ask the user to connect its wallet if it's not connected
+ **********************************************************************************************/
+ useEffect((): void => {
+ if (!isActive && !address) {
+ onConnect();
+ return;
+ }
+ }, [address, isActive, onConnect]);
+
+ /**********************************************************************************************
+ ** This effect is used to handle some UI transitions and sections jumps. Once the current step
+ ** changes, we need to scroll to the correct section.
+ ** This effect is ignored on mount but will be triggered on every update to set the correct
+ ** scroll position.
+ **********************************************************************************************/
+ useUpdateEffect((): void => {
+ setTimeout((): void => {
+ let currentStepContainer;
+ const scalooor = document?.getElementById('scalooor');
+
+ if (currentStep === Step.LINKDETAILS) {
+ currentStepContainer = document?.getElementById('linkDetails');
+ } else if (currentStep === Step.CLAIMSUCCESS) {
+ currentStepContainer = document?.getElementById('claimSuccess');
+ }
+ const currentElementHeight = currentStepContainer?.offsetHeight;
+ if (scalooor?.style) {
+ scalooor.style.height = `calc(100vh - ${currentElementHeight}px - ${HEADER_HEIGHT}px + 36px)`;
+ }
+ if (currentStepContainer) {
+ scrollToTargetAdjusted(currentStepContainer);
+ }
+ }, 0);
+ }, [currentStep, isWalletLedger, isWalletSafe]);
+
+ useEffect(() => {
+ if (claimUrl) {
+ peanutGetLinkDetails({claimUrl});
+ }
+ }, [claimUrl]);
+
+ /**********************************************************************************************
+ ** This function is used to get the details of the link (amount, token, chain).
+ **********************************************************************************************/
+ async function peanutGetLinkDetails({claimUrl}: {claimUrl: string}): Promise {
+ try {
+ const linkDetails = await getLinkDetails({
+ link: claimUrl
+ });
+ console.log('linkDetails', linkDetails);
+ set_linkDetails(linkDetails);
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ const contextValue = useMemo(
+ (): TClaimLink => ({
+ currentStep,
+ set_currentStep,
+ linkDetails,
+ set_linkDetails,
+ claimTxHash,
+ set_claimTxHash,
+ claimUrl,
+ set_claimUrl
+ }),
+ [currentStep, set_currentStep, linkDetails, set_linkDetails, claimTxHash, set_claimTxHash]
+ );
+
+ return (
+
+
+ {children}
+
+
+
+ );
+};
+
+export const useClaimLinkPeanut = (): TClaimLink => React.useContext(ClaimLinkPeanutContext);
diff --git a/components/apps/peanut/create/1.ViewChainToSend.tsx b/components/apps/peanut/create/1.ViewChainToSend.tsx
new file mode 100644
index 00000000..dbc37ff2
--- /dev/null
+++ b/components/apps/peanut/create/1.ViewChainToSend.tsx
@@ -0,0 +1,40 @@
+import {NetworkSelector} from 'components/common/HeaderElements';
+import {Button} from '@yearn-finance/web-lib/components/Button';
+import ViewSectionHeading from '@common/ViewSectionHeading';
+
+import type {ReactElement} from 'react';
+
+function ViewChainToSend({onProceed}: {onProceed: VoidFunction}): ReactElement {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+export default ViewChainToSend;
diff --git a/components/apps/peanut/create/2.ViewTokenToSend.tsx b/components/apps/peanut/create/2.ViewTokenToSend.tsx
new file mode 100644
index 00000000..a1e81f10
--- /dev/null
+++ b/components/apps/peanut/create/2.ViewTokenToSend.tsx
@@ -0,0 +1,142 @@
+import React, {useState} from 'react';
+import ComboboxAddressInput from 'components/common/ComboboxAddressInput';
+import {useTokenList} from 'contexts/useTokenList';
+import {Step} from '@disperse/useDisperse';
+import {useDeepCompareEffect, useUpdateEffect} from '@react-hookz/web';
+import {Button} from '@yearn-finance/web-lib/components/Button';
+import {useChainID} from '@yearn-finance/web-lib/hooks/useChainID';
+import {isZeroAddress, toAddress} from '@yearn-finance/web-lib/utils/address';
+import {ETH_TOKEN_ADDRESS, ZERO_ADDRESS} from '@yearn-finance/web-lib/utils/constants';
+import {getNetwork} from '@yearn-finance/web-lib/utils/wagmi/utils';
+import ViewSectionHeading from '@common/ViewSectionHeading';
+
+import {useCreateLinkPeanut} from './useCreateLinkPeanut';
+
+import type {ReactElement} from 'react';
+import type {TDict} from '@yearn-finance/web-lib/types';
+import type {TToken} from '@utils/types/types';
+
+function ViewTokenToSend({onProceed}: {onProceed: VoidFunction}): ReactElement {
+ const {safeChainID} = useChainID();
+ const {currentStep, tokenToSend, set_tokenToSend} = useCreateLinkPeanut();
+ const {tokenList} = useTokenList();
+ const [localTokenToSend, set_localTokenToSend] = useState(ETH_TOKEN_ADDRESS);
+ const [isValidTokenToReceive, set_isValidTokenToReceive] = useState(true);
+ const [possibleTokenToReceive, set_possibleTokenToReceive] = useState>({});
+
+ /* 🔵 - Yearn Finance **************************************************************************
+ ** On mount, fetch the token list from the tokenlistooor repo for the cowswap token list, which
+ ** will be used to populate the tokenToDisperse token combobox.
+ ** Only the tokens in that list will be displayed as possible destinations.
+ **********************************************************************************************/
+ useDeepCompareEffect((): void => {
+ const possibleDestinationsTokens: TDict = {};
+ const {wrappedToken} = getNetwork(safeChainID).contracts;
+ if (wrappedToken) {
+ possibleDestinationsTokens[ETH_TOKEN_ADDRESS] = {
+ address: ETH_TOKEN_ADDRESS,
+ chainID: safeChainID,
+ name: wrappedToken.coinName,
+ symbol: wrappedToken.coinSymbol,
+ decimals: wrappedToken.decimals,
+ logoURI: `${process.env.SMOL_ASSETS_URL}/token/${safeChainID}/${ETH_TOKEN_ADDRESS}/logo-128.png`
+ };
+ }
+ for (const eachToken of Object.values(tokenList)) {
+ if (eachToken.chainID === safeChainID) {
+ possibleDestinationsTokens[toAddress(eachToken.address)] = eachToken;
+ }
+ }
+ set_possibleTokenToReceive(possibleDestinationsTokens);
+ }, [tokenList, safeChainID]);
+
+ /* 🔵 - Yearn Finance **************************************************************************
+ ** When the tokenToDisperse token changes, check if it is a valid tokenToDisperse token. The check is
+ ** trivial as we only check if the address is valid.
+ **********************************************************************************************/
+ useUpdateEffect((): void => {
+ set_isValidTokenToReceive('undetermined');
+ if (!isZeroAddress(toAddress(localTokenToSend))) {
+ set_isValidTokenToReceive(true);
+ }
+ }, [tokenToSend]);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ViewTokenToSend;
diff --git a/components/apps/peanut/create/3.ViewAmountToSend.tsx b/components/apps/peanut/create/3.ViewAmountToSend.tsx
new file mode 100644
index 00000000..b4a57cd3
--- /dev/null
+++ b/components/apps/peanut/create/3.ViewAmountToSend.tsx
@@ -0,0 +1,232 @@
+import React, {memo, useCallback, useMemo, useState} from 'react';
+import {useWallet} from 'contexts/useWallet';
+import {handleInputChangeEventValue} from 'utils/handleInputChangeEventValue';
+import {IconSpinner} from '@icons/IconSpinner';
+import {CHAIN_DETAILS, getLinksFromTx, getRandomString, prepareTxs} from '@squirrel-labs/peanut-sdk';
+import {prepareSendTransaction, sendTransaction, waitForTransaction} from '@wagmi/core';
+import {Button} from '@yearn-finance/web-lib/components/Button';
+import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
+import {useChainID} from '@yearn-finance/web-lib/hooks/useChainID';
+import {isZeroAddress, toAddress} from '@yearn-finance/web-lib/utils/address';
+import {formatAmount} from '@yearn-finance/web-lib/utils/format.number';
+
+import {useCreateLinkPeanut} from './useCreateLinkPeanut';
+
+import type {ChangeEvent, ReactElement} from 'react';
+import type {TNormalizedBN} from '@yearn-finance/web-lib/utils/format.bigNumber';
+import type {TToken} from '@utils/types/types';
+
+function AmountToSendInput({
+ token,
+ amount,
+ onChange
+}: {
+ token: TToken | undefined;
+ amount: TNormalizedBN | undefined;
+ onChange: (amount: TNormalizedBN) => void;
+}): ReactElement {
+ /**********************************************************************************************
+ ** onInputChange is triggered when the user is typing in the input field. It updates the
+ ** amount in the state and triggers the debounced retrieval of the quote from the Cowswap API.
+ ** It is set as callback to avoid unnecessary re-renders.
+ **********************************************************************************************/
+ const onInputChange = useCallback(
+ (e: ChangeEvent): void => {
+ onChange(handleInputChangeEventValue(e, token?.decimals || 18));
+ },
+ [onChange, token?.decimals]
+ );
+
+ return (
+