Skip to main content

Yield

Earning yield on stablecoins and other assets through DeFi protocols like Aave provides users with opportunities to generate passive income while maintaining liquidity. With WaaP's 2PC and 2PC-MPC security, users can safely interact with lending protocols without compromising their private keys. This recipe demonstrates how to integrate Aave V3 lending markets with WaaP, allowing users to supply assets to earn interest and borrow against their collateral.

What are we cooking?

A React application based on WaaP quick start example with WAGMI that connects WaaP to Aave V3 lending markets, enabling users to:

  • Supply assets to earn yield
  • Borrow against supplied collateral
  • Track positions and account health
  • Manage deposits and withdrawals

Key Components

  • WaaP - Non-custodial wallets with 2PC and 2PC-MPC security and policy engine
  • Aave V3 Markets - Lending protocol with supply/borrow functionality
  • Wagmi Integration - Type-safe blockchain interactions
  • Transaction Simulation - Built-in transaction previews

Project Setup

Get started with 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 Aave Dependencies

npm install @aave/react

The @aave/react package provides hooks and utilities for interacting with Aave V3 markets. For more information, see the Aave React SDK documentation.

Configure Aave Client

Create src/lib/aave.ts:

import { AaveClient } from "@aave/react";

export const aaveClient = AaveClient.create();

Setup Providers

Configure your app with the necessary providers in src/lib/providers.tsx:

"use client";

import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AaveProvider } from "@aave/react";
import { config } from "./wagmi";
import { aaveClient } from "./aave";

export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = new QueryClient();

return (
<WagmiProvider config={config}>
<AaveProvider client={aaveClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</AaveProvider>
</WagmiProvider>
);
}

Core Functionality

Supply Assets to Earn Yield

Supply stablecoins or other assets to Aave markets to earn interest:

import { useState } from 'react';
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { parseUnits } from 'viem';
import { supply, evmAddress, chainId } from '@aave/react';

function SupplyAsset() {
const { address } = useAccount();
const [amount, setAmount] = useState('');
const [isSupplying, setIsSupplying] = useState(false);

// Aave V3 USDC market on Sepolia testnet
const marketAddress = '0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951';
const usdcAddress = '0x1c7D4B196Cb0C7B01d743Fbc70B6A3cE8C4C4C4C4';

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

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

const handleSupply = async () => {
if (!amount || parseFloat(amount) <= 0) return;

setIsSupplying(true);
try {
// First, approve USDC spending
await writeContract({
address: usdcAddress,
abi: [{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }]
}],
functionName: 'approve',
args: [marketAddress, parseUnits(amount, 6)] // USDC has 6 decimals
});

// Then supply to Aave
await supply({
market: evmAddress(marketAddress),
amount: {
erc20: {
currency: evmAddress(usdcAddress),
value: parseUnits(amount, 6).toString()
}
},
supplier: evmAddress(address),
chainId: chainId(11155111), // Sepolia testnet
walletClient: window.waap // WaaP as wallet client
});
} catch (error) {
console.error('Supply failed:', error);
} finally {
setIsSupplying(false);
}
};

return (
<div>
<h3>Supply USDC to Earn Yield</h3>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount in USDC"
disabled={isSupplying || isConfirming}
/>
<button
onClick={handleSupply}
disabled={isSupplying || isConfirming || !amount}
>
{isSupplying ? 'Supplying...' : isConfirming ? 'Confirming...' : 'Supply'}
</button>
{isSuccess && <p>Successfully supplied {amount} USDC!</p>}
</div>
);
}

Borrow Against Collateral

Borrow assets using your supplied collateral:

import { borrow } from '@aave/react';

function BorrowAsset() {
const { address } = useAccount();
const [amount, setAmount] = useState('');
const [isBorrowing, setIsBorrowing] = useState(false);

const marketAddress = '0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951';
const usdcAddress = '0x1c7D4B196Cb0C7B01d743Fbc70B6A3cE8C4C4C4C4';

const handleBorrow = async () => {
if (!amount || parseFloat(amount) <= 0) return;

setIsBorrowing(true);
try {
await borrow({
market: evmAddress(marketAddress),
amount: {
erc20: {
currency: evmAddress(usdcAddress),
value: parseUnits(amount, 6).toString()
}
},
borrower: evmAddress(address),
chainId: chainId(1),
walletClient: window.waap
});
} catch (error) {
console.error('Borrow failed:', error);
} finally {
setIsBorrowing(false);
}
};

return (
<div>
<h3>Borrow USDC</h3>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount to borrow"
disabled={isBorrowing}
/>
<button
onClick={handleBorrow}
disabled={isBorrowing || !amount}
>
{isBorrowing ? 'Borrowing...' : 'Borrow'}
</button>
</div>
);
}

Check Account Health

Monitor your account health and available borrowing power:

