Skip to main content

Swapping tokens

Token swapping enables users to exchange one cryptocurrency for another efficiently and securely. With WaaP's 2PC and 2PC-MPC security, users can safely interact with DEX aggregators like 1inch without compromising their private keys. This recipe demonstrates how to integrate 1inch Fusion Plus API with WaaP, allowing users to swap tokens across multiple DEXs with optimal pricing and MEV protection.

What are we cooking?

A React application based on WaaP quick start example with WAGMI that connects WaaP to 1inch Fusion Plus, enabling users to:

  • Swap tokens across multiple DEXs
  • Get optimal pricing and routing
  • Benefit from MEV protection
  • Track swap history and gas optimization

Key Components

  • WaaP - Non-custodial wallets with 2PC and 2PC-MPC security and policy engine
  • 1inch Fusion Plus - DEX aggregator with MEV protection and optimal routing
  • Wagmi Integration - Type-safe blockchain interactions
  • Cross-chain Support - Swap tokens across multiple networks

Project Setup

Get started with a WaaP quick start example

npx gitpick holonym-foundation/waap-examples/tree/main/waap-wagmi-nextjs
cd waap-wagmi-nextjs
npm install
npm run dev

Install 1inch Dependencies

npm install @1inch/fusion-sdk @1inch/limit-order-protocol-utils

The 1inch Fusion SDK provides MEV-protected swaps with optimal routing. For more information, see the 1inch Fusion Plus documentation.

Configure 1inch Client

Create src/lib/1inch.ts:

import { FusionSDK } from '@1inch/fusion-sdk';

const fusionSDK = new FusionSDK({
url: 'https://api.1inch.dev/fusion',
network: 1, // Ethereum mainnet
blockchainProvider: {
rpcUrl: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY',
},
});

export { fusionSDK };

Core Functionality

Get Quote for Token Swap

Get the best quote for swapping tokens:

import { useState, useEffect } from 'react';
import { useAccount } from 'wagmi';
import { fusionSDK } from '../lib/1inch';

function SwapQuote() {
const { address } = useAccount();
const [fromToken, setFromToken] = useState('0x1c7D4B196Cb0C7B01d743Fbc70B6A3cE8C4C4C4C4'); // USDC on Sepolia
const [toToken, setToToken] = useState('0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357'); // DAI on Sepolia
const [amount, setAmount] = useState('');
const [quote, setQuote] = useState(null);
const [loading, setLoading] = useState(false);

const getQuote = async () => {
if (!amount || !address) return;

setLoading(true);
try {
const quoteData = await fusionSDK.getQuote({
fromTokenAddress: fromToken,
toTokenAddress: toToken,
amount: (parseFloat(amount) * Math.pow(10, 6)).toString(), // USDC has 6 decimals
walletAddress: address,
});

setQuote(quoteData);
} catch (error) {
console.error('Failed to get quote:', error);
} finally {
setLoading(false);
}
};

useEffect(() => {
if (amount && address) {
getQuote();
}
}, [amount, fromToken, toToken, address]);

return (
<div>
<h3>Get Swap Quote</h3>
<div>
<input
type="text"
value={fromToken}
onChange={(e) => setFromToken(e.target.value)}
placeholder="From token address"
/>
<input
type="text"
value={toToken}
onChange={(e) => setToToken(e.target.value)}
placeholder="To token address"
/>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount to swap"
/>
</div>

{loading && <p>Getting quote...</p>}

{quote && (
<div>
<p>From: {quote.fromToken.symbol} {amount}</p>
<p>To: {quote.toToken.symbol} {quote.toAmount}</p>
<p>Price Impact: {quote.priceImpact}%</p>
<p>Gas Fee: {quote.gasFee} ETH</p>
</div>
)}
</div>
);
}

Execute Token Swap

Execute the swap using 1inch Fusion Plus:

import { useState } from 'react';
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { fusionSDK } from '../lib/1inch';

function ExecuteSwap() {
const { address } = useAccount();
const [swapData, setSwapData] = useState(null);
const [isSwapping, setIsSwapping] = useState(false);

const { writeContract, data: hash } = useWriteContract();

const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash,
});

const executeSwap = async (fromToken, toToken, amount) => {
if (!address) return;

setIsSwapping(true);
try {
// Get the swap quote
const quote = await fusionSDK.getQuote({
fromTokenAddress: fromToken,
toTokenAddress: toToken,
amount: (parseFloat(amount) * Math.pow(10, 6)).toString(),
walletAddress: address,
});

// Get the swap transaction data
const swapTransaction = await fusionSDK.getSwapTransaction({
fromTokenAddress: fromToken,
toTokenAddress: toToken,
amount: (parseFloat(amount) * Math.pow(10, 6)).toString(),
walletAddress: address,
slippage: 1, // 1% slippage tolerance
});

// Execute the swap using WaaP
await writeContract({
address: swapTransaction.to,
abi: swapTransaction.data.abi,
functionName: swapTransaction.data.functionName,
args: swapTransaction.data.args,
value: swapTransaction.value,
gas: swapTransaction.gas,
});

setSwapData(quote);
} catch (error) {
console.error('Swap failed:', error);
} finally {
setIsSwapping(false);
}
};

