@coinbase/create-cdp-app 0.0.36 → 0.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +22 -9
  2. package/dist/index.js +168 -46
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/template-nextjs/README.md +3 -2
  6. package/template-nextjs/env.example +2 -0
  7. package/template-nextjs/package.json +3 -1
  8. package/template-nextjs/public/sol.svg +13 -0
  9. package/template-nextjs/src/app/globals.css +6 -0
  10. package/template-nextjs/src/components/Providers.tsx +32 -14
  11. package/template-nextjs/src/components/SignedInScreen.tsx +63 -15
  12. package/template-nextjs/src/components/SignedInScreenWithOnramp.tsx +22 -3
  13. package/template-nextjs/src/components/SolanaTransaction.tsx +157 -0
  14. package/template-nextjs/src/components/UserBalance.tsx +5 -3
  15. package/template-react/README.md +2 -1
  16. package/template-react/env.example +3 -0
  17. package/template-react/package.json +3 -1
  18. package/template-react/public/sol.svg +13 -0
  19. package/template-react/src/Header.tsx +20 -8
  20. package/template-react/src/SignedInScreen.tsx +66 -13
  21. package/template-react/src/SolanaTransaction.tsx +158 -0
  22. package/template-react/src/UserBalance.tsx +29 -10
  23. package/template-react/src/config.ts +25 -11
  24. package/template-react/src/index.css +6 -0
  25. package/template-react/src/main.tsx +2 -2
  26. package/template-react-native/App.tsx +79 -62
  27. package/template-react-native/EOATransaction.tsx +35 -22
  28. package/template-react-native/SmartAccountTransaction.tsx +9 -9
  29. package/template-react-native/components/SignInForm.tsx +433 -0
  30. package/template-react-native/types.ts +0 -22
  31. package/template-react-native/components/SignInModal.tsx +0 -342
@@ -343,6 +343,12 @@ header .wallet-address {
343
343
  margin: 0;
344
344
  }
345
345
 
