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:
- Verify Token Addresses - Always double-check token contract addresses
- Set Slippage Tolerance - Use appropriate slippage settings (1-3% for stablecoins, higher for volatile tokens)
- Check Price Impact - Avoid swaps with high price impact (>5%)
- MEV Protection - 1inch Fusion Plus provides MEV protection, but monitor gas prices
- 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.