import { useAccountHealth, useUserSupplies, useUserBorrows } from '@aave/react';

function AccountHealth() {
const { address } = useAccount();
const { data: health, isLoading: healthLoading } = useAccountHealth({
account: address,
chainId: 1
});

const { data: supplies, isLoading: suppliesLoading } = useUserSupplies({
account: address,
chainId: 1
});

const { data: borrows, isLoading: borrowsLoading } = useUserBorrows({
account: address,
chainId: 1
});

if (healthLoading || suppliesLoading || borrowsLoading) {
return <div>Loading account data...</div>;
}

return (
<div>
<h3>Account Overview</h3>
<div>
<p>Health Factor: {health?.healthFactor ? health.healthFactor.toFixed(2) : 'N/A'}</p>
<p>Total Collateral: ${health?.totalCollateralUSD ? health.totalCollateralUSD.toFixed(2) : '0'}</p>
<p>Total Debt: ${health?.totalDebtUSD ? health.totalDebtUSD.toFixed(2) : '0'}</p>
<p>Available to Borrow: ${health?.availableBorrowsUSD ? health.availableBorrowsUSD.toFixed(2) : '0'}</p>
</div>

<div>
<h4>Your Supplies</h4>
{supplies?.map((supply) => (
<div key={supply.currency.address}>
<p>{supply.currency.symbol}: {supply.supplyBalance} (APY: {(supply.supplyAPY * 100).toFixed(2)}%)</p>
</div>
))}
</div>

<div>
<h4>Your Borrows</h4>
{borrows?.map((borrow) => (
<div key={borrow.currency.address}>
<p>{borrow.currency.symbol}: {borrow.borrowBalance} (APY: {(borrow.borrowAPY * 100).toFixed(2)}%)</p>
</div>
))}
</div>
</div>
);
}

Withdraw and Repay

Complete the lending cycle by withdrawing supplied assets and repaying borrowed amounts:

import { withdraw, repay } from '@aave/react';

function ManagePositions() {
const { address } = useAccount();
const [withdrawAmount, setWithdrawAmount] = useState('');
const [repayAmount, setRepayAmount] = useState('');

const handleWithdraw = async (currencyAddress, amount) => {
try {
await withdraw({
market: evmAddress(marketAddress),
amount: {
erc20: {
currency: evmAddress(currencyAddress),
value: parseUnits(amount, 6).toString()
}
},
supplier: evmAddress(address),
chainId: chainId(1),
walletClient: window.waap
});
} catch (error) {
console.error('Withdraw failed:', error);
}
};

const handleRepay = async (currencyAddress, amount) => {
try {
await repay({
market: evmAddress(marketAddress),
amount: {
erc20: {
currency: evmAddress(currencyAddress),
value: parseUnits(amount, 6).toString()
}
},
borrower: evmAddress(address),
chainId: chainId(1),
walletClient: window.waap
});
} catch (error) {
console.error('Repay failed:', error);
}
};

return (
<div>
<h3>Manage Positions</h3>
{/* Withdraw and repay forms */}
</div>
);
}

Market Data and APY

Display current market rates and available assets:

import { useMarkets } from '@aave/react';

function MarketOverview() {
const { data: markets, isLoading } = useMarkets({
chainId: 1
});

if (isLoading) return <div>Loading markets...</div>;

return (
<div>
<h3>Available Markets</h3>
{markets?.map((market) => (
<div key={market.address} className="market-card">
<h4>{market.currency.symbol}</h4>
<p>Supply APY: {(market.supplyAPY * 100).toFixed(2)}%</p>
<p>Borrow APY: {(market.borrowAPY * 100).toFixed(2)}%</p>
<p>Total Supply: {market.totalSupply}</p>
<p>Total Borrow: {market.totalBorrow}</p>
</div>
))}
</div>
);
}

Security Considerations

When earning yield with WaaP and Aave:

  1. Monitor Health Factor - Keep your health factor above 1.0 to avoid liquidation
  2. Diversify Collateral - Don't over-concentrate in a single asset
  3. Understand Risks - DeFi protocols carry smart contract and market risks
  4. Start Small - Begin with small amounts to understand the mechanics
  5. Regular Monitoring - Check your positions regularly, especially during volatile markets

Cross-Chain Yield Opportunities

WaaP supports multiple chains, enabling access to different Aave markets:

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

const aaveMarkets = {
[sepolia.id]: '0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951', // Sepolia
[baseSepolia.id]: '0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951' // Base Sepolia
};

Conclusion

WaaP's integration with Aave V3 provides a secure and user-friendly way to earn yield on stablecoins and other assets. The combination of 2PC and 2PC-MPC security and DeFi protocols enables users to participate in lending markets without the complexity of managing private keys directly. This makes yield farming accessible to both beginners and experienced DeFi users.

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