346
+ .transaction-result {
347
+ display: flex;
348
+ flex-direction: column;
349
+ align-items: center;
350
+ }
351
+
346
352
  @media (min-width: 540px) {
347
353
  .header-inner {
348
354
  flex-direction: row;
@@ -3,13 +3,13 @@ import { StrictMode } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
4
 
5
5
  import App from "./App.tsx";
6
- import { APP_CONFIG, CDP_CONFIG } from "./config.ts";
6
+ import { CDP_CONFIG } from "./config.ts";
7
7
  import { theme } from "./theme.ts";
8
8
  import "./index.css";
9
9
 
10
10
  createRoot(document.getElementById("root")!).render(
11
11
  <StrictMode>
12
- <CDPReactProvider config={CDP_CONFIG} app={APP_CONFIG} theme={theme}>
12
+ <CDPReactProvider config={CDP_CONFIG} theme={theme}>
13
13
  <App />
14
14
  </CDPReactProvider>
15
15
  </StrictMode>,
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  CDPHooksProvider,
3
- useConfig,
4
3
  useIsInitialized,
5
4
  useSignInWithEmail,
6
5
  useVerifyEmailOTP,
@@ -8,27 +7,15 @@ import {
8
7
  useVerifySmsOTP,
9
8
  useIsSignedIn,
10
9
  useSignOut,
11
- useCurrentUser,
12
10
  Config,
13
11
  } from "@coinbase/cdp-hooks";
14
12
  import { StatusBar } from "expo-status-bar";
15
13
  import { useState } from "react";
16
- import {
17
- StyleSheet,
18
- Text,
19
- View,
20
- TouchableOpacity,
21
- Alert,
22
- ScrollView,
23
- SafeAreaView,
24
- Animated,
25
- Dimensions,
26
- } from "react-native";
14
+ import { StyleSheet, Text, View, Alert, ScrollView, SafeAreaView } from "react-native";
27
15
 
28
16
  import Transaction from "./Transaction";
29
17
  import { ThemeProvider, useTheme } from "./theme/ThemeContext";
30
- import { WelcomeScreen } from "./components/WelcomeScreen";
31
- import { SignInModal } from "./components/SignInModal";
18
+ import { SignInForm } from "./components/SignInForm";
32
19
  import { DarkModeToggle } from "./components/DarkModeToggle";
33
20
  import { WalletHeader } from "./components/WalletHeader";
34
21
  import { AuthMethod } from "./types";
@@ -69,9 +56,7 @@ function CDPApp() {
69
56
  const { signInWithSms } = useSignInWithSms();
70
57
  const { verifySmsOTP } = useVerifySmsOTP();
71
58
  const { signOut } = useSignOut();
72
- const { config } = useConfig();
73
59
  const { colors, isDarkMode } = useTheme();
74
- const { currentUser } = useCurrentUser();
75
60
 
76
61
  const [authMethod, setAuthMethod] = useState<AuthMethod>("email");
77
62
  const [email, setEmail] = useState("");
@@ -79,8 +64,6 @@ function CDPApp() {
79
64
  const [otp, setOtp] = useState("");
80
65
  const [flowId, setFlowId] = useState("");
81
66
  const [isLoading, setIsLoading] = useState(false);
82
- const [showSignInModal, setShowSignInModal] = useState(false);
83
- const [slideAnim] = useState(new Animated.Value(Dimensions.get("window").height));
84
67
 
85
68
  const handleSignIn = async () => {
86
69
  if (authMethod === "email") {
@@ -135,7 +118,6 @@ function CDPApp() {
135
118
  }
136
119
  setOtp("");
137
120
  setFlowId("");
138
- closeSignInModal();
139
121
  } catch (error) {
140
122
  Alert.alert("Error", error instanceof Error ? error.message : "Failed to verify OTP.");
141
123
  } finally {
@@ -159,31 +141,11 @@ function CDPApp() {
159
141
  setAuthMethod(authMethod === "email" ? "sms" : "email");
160
142
  setEmail("");
161
143
  setPhoneNumber("");
162
- setOtp("");
163
- setFlowId("");
164
144
  };
165
145
 
166
- const openSignInModal = () => {
167
- setShowSignInModal(true);
168
- Animated.timing(slideAnim, {
169
- toValue: 0,
170
- duration: 300,
171
- useNativeDriver: true,
172
- }).start();
173
- };
174
-
175
- const closeSignInModal = () => {
176
- Animated.timing(slideAnim, {
177
- toValue: Dimensions.get("window").height,
178
- duration: 300,
179
- useNativeDriver: true,
180
- }).start(() => {
181
- setShowSignInModal(false);
182
- setEmail("");
183
- setPhoneNumber("");
184
- setOtp("");
185
- setFlowId("");
186
- });
146
+ const handleBack = () => {
147
+ setOtp("");
148
+ setFlowId("");
187
149
  };
188
150
 
189
151
  const createStyles = () =>
@@ -269,7 +231,21 @@ function CDPApp() {
269
231
 
270
232
  <View style={styles.content}>
271
233
  {!isSignedIn ? (
272
- <WelcomeScreen onSignInPress={openSignInModal} />
234
+ <SignInForm
235
+ authMethod={authMethod}
236
+ onAuthMethodToggle={handleAuthMethodToggle}
237
+ email={email}
238
+ setEmail={setEmail}
239
+ phoneNumber={phoneNumber}
240
+ setPhoneNumber={setPhoneNumber}
241
+ otp={otp}
242
+ setOtp={setOtp}
243
+ flowId={flowId}
244
+ isLoading={isLoading}
245
+ onSignIn={handleSignIn}
246
+ onVerifyOTP={handleVerifyOTP}
247
+ onBack={handleBack}
248
+ />
273
249
  ) : (
274
250
  <>
275
251
  <WalletHeader onSignOut={handleSignOut} />
@@ -286,24 +262,6 @@ function CDPApp() {
286
262
  )}
287
263
  </View>
288
264
 
289
- <SignInModal
290
- visible={showSignInModal}
291
- onClose={closeSignInModal}
292
- authMethod={authMethod}
293
- onAuthMethodToggle={handleAuthMethodToggle}
294
- email={email}
295
- setEmail={setEmail}
296
- phoneNumber={phoneNumber}
297
- setPhoneNumber={setPhoneNumber}
298
- otp={otp}
299
- setOtp={setOtp}
300
- flowId={flowId}
301
- isLoading={isLoading}
302
- onSignIn={handleSignIn}
303
- onVerifyOTP={handleVerifyOTP}
304
- slideAnim={slideAnim}
305
- />
306
-
307
265
  <StatusBar style={isDarkMode ? "light" : "dark"} />
308
266
  </SafeAreaView>
309
267
  );
@@ -315,6 +273,65 @@ function CDPApp() {
315
273
  * @returns {JSX.Element} The rendered main component
316
274
  */
317
275
  export default function App() {
276
+ // Check if project ID is empty or the placeholder value
277
+ const projectId = process.env.EXPO_PUBLIC_CDP_PROJECT_ID;
278
+ const isPlaceholderProjectId = !projectId || projectId === "your-project-id-here";
279
+
280
+ if (isPlaceholderProjectId) {
281
+ return (
282
+ <ThemeProvider>
283
+ <SafeAreaView
284
+ style={{
285
+ flex: 1,
286
+ backgroundColor: "#f5f5f5",
287
+ justifyContent: "center",
288
+ alignItems: "center",
289
+ padding: 20,
290
+ }}
291
+ >
292
+ <Text
293
+ style={{
294
+ fontSize: 24,
295
+ fontWeight: "bold",
296
+ color: "#333",
297
+ textAlign: "center",
298
+ marginBottom: 16,
299
+ }}
300
+ >
301
+ ⚠️ CDP Project ID Required
302
+ </Text>
303
+ <Text
304
+ style={{
305
+ fontSize: 16,
306
+ color: "#666",
307
+ textAlign: "center",
308
+ lineHeight: 24,
309
+ marginBottom: 24,
310
+ }}
311
+ >
312
+ Please configure your CDP project ID in the .env file. Create a .env file in the project
313
+ root and add your CDP project ID.
314
+ </Text>
315
+ <View
316
+ style={{
317
+ backgroundColor: "#f0f0f0",
318
+ padding: 16,
319
+ borderRadius: 8,
320
+ borderWidth: 1,
321
+ borderColor: "#ddd",
322
+ }}
323
+ >
324
+ <Text
325
+ style={{ fontFamily: "monospace", fontSize: 14, color: "#333", textAlign: "center" }}
326
+ >
327
+ EXPO_PUBLIC_CDP_PROJECT_ID=your-actual-project-id
328
+ </Text>
329
+ </View>
330
+ </SafeAreaView>
331
+ </ThemeProvider>
332
+ );
333
+ }
334
+
318
335
  return (
319
336
  <CDPHooksProvider config={cdpConfig}>
320
337
  <ThemeProvider>
@@ -23,6 +23,9 @@ interface Props {
23
23
  onSuccess?: () => void;
24
24
  }
25
25
 
26
+ // ETH Faucet contract address on Base Sepolia
27
+ const FAUCET_ADDRESS = "0x3e4ed2D6d6235f9D26707fd5d5AF476fb9C91B0F" as const;
28
+
26
29
  function EOATransaction(props: Props) {
27
30
  const { onSuccess } = props;
28
31
  const { colors } = useTheme();
@@ -69,7 +72,7 @@ function EOATransaction(props: Props) {
69
72
  network: "base-sepolia",
70
73
  evmAccount: evmAddress,
71
74
  transaction: {
72
- to: evmAddress,
75
+ to: FAUCET_ADDRESS,
73
76
  value: 1000000000n,
74
77
  gas: 21000n,
75
78
  chainId: 84532,
@@ -97,6 +100,27 @@ function EOATransaction(props: Props) {
97
100
  }
98
101
  };
99
102
 
103
+ const openFaucet = () => {
104
+ const ethFaucetUrl = `https://portal.cdp.coinbase.com/products/faucet?token=ETH&address=${evmAddress}`;
105
+ Alert.alert(
106
+ "Get testnet ETH",
107
+ "",
108
+ [
109
+ {
110
+ text: "Copy ETH Faucet Link",
111
+ onPress: () => copyToClipboard(ethFaucetUrl, "ETH Faucet Link"),
112
+ },
113
+ {
114
+ text: "Open ETH Faucet",
115
+ onPress: () => Linking.openURL(ethFaucetUrl),
116
+ style: "default",
117
+ },
118
+ { text: "Cancel", style: "cancel" },
119
+ ],
120
+ { cancelable: true },
121
+ );
122
+ };
123
+
100
124
  const createStyles = () =>
101
125
  StyleSheet.create({
102
126
  container: {
@@ -226,6 +250,10 @@ function EOATransaction(props: Props) {
226
250
  fontStyle: "italic",
227
251
  textAlign: "center",
228
252
  },
253
+ faucetLink: {
254
+ color: colors.accent,
255
+ textDecorationLine: "underline",
256
+ },
229
257
  });
230
258
 
231
259
  const styles = createStyles();
@@ -241,24 +269,7 @@ function EOATransaction(props: Props) {
241
269
  : `${parseFloat(formattedBalance).toFixed(8)} ETH`}
242
270
  </Text>
243
271
  {!hasBalance && (
244
- <TouchableOpacity
245
- style={styles.faucetButton}
246
- onPress={() => {
247
- const ethFaucetUrl = `https://portal.cdp.coinbase.com/products/faucet?token=ETH&address=${evmAddress}`;
248
- Alert.alert("Get testnet ETH", "", [
249
- {
250
- text: "Copy ETH Faucet Link",
251
- onPress: () => copyToClipboard(ethFaucetUrl, "ETH Faucet Link"),
252
- },
253
- {
254
- text: "Open ETH Faucet",
255
- onPress: () => Linking.openURL(ethFaucetUrl),
256
- style: "default",
257
- },
258
- { text: "Cancel", style: "cancel" },
259
- ]);
260
- }}
261
- >
272
+ <TouchableOpacity style={styles.faucetButton} onPress={openFaucet}>
262
273
  <Text style={styles.faucetButtonText}>Get funds from faucet</Text>
263
274
  </TouchableOpacity>
264
275
  )}
@@ -268,9 +279,11 @@ function EOATransaction(props: Props) {
268
279
  <View style={styles.transactionSection}>
269
280
  <Text style={styles.sectionTitle}>Transfer ETH</Text>
270
281
  <Text style={styles.sectionDescription}>
271
- This example transaction sends a tiny amount of ETH from your wallet to itself. This
272
- transfer carries a small transaction fee, so you will see your balance decrease despite
273
- transferring to yourself.
282
+ This example transaction sends a tiny amount of ETH from your wallet to the{" "}
283
+ <Text style={styles.faucetLink} onPress={openFaucet}>
284
+ CDP Faucet
285
+ </Text>
286
+ .
274
287
  </Text>
275
288
 
276
289
  <TouchableOpacity
@@ -24,6 +24,9 @@ import { useTheme } from "./theme/ThemeContext";
24
24
  // USDC contract address on Base Sepolia
25
25
  const USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e" as const;
26
26
 
27
+ // USDC Faucet contract address on Base Sepolia
28
+ const FAUCET_ADDRESS = "0x8fDDcc0c5C993A1968B46787919Cc34577d6dC5c" as const;
29
+
27
30
  // ERC20 ABI for balance and transfer
28
31
  const ERC20_ABI = [
29
32
  {
@@ -72,11 +75,6 @@ function SmartAccountTransaction(props: Props) {
72
75
 
73
76
  const smartAccount = currentUser?.evmSmartAccounts?.[0];
74
77
 
75
- const formattedBalance = useMemo(() => {
76
- if (balance === undefined) return undefined;
77
- return formatEther(balance);
78
- }, [balance]);
79
-
80
78
  const formattedUsdcBalance = useMemo(() => {
81
79
  if (usdcBalance === undefined) return undefined;
82
80
  return formatUnits(usdcBalance, 6); // USDC has 6 decimals
@@ -120,13 +118,13 @@ function SmartAccountTransaction(props: Props) {
120
118
  setErrorMessage("");
121
119
 
122
120
  try {
123
- // Send 1 USDC to self as an example
121
+ // Send 1 USDC to the faucet
124
122
  const usdcAmount = parseUnits("1", 6); // USDC has 6 decimals
125
123
 
126
124
  const transferData = encodeFunctionData({
127
125
  abi: ERC20_ABI,
128
126
  functionName: "transfer",
129
- args: [smartAccount, usdcAmount],
127
+ args: [FAUCET_ADDRESS, usdcAmount],
130
128
  });
131
129
 
132
130
  const result = await sendUserOperation({
@@ -189,7 +187,6 @@ function SmartAccountTransaction(props: Props) {
189
187
  );
190
188
  };
191
189
 
192
- const hasBalance = balance && balance > 0n; // Still check ETH for gas
193
190
  const hasUsdcBalance = usdcBalance && usdcBalance > 0n;
194
191
 
195
192
  const createStyles = () =>
@@ -378,7 +375,10 @@ function SmartAccountTransaction(props: Props) {
378
375
  <View style={styles.transactionSection}>
379
376
  <Text style={styles.sectionTitle}>Transfer 1 USDC</Text>
380
377
  <Text style={styles.sectionSubtitle}>
381
- This example transaction sends 1 USDC from your wallet to itself.
378
+ This example transaction sends 1 USDC from your wallet to the{" "}
379
+ <Text style={styles.faucetLink} onPress={openFaucet}>
380
+ CDP Faucet
381
+ </Text>
382
382
  </Text>
383
383
 
384
384
  {!hasUsdcBalance && (