Skip to main content

Swapping tokens

Token swapping enables users to exchange one cryptocurrency for another efficiently and securely. With Human Wallet'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 Human Wallet, allowing users to swap tokens across multiple DEXs with optimal pricing and MEV protection.

Demo and source code​

This recipe is also available as an example project.

→ View live demo | → Browse source code

What are we cooking?​

A React application based on Human Wallet quick start example with WAGMI that connects Human Wallet 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​

  • Human Wallet - 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 Human Wallet quick start example​

npx gitpick holonym-foundation/human-wallet-examples/tree/main/human-wallet-wagmi-nextjs
cd human-wallet-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 Human Wallet
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​

Human Wallet 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 Human Wallet 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​

Human Wallet'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.