return (
<div>
<h3>Execute Swap</h3>
<button
onClick={() => executeSwap(
'0x1c7D4B196Cb0C7B01d743Fbc70B6A3cE8C4C4C4C4', // USDC on Sepolia
'0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357', // DAI on Sepolia
'100'
)}
disabled={isSwapping || isConfirming}
>
{isSwapping ? 'Swapping...' : isConfirming ? 'Confirming...' : 'Swap USDC to DAI'}
</button>

{isSuccess && swapData && (
<div>
<p>Swap successful!</p>
<p>Received: {swapData.toAmount} {swapData.toToken.symbol}</p>
</div>
)}
</div>
);
}

Token Selection Interface

Create a user-friendly token selection interface:

import { useState, useEffect } from 'react';

const POPULAR_TOKENS = {
'0x1c7D4B196Cb0C7B01d743Fbc70B6A3cE8C4C4C4C4': { symbol: 'USDC', name: 'USD Coin', decimals: 6 },
'0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357': { symbol: 'DAI', name: 'Dai Stablecoin', decimals: 18 },
'0x7169D38820dfd117C3FA1f22a697dBA58d90BA06': { symbol: 'USDT', name: 'Tether USD', decimals: 6 },
'0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14': { symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },
};

function TokenSelector({ selectedToken, onTokenSelect, label }) {
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState('');

const filteredTokens = Object.entries(POPULAR_TOKENS).filter(([address, token]) =>
token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) ||
token.name.toLowerCase().includes(searchTerm.toLowerCase())
);

return (
<div className="relative">
<label className="block text-sm font-medium mb-2">{label}</label>
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full p-3 border border-gray-300 rounded-lg text-left flex justify-between items-center"
>
<span>
{selectedToken ? POPULAR_TOKENS[selectedToken]?.symbol : 'Select token'}
</span>
<span></span>
</button>

{isOpen && (
<div className="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg">
<input
type="text"
placeholder="Search tokens..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full p-2 border-b border-gray-200"
/>
<div className="max-h-60 overflow-y-auto">
{filteredTokens.map(([address, token]) => (
<button
key={address}
onClick={() => {
onTokenSelect(address);
setIsOpen(false);
}}
className="w-full p-3 text-left hover:bg-gray-100 flex justify-between items-center"
>
<div>
<div className="font-medium">{token.symbol}</div>
<div className="text-sm text-gray-500">{token.name}</div>
</div>
<div className="text-xs text-gray-400">{address.slice(0, 6)}...{address.slice(-4)}</div>
</button>
))}
</div>
</div>
)}
</div>
);
}

Complete Swap Interface

Combine all components into a complete swap interface:

import { useState } from 'react';
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { fusionSDK } from '../lib/1inch';
import { TokenSelector } from './TokenSelector';

function SwapInterface() {
const { address } = useAccount();
const [fromToken, setFromToken] = useState('0x1c7D4B196Cb0C7B01d743Fbc70B6A3cE8C4C4C4C4');
const [toToken, setToToken] = useState('0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357');
const [amount, setAmount] = useState('');
const [quote, setQuote] = useState(null);
const [loading, setLoading] = useState(false);

const { writeContract, data: hash } = useWriteContract();

const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash,
});

const getQuote = async () => {
if (!amount || !address || fromToken === toToken) return;

setLoading(true);
try {
const quoteData = await fusionSDK.getQuote({
fromTokenAddress: fromToken,
toTokenAddress: toToken,
amount: (parseFloat(amount) * Math.pow(10, 6)).toString(),
walletAddress: address,
});

setQuote(quoteData);
} catch (error) {
console.error('Failed to get quote:', error);
} finally {
setLoading(false);
}
};

const executeSwap = async () => {
if (!quote || !address) return;

try {
const swapTransaction = await fusionSDK.getSwapTransaction({
fromTokenAddress: fromToken,
toTokenAddress: toToken,
amount: (parseFloat(amount) * Math.pow(10, 6)).toString(),
walletAddress: address,
slippage: 1,
});

await writeContract({
address: swapTransaction.to,
abi: swapTransaction.data.abi,
functionName: swapTransaction.data.functionName,
args: swapTransaction.data.args,
value: swapTransaction.value,
gas: swapTransaction.gas,
});
} catch (error) {
console.error('Swap failed:', error);
}
};

