Guide: Integrating Gasless Crypto-Backed Loans with Gelato
This tutorial provides a complete guide for developers to integrate non-custodial, crypto-backed loans into their applications using Morpho's lending protocol and Gelato's Smart Wallet SDK. This powerful combination allows you to offer your users a seamless, Web2-like experience with features like social logins and gasless transactions, all while leveraging Morpho's robust and efficient onchain infrastructure.
We will walk through the entire process, from initial setup to implementing the core supply and borrow functionalities.
Final Product: By the end of this guide, you'll be able to build a user flow similar to the official Morpho x Gelato Demo.
Architecture Overview
The integration relies on two key components working in concert:
- Morpho Protocol: Provides the core, permissionless lending infrastructure. Morpho's smart contracts handle all the logic for supplying collateral, borrowing assets, managing interest rates, and ensuring protocol solvency.
- Gelato Smart Wallet SDK: Acts as the abstraction layer between your application and the blockchain. It converts traditional wallets (EOAs) into smart accounts, enabling features like one-click social logins, gas fee sponsorship, and batching multiple transactions into a single, atomic operation.
Here is a high-level view of the interaction flow:
┌─────────────────┐
│ End User │
└─────────────────┘
│
│ Interacts with
▼
┌─────────────────┐
│ Your Application│
└─────────────────┘
│
│ Uses
▼
┌─────────────────┐
│ Gelato Smart │
│ Wallet SDK │
└─────────────────┘
│
│ 1. Creates Smart Account & Session
│ 2. Bundles & Sponsors Transactions
▼
┌─────────────────┐
│ Blockchain │
└─────────────────┘
│
│ Executes atomic calls
▼
┌─────────────────┐
│ Morpho Protocol │
│ Contracts │
│ (supply, borrow)│
└─────────────────┘
Part 1: Setup and Configuration
This guide assumes you are building a React application with TypeScript. A correct setup is critical for a stable and secure application.
Step 1: Install Dependencies
First, install all necessary packages from Gelato, Dynamic, and other required libraries.
npm install @gelatonetwork/smartwallet @dynamic-labs/sdk-react-core @dynamic-labs/wagmi-connector @tanstack/react-query viem wagmi
Step 2: Obtain API Keys
You will need two API keys:
-
Gelato API Key: This key is used to sponsor transactions (i.e., pay for your users' gas fees).
- Go to the Gelato Relay App.
- Create a new app, select the required networks, and copy your Sponsor API Key.
-
Dynamic Environment ID: This key connects your application to Dynamic's wallet-as-a-service infrastructure.
- Sign up at the Dynamic Dashboard.
- Create a new project and copy your Environment ID.
Step 3: Define Constants and ABIs
For a clean implementation, define your contract addresses, ABIs, and market parameters in a separate constants file.
// constants.ts
import { base, arbitrum, optimism, polygon, scroll } from "viem/chains";
// 1. Supported Networks
export const SUPPORTED_NETWORKS = {
[base.id]: base,
[arbitrum.id]: arbitrum,
[optimism.id]: optimism,
[polygon.id]: polygon,
[scroll.id]: scroll,
};
// 2. Contract Addresses (Example for Base Mainnet)
export const MORPHO_BLUE_ADDRESS = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb";
export const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
export const CBBTC_ADDRESS = "0x7a5df391d15e85A5994F25f20101438914b4334f"; // Note: Use the correct cbBTC address for your network
// 3. Morpho Market Parameters (Example for cbBTC/USDC market)
// NOTE: For a production app, fetch these dynamically. See the "Get Data" tutorials.
export const CBBTC_USDC_MARKET_PARAMS = {
loanToken: USDC_ADDRESS,
collateralToken: CBBTC_ADDRESS,
oracle: "0x...", // The specific oracle for this market
irm: "0x...", // The specific IRM for this market
lltv: BigInt("860000000000000000"), // 86%
};
// 4. Minimal ABI fragments
export const ERC20_ABI = [
{
"inputs": [{"name":"spender","type":"address"}, {"name":"amount","type":"uint256"}],
"name": "approve",
"outputs": [{"name":"","type":"bool"}],
"stateMutability": "nonpayable",
"type": "function",
},
];
export const MORPHO_BLUE_ABI = [
// supplyCollateral, borrow, repay etc.
// NOTE: A more complete ABI is needed. See official Morpho resources.
];
Step 4: Configure the Context Provider
Wrap your application's root with the GelatoSmartWalletContextProvider
and QueryClientProvider
. This makes the smart wallet client and user session available throughout your component tree.
// In your main App.tsx or a layout component
import { GelatoSmartWalletContextProvider } from "@gelatonetwork/smartwallet";
import { dynamic } from "@gelatonetwork/smartwallet/adapters";
import { wagmi } from "@gelatonetwork/smartwallet/networks";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { http } from "viem";
import { SUPPORTED_NETWORKS } from "./constants";
const queryClient = new QueryClient();
const chains = Object.values(SUPPORTED_NETWORKS);
const transports = chains.reduce((acc, chain) => {
acc[chain.id] = http();
return acc;
}, {} as Record<number, ReturnType<typeof http>>);
const App = ({ children }: { children: React.ReactNode }) => {
return (
<GelatoSmartWalletContextProvider
settings={{
// Specify the smart account implementation
scw: { type: "gelato" },
// Your Gelato Sponsor API Key
apiKey: process.env.NEXT_PUBLIC_GELATO_API_KEY as string,
// Connects to Dynamic for Wallet-as-a-Service
waas: dynamic(process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID as string),
// Standard wagmi configuration for chains and transports
wagmi: wagmi({ chains, transports }),
}}
>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</GelatoSmartWalletContextProvider>
);
};
Part 2: Building a Complete Lending Component
Now, let's build a complete React component that handles user authentication and the core borrow functionality.
// components/MorphoLendingComponent.tsx
import React, { useState } from "react";
import { useGelatoSmartWalletProviderContext } from "@gelatonetwork/smartwallet";
import { encodeFunctionData, parseUnits } from "viem";
import { sponsored } from "@gelatonetwork/smartwallet";
// Import constants and ABIs from your constants file
import {
CBBTC_ADDRESS,
USDC_ADDRESS,
MORPHO_BLUE_ADDRESS,
CBBTC_USDC_MARKET_PARAMS,
ERC20_ABI,
MORPHO_BLUE_ABI,
} from "../constants";
const GELATO_API_KEY = process.env.NEXT_PUBLIC_GELATO_API_KEY as string;
export const MorphoLendingComponent = () => {
const [borrowAmount, setBorrowAmount] = useState("");
const [collateralAmount, setCollateralAmount] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [txHash, setTxHash] = useState<string | null>(null);
const {
client: smartWalletClient,
smartWallet,
connect,
disconnect,
isConnected,
} = useGelatoSmartWalletProviderContext();
const handleBorrow = async () => {
if (!smartWalletClient || !smartWallet || !borrowAmount || !collateralAmount) return;
setLoading(true);
setError(null);
setTxHash(null);
try {
const parsedCollateral = parseUnits(collateralAmount, 8); // cbBTC has 8 decimals
const parsedBorrow = parseUnits(borrowAmount, 6); // USDC has 6 decimals
const calls = [
// 1. Approve Morpho to spend the collateral
{
to: CBBTC_ADDRESS as `0x${string}`,
data: encodeFunctionData({
abi: ERC20_ABI,
functionName: "approve",
args: [MORPHO_BLUE_ADDRESS, parsedCollateral],
}),
},
// 2. Supply collateral to the market
{
to: MORPHO_BLUE_ADDRESS as `0x${string}`,
data: encodeFunctionData({
abi: MORPHO_BLUE_ABI,
functionName: "supplyCollateral",
args: [CBBTC_USDC_MARKET_PARAMS, parsedCollateral, smartWallet.address, "0x"],
}),
},
// 3. Borrow the loan asset
{
to: MORPHO_BLUE_ADDRESS as `0x${string}`,
data: encodeFunctionData({
abi: MORPHO_BLUE_ABI,
functionName: "borrow",
args: [
CBBTC_USDC_MARKET_PARAMS,
parsedBorrow,
BigInt(0), // shares
smartWallet.address, // receiver
smartWallet.address, // onBehalf
],
}),
},
];
// Execute the bundled, sponsored transaction
const response = await smartWalletClient.execute({
payment: sponsored(GELATO_API_KEY),
calls,
});
const receipt = await response.wait();
setTxHash(receipt.id);
} catch (e: any) {
console.error("Borrow failed:", e);
setError(e.message || "An unknown error occurred.");
} finally {
setLoading(false);
}
};
// UI Rendering
if (!isConnected) {
return <button onClick={() => connect()}>Create Wallet / Login</button>;
}
return (
<div>
<h3>Borrow USDC with cbBTC Collateral</h3>
<input
type="text"
placeholder="Collateral Amount (cbBTC)"
value={collateralAmount}
onChange={(e) => setCollateralAmount(e.target.value)}
disabled={loading}
/>
<input
type="text"
placeholder="Borrow Amount (USDC)"
value={borrowAmount}
onChange={(e) => setBorrowAmount(e.target.value)}
disabled={loading}
/>
<button onClick={handleBorrow} disabled={loading}>
{loading ? "Processing..." : "Supply & Borrow"}
</button>
<button onClick={() => disconnect()} disabled={loading}>
Disconnect
</button>
{txHash && <p>Success! Transaction: {txHash}</p>}
{error && <p style={{ color: "red" }}>Error: {error}</p>}
</div>
);
};
Part 3: Production Considerations
Security Best Practices
- Key Management: Never expose private keys or API keys in client-side code. Use secure environment variables.
- Input Validation: Always validate and sanitize user inputs before constructing transactions to prevent injection attacks.
- Slippage Protection: For production applications, especially those involving swaps, it is crucial to implement slippage protection. The Gelato SDK can be combined with DEX aggregators that handle this.
- Dependency Management: Keep all SDKs and libraries updated to their latest stable versions to benefit from security patches.
Common Issues & Troubleshooting
Issue | Solution |
---|---|
Transaction fails silently | Check the browser console for detailed error messages. Common causes include incorrect contract addresses or ABI definitions. |
"Insufficient Allowance" | Ensure the approve call is included in your bundle and the amount is sufficient. |
"Provider Not Found" | Verify that GelatoSmartWalletContextProvider correctly wraps your entire application or component tree. |
Gas Estimation Fails | This often points to an onchain error. Ensure the user's smart wallet is funded (if not sponsored) or that the contract logic is sound. |
Key Resources & Next Steps
You now have the building blocks to integrate a powerful, abstracted lending experience. To learn more, explore these resources:
Live Demo
Try the complete user flow in the official Morpho x Gelato demo application.
View Demo →
Gelato Smart Wallet SDK Docs
Dive deeper into the Gelato SDK's features, including different account types and payment options.
Read Docs →
Morpho Borrow Concepts
Understand the core concepts of Morpho Markets, including LTV, health factor, and liquidations.
Learn More →
Community Support
Join the Morpho Discord to ask questions and get help from the community and core contributors.
Join Discord →