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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ The widget is responsive, so you can change the width and height in any way you
- chain: default chain (eg `chain=ethereum`). This parameter is required
- from: token to sell, to use the gas token for the chain use 0x0000000000000000000000000000000000000000 (eg `from=0x0000000000000000000000000000000000000000`)
- to: token to buy, to use the gas token for the chain use 0x0000000000000000000000000000000000000000 (eg `to=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48`)
- fromAmount: amount to sell (eg `fromAmount=0.1`). Only one of `fromAmount` or `toAmount` can be specified
- toAmount: amount to buy (eg `toAmount=400`). Only one of `fromAmount` or `toAmount` can be specified
- background: color of the background (eg `background=rgb(10,20,30)`)

Note: only tokens that are part of our token lists are accepted in `from` and `to`, this is to prevent scammers linking to llamaswap with fake tokens loaded (eg a fake USDC)
**Important notes:**
- Only tokens that are part of our token lists are accepted in `from` and `to`, this is to prevent scammers linking to llamaswap with fake tokens loaded (eg a fake USDC)
- If both `fromAmount` and `toAmount` are provided, only `fromAmount` will be used and `toAmount` will be ignored

#### API integration

Expand Down
69 changes: 63 additions & 6 deletions src/components/Aggregator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState, Fragment, useEffect } from 'react';
import { useRef, useState, Fragment, useEffect, useCallback } from 'react';
import { useMutation } from '@tanstack/react-query';
import { useAccount, useSignTypedData, useCapabilities, useSwitchChain, useBytecode } from 'wagmi';
import { useAddRecentTransaction, useConnectModal } from '@rainbow-me/rainbowkit';
Expand Down Expand Up @@ -340,7 +340,56 @@ export function AggregatorContainer() {
// swap input fields and selected aggregator states
const [aggregator, setAggregator] = useState<string | null>(null);
const [isPrivacyEnabled, setIsPrivacyEnabled] = useLocalStorage('llamaswap-isprivacyenabled', false);
const [[amount, amountOut], setAmount] = useState<[number | string, number | string]>(['', '']);
const [[amount, amountOut], setAmountState] = useState<[number | string, number | string]>(['', '']);

// Get router early since it's needed for URL updates
const router = useRouter();

// Wrapper to update both state and URL params
const setAmount = useCallback<React.Dispatch<React.SetStateAction<[string | number, string | number]>>>((newAmounts) => {
setAmountState(newAmounts);

// Also update URL params when amounts change
if (typeof newAmounts === 'function') {
// Handle function updates
setAmountState((prev) => {
const updated = newAmounts(prev);
const [fromAmt, toAmt] = updated;
const query = { ...router.query };

// Clear both first
delete query.fromAmount;
delete query.toAmount;

// Set the appropriate one
if (fromAmt && fromAmt !== '' && fromAmt !== '0') {
query.fromAmount = String(fromAmt);
} else if (toAmt && toAmt !== '' && toAmt !== '0') {
query.toAmount = String(toAmt);
}

router.push({ pathname: router.pathname, query }, undefined, { shallow: true });
return updated;
});
} else {
// Handle direct value updates
const [fromAmt, toAmt] = newAmounts;
const query = { ...router.query };

// Clear both first
delete query.fromAmount;
delete query.toAmount;

// Set the appropriate one
if (fromAmt && fromAmt !== '' && fromAmt !== '0') {
query.fromAmount = String(fromAmt);
} else if (toAmt && toAmt !== '' && toAmt !== '0') {
query.toAmount = String(toAmt);
}

router.push({ pathname: router.pathname, query }, undefined, { shallow: true });
}
}, [router]);

const [slippage, setSlippage] = useLocalStorage('llamaswap-slippage', '0.3');
const [lastOutputValue, setLastOutputValue] = useState<{ aggregator: string; amount: number } | null>(null);
Expand All @@ -366,9 +415,8 @@ export function AggregatorContainer() {

// get selected chain and tokens from URL query params
const routesRef = useRef<HTMLDivElement>(null);
const router = useRouter();

const { toTokenAddress } = useQueryParams();
const { toTokenAddress, fromAmount: fromAmountQuery, toAmount: toAmountQuery } = useQueryParams();
const { selectedChain, selectedToToken, finalSelectedFromToken, finalSelectedToToken } = useSelectedChainAndTokens();
const isValidSelectedChain = selectedChain && chainOnWallet ? selectedChain.id === chainOnWallet.id : false;
const isOutputTrade = amountOut && amountOut !== '';
Expand Down Expand Up @@ -539,7 +587,7 @@ export function AggregatorContainer() {
.push(
{
pathname: '/',
query: { ...router.query, chain: newChain.value, from: zeroAddress, to: undefined }
query: { ...router.query, chain: newChain.value, from: zeroAddress, to: undefined, fromAmount: '10', toAmount: undefined }
},
undefined,
{ shallow: true }
Expand Down Expand Up @@ -574,6 +622,15 @@ export function AggregatorContainer() {
}
}, [router?.query, savedTokens]);

// Initialize amounts from query parameters only
useEffect(() => {
if (fromAmountQuery) {
setAmountState([fromAmountQuery, '']);
} else if (toAmountQuery) {
setAmountState(['', toAmountQuery]);
}
}, [fromAmountQuery, toAmountQuery]);

useEffect(() => {
if (selectedRoute?.amount && aggregator) {
if (
Expand Down Expand Up @@ -1174,7 +1231,7 @@ export function AggregatorContainer() {
router.push(
{
pathname: router.pathname,
query: { ...router.query, to: finalSelectedFromToken?.address, from: finalSelectedToToken?.address }
query: { ...router.query, to: finalSelectedFromToken?.address, from: finalSelectedToToken?.address, fromAmount: amountOut || undefined, toAmount: amount || undefined }
},
undefined,
{ shallow: true }
Expand Down
21 changes: 18 additions & 3 deletions src/hooks/useQueryParams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,27 @@ export function useQueryParams() {
const toToken = urlParams.get('to');
const fromToken = urlParams.get('from');
const chainOnURL = urlParams.get('chain');
const fromAmountOnURL = urlParams.get('fromAmount');
const toAmountOnURL = urlParams.get('toAmount');

const { ...query } = router.query;

const chainName = typeof chainOnURL === 'string' ? chainOnURL.toLowerCase() : 'ethereum';
const fromTokenAddress = typeof fromToken === 'string' ? fromToken.toLowerCase() : null;
const toTokenAddress = typeof toToken === 'string' ? toToken.toLowerCase() : null;

// Only allow one amount parameter to be set - prioritize fromAmount if both exist
const fromAmount = fromAmountOnURL || null;
const toAmount = fromAmountOnURL ? null : toAmountOnURL || null;

// Clean up URL if both amount params exist (keep only fromAmount)
useEffect(() => {
if (router.isReady && fromAmountOnURL && toAmountOnURL) {
const query = { ...router.query };
delete query.toAmount;
router.push({ pathname: router.pathname, query }, undefined, { shallow: true });
}
}, [router, fromAmountOnURL, toAmountOnURL]);

useEffect(() => {
if (router.isReady && !chainOnURL) {
Expand All @@ -30,7 +45,7 @@ export function useQueryParams() {
router.push(
{
pathname: '/',
query: { ...query, chain: chain.value, from: zeroAddress, tab: 'swap' }
query: { ...query, chain: chain.value, from: zeroAddress, fromAmount: "0.1", tab: 'swap' }
},
undefined,
{ shallow: true }
Expand All @@ -40,7 +55,7 @@ export function useQueryParams() {
router.push(
{
pathname: '/',
query: { ...query, chain: 'ethereum', from: zeroAddress, tab: 'swap' }
query: { ...query, chain: 'ethereum', from: zeroAddress, fromAmount: "0.1", tab: 'swap' }
},
undefined,
{ shallow: true }
Expand All @@ -49,5 +64,5 @@ export function useQueryParams() {
}
}, [chainOnURL, chainOnWallet, isConnected, router]);

return { chainName, fromTokenAddress, toTokenAddress };
return { chainName, fromTokenAddress, toTokenAddress, fromAmount, toAmount };
}