return (
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
<h2 className="text-2xl font-bold mb-6">Swap Tokens</h2>

<div className="space-y-4">
<TokenSelector
selectedToken={fromToken}
onTokenSelect={setFromToken}
label="From"
/>

<div>
<label className="block text-sm font-medium mb-2">Amount</label>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.0"
className="w-full p-3 border border-gray-300 rounded-lg"
/>
</div>

<TokenSelector
selectedToken={toToken}
onTokenSelect={setToToken}
label="To"
/>

<button
onClick={getQuote}
disabled={!amount || fromToken === toToken}
className="w-full py-3 bg-blue-600 text-white rounded-lg disabled:bg-gray-400"
>
Get Quote
</button>

{loading && <p className="text-center">Getting best quote...</p>}

{quote && (
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="font-semibold mb-2">Quote Details</h3>
<div className="space-y-1 text-sm">
<p>You will receive: {quote.toAmount} {quote.toToken.symbol}</p>
<p>Price impact: {quote.priceImpact}%</p>
<p>Gas fee: {quote.gasFee} ETH</p>
<p>Route: {quote.protocols?.join(' → ')}</p>
</div>

<button
onClick={executeSwap}
disabled={isConfirming}
className="w-full mt-4 py-3 bg-green-600 text-white rounded-lg disabled:bg-gray-400"
>
{isConfirming ? 'Confirming...' : 'Swap'}
</button>
</div>
)}

{isSuccess && (
<div className="p-4 bg-green-50 text-green-800 rounded-lg">
Swap completed successfully!
</div>
)}
</div>
</div>
);
}

Cross-Chain Swapping

WaaP supports multiple chains, enabling cross-chain token swaps:

import { sepolia, baseSepolia } from 'wagmi/chains';

const CHAIN_CONFIGS = {
[sepolia.id]: {
name: 'Sepolia',
fusionSDK: new FusionSDK({
url: 'https://api.1inch.dev/fusion',
network: 11155111,
blockchainProvider: { rpcUrl: 'https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY' }
})
},
[baseSepolia.id]: {
name: 'Base Sepolia',
fusionSDK: new FusionSDK({
url: 'https://api.1inch.dev/fusion',
network: 84532,
blockchainProvider: { rpcUrl: 'https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY' }
})
}
};

function CrossChainSwap() {
const [selectedChain, setSelectedChain] = useState(sepolia.id);

const currentConfig = CHAIN_CONFIGS[selectedChain];

return (
<div>
<h3>Cross-Chain Token Swap</h3>
<div>
{Object.entries(CHAIN_CONFIGS).map(([chainId, config]) => (
<button
key={chainId}
onClick={() => setSelectedChain(Number(chainId))}
className={`p-2 m-1 rounded ${
selectedChain === Number(chainId) ? 'bg-blue-600 text-white' : 'bg-gray-200'
}`}
>
{config.name}
</button>
))}
</div>
<p>Current chain: {currentConfig.name}</p>
{/* Swap interface using currentConfig.fusionSDK */}
</div>
);
}

Security Considerations

When swapping tokens with WaaP and 1inch:

  1. Verify Token Addresses - Always double-check token contract addresses
  2. Set Slippage Tolerance - Use appropriate slippage settings (1-3% for stablecoins, higher for volatile tokens)
  3. Check Price Impact - Avoid swaps with high price impact (>5%)
  4. MEV Protection - 1inch Fusion Plus provides MEV protection, but monitor gas prices
  5. Test with Small Amounts - Start with small amounts to test the integration

Advanced Features

Swap History

Track user's swap history:

function SwapHistory() {
const [swaps, setSwaps] = useState([]);

const loadSwapHistory = async (userAddress) => {
// Load from localStorage or API
const history = JSON.parse(localStorage.getItem(`swaps_${userAddress}`) || '[]');
setSwaps(history);
};

const addSwapToHistory = (swapData) => {
const newSwap = {
id: Date.now(),
timestamp: new Date().toISOString(),
fromToken: swapData.fromToken.symbol,
toToken: swapData.toToken.symbol,
fromAmount: swapData.fromAmount,
toAmount: swapData.toAmount,
txHash: swapData.txHash
};

const updatedHistory = [newSwap, ...swaps];
setSwaps(updatedHistory);
localStorage.setItem(`swaps_${userAddress}`, JSON.stringify(updatedHistory));
};

return (
<div>
<h3>Swap History</h3>
{swaps.map((swap) => (
<div key={swap.id} className="p-3 border-b">
<p>{swap.fromAmount} {swap.fromToken}{swap.toAmount} {swap.toToken}</p>
<p className="text-sm text-gray-500">{new Date(swap.timestamp).toLocaleString()}</p>
</div>
))}
</div>
);
}

Conclusion

WaaP's integration with 1inch Fusion Plus provides a secure and efficient way to swap tokens across multiple DEXs. The combination of 2PC and 2PC-MPC security with MEV protection and optimal routing enables users to execute trades with confidence and minimal risk. This makes token swapping accessible to both beginners and experienced DeFi users.

For more advanced features and integrations, refer to our Methods guide and Customization guide.