IntroductionRecipes

Onramp Funds to Embedded Smart Wallets with Coinbase

This Recipe demonstrates how to integrate the Coinbase Onramp into an app that uses Alchemy Embedded Smart Wallets. It uses the Coinbase Developer Platform and assumes you’ve configured your smart wallet integration using @account-kit/react.

Goal: Let users seamlessly buy crypto (e.g. ETH) via card and fund their smart wallet directly.

Framework: This recipe shows a front-end integration in Next.js. The same component can be dropped into any React app.

Heads-up: Coinbase Onramp only works on mainnet. Running this demo will purchase real USDC on Base. Use a payment method you’re comfortable with.


Prerequisites


Step-by-Step Integration

1. Install Dependencies

$npm install @coinbase/onchainkit

2. Create the Onramp Component

This opens a Coinbase-hosted onramp UI in a popup where the user can complete the transaction.

1// components/on-ramp.tsx
2import { useEffect, useState } from "react";
3import {
4 setupOnrampEventListeners,
5 getOnrampBuyUrl,
6} from "@coinbase/onchainkit/fund";
7import { useSmartAccountClient } from "@account-kit/react";
8import type { SuccessEventData, OnrampError } from "@coinbase/onchainkit/fund";
9
10const CDP_PROJECT_ID = process.env.NEXT_PUBLIC_CDP_PROJECT_ID;
11
12function OnRampPartnerCard() {
13 const { address } = useSmartAccountClient({});
14 const [onrampUrl, setOnrampUrl] = useState<string | null>(null);
15 const [isLoading, setIsLoading] = useState(false);
16 const [error, setError] = useState<string | null>(null);
17 const [isComplete, setIsComplete] = useState(false);
18 const [popupWindow, setPopupWindow] = useState<Window | null>(null);
19
20 useEffect(() => {
21 if (!address || !CDP_PROJECT_ID) return;
22
23 setIsLoading(true);
24 setError(null);
25
26 try {
27 const url = getOnrampBuyUrl({
28 projectId: CDP_PROJECT_ID,
29 addresses: { [address]: ["base"] },
30 assets: ["USDC"], // or ["ETH"]
31 presetFiatAmount: 1,
32 fiatCurrency: "USD",
33 });
34 setOnrampUrl(url);
35 } catch (err) {
36 setError(
37 err instanceof Error ? err.message : "Failed to generate onramp URL",
38 );
39 } finally {
40 setIsLoading(false);
41 }
42 }, [address]);
43
44 useEffect(() => {
45 if (!onrampUrl) return;
46
47 const unsubscribe = setupOnrampEventListeners({
48 onSuccess: (data?: SuccessEventData) => {
49 console.log("Onramp purchase successful:", data);
50 setIsComplete(true);
51 if (popupWindow && !popupWindow.closed) popupWindow.close();
52 },
53 onExit: (err?: OnrampError) => {
54 if (err) setError("Transaction was cancelled or failed");
55 if (popupWindow && !popupWindow.closed) popupWindow.close();
56 },
57 });
58
59 return unsubscribe;
60 }, [onrampUrl, popupWindow]);
61
62 const openOnrampPopup = () => {
63 if (!onrampUrl) return;
64 const popup = window.open(
65 onrampUrl,
66 "coinbase-onramp",
67 "width=500,height=700,scrollbars=yes,resizable=yes,status=yes,location=yes,toolbar=no,menubar=no",
68 );
69 if (popup) setPopupWindow(popup);
70 };
71
72 if (!address) {
73 return (
74 <div className="text-center">
75 <p className="text-gray-600 mb-4">
76 Please connect your wallet to buy crypto.
77 </p>
78 <button
79 disabled
80 className="px-6 py-3 bg-gray-300 text-gray-500 rounded-lg cursor-not-allowed"
81 >
82 Buy Crypto
83 </button>
84 </div>
85 );
86 }
87
88 if (isComplete) {
89 return (
90 <div className="text-center">
91 <div className="text-green-600 mb-4">
92 <svg
93 className="w-12 h-12 mx-auto mb-2"
94 fill="currentColor"
95 viewBox="0 0 20 20"
96 >
97 <path
98 fillRule="evenodd"
99 d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
100 clipRule="evenodd"
101 />
102 </svg>
103 <p className="text-lg font-semibold">Purchase Complete!</p>
104 <p className="text-sm text-gray-600 mb-4">
105 Your transaction has been processed successfully.
106 </p>
107 </div>
108 <button
109 onClick={() => setIsComplete(false)}
110 className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
111 >
112 Buy More Crypto
113 </button>
114 </div>
115 );
116 }
117
118 if (error) {
119 return (
120 <div className="text-center">
121 <p className="text-red-600 mb-4">Error: {error}</p>
122 <button
123 onClick={() => window.location.reload()}
124 className="px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
125 >
126 Retry
127 </button>
128 </div>
129 );
130 }
131
132 return (
133 <div className="text-center">
134 <p className="text-gray-600 mb-4">
135 Purchase cryptocurrency directly to your wallet using Coinbase Onramp.
136 </p>
137 <button
138 onClick={openOnrampPopup}
139 disabled={isLoading || !onrampUrl}
140 className={`px-6 py-3 rounded-lg font-semibold transition-colors ${
141 isLoading || !onrampUrl
142 ? "bg-gray-300 text-gray-500 cursor-not-allowed"
143 : "bg-blue-600 text-white hover:bg-blue-700"
144 }`}
145 >
146 {isLoading ? (
147 <div className="flex items-center gap-2">
148 <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
149 Loading...
150 </div>
151 ) : (
152 "Buy Crypto"
153 )}
154 </button>
155 </div>
156 );
157}
158
159export default OnRampPartnerCard;

3. Add to Your App

1// app/page.tsx
2import OnRampPartnerCard from "./components/on-ramp";
3
4export default function Home() {
5 return (
6 <div className="container mx-auto p-4">
7 {/* Your other components */}
8
9 <div className="bg-white rounded-lg shadow-lg p-6">
10 <h2 className="text-xl font-semibold mb-4">Buy Crypto</h2>
11 <OnRampPartnerCard />
12 </div>
13 </div>
14 );
15}

Best Practices

  • Let users change networks/assets in the Coinbase modal (default here is card ➜ ETH).
  • For user-level analytics you can pass partnerUserId when generating the onramp URL.

Success!

Users can now click “Buy Crypto”, complete their purchase in a popup, and immediately spend the ETH that lands in their smart wallet.

Next Steps

  • Sponsor their first transaction – set up Gas Manager so new users don’t pay gas.

Resources