@coinbase/create-cdp-app 0.0.35 → 0.0.37

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 (33) hide show
  1. package/README.md +22 -9
  2. package/dist/index.js +168 -42
  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 +4 -2
  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/Header.tsx +2 -1
  11. package/template-nextjs/src/components/Providers.tsx +32 -12
  12. package/template-nextjs/src/components/SignedInScreen.tsx +63 -15
  13. package/template-nextjs/src/components/SignedInScreenWithOnramp.tsx +22 -3
  14. package/template-nextjs/src/components/SolanaTransaction.tsx +157 -0
  15. package/template-nextjs/src/components/UserBalance.tsx +5 -3
  16. package/template-react/README.md +2 -1
  17. package/template-react/env.example +4 -2
  18. package/template-react/package.json +3 -1
  19. package/template-react/public/sol.svg +13 -0
  20. package/template-react/src/Header.tsx +21 -9
  21. package/template-react/src/SignedInScreen.tsx +66 -13
  22. package/template-react/src/SolanaTransaction.tsx +158 -0
  23. package/template-react/src/UserBalance.tsx +29 -10
  24. package/template-react/src/config.ts +31 -11
  25. package/template-react/src/index.css +6 -0
  26. package/template-react/src/main.tsx +2 -2
  27. package/template-react-native/App.tsx +83 -63
  28. package/template-react-native/EOATransaction.tsx +35 -22
  29. package/template-react-native/SmartAccountTransaction.tsx +9 -9
  30. package/template-react-native/components/SignInForm.tsx +433 -0
  31. package/template-react-native/env.example +1 -1
  32. package/template-react-native/types.ts +0 -22
  33. package/template-react-native/components/SignInModal.tsx +0 -342
@@ -1,14 +1,34 @@
1
- import { type Config } from "@coinbase/cdp-hooks";
2
- import { type AppConfig } from "@coinbase/cdp-react";
1
+ import { type Config } from "@coinbase/cdp-react";
3
2
 
4
- export const CDP_CONFIG: Config = {
5
- projectId: import.meta.env.VITE_CDP_PROJECT_ID,
6
- createAccountOnLogin:
7
- import.meta.env.VITE_CDP_CREATE_ACCOUNT_TYPE === "evm-smart" ? "evm-smart" : "evm-eoa",
8
- };
3
+ const ethereumAccountType = import.meta.env.VITE_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE
4
+ ? import.meta.env.VITE_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE === "smart"
5
+ ? "smart"
6
+ : "eoa"
7
+ : undefined;
8
+
9
+ const solanaAccountType = import.meta.env.VITE_CDP_CREATE_SOLANA_ACCOUNT
10
+ ? import.meta.env.VITE_CDP_CREATE_SOLANA_ACCOUNT === "true"
11
+ : undefined;
9
12
 
10
- export const APP_CONFIG: AppConfig = {
11
- name: "CDP React StarterKit",
12
- logoUrl: "http://localhost:3000/logo.svg",
13
+ if (!ethereumAccountType && !solanaAccountType) {
14
+ throw new Error(
15
+ "Either VITE_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE or VITE_CDP_CREATE_SOLANA_ACCOUNT must be defined",
16
+ );
17
+ }
18
+
19
+ export const CDP_CONFIG = {
20
+ projectId: import.meta.env.VITE_CDP_PROJECT_ID,
21
+ ...(ethereumAccountType && {
22
+ ethereum: {
23
+ createOnLogin: ethereumAccountType,
24
+ },
25
+ }),
26
+ ...(solanaAccountType && {
27
+ solana: {
28
+ createOnLogin: solanaAccountType,
29
+ },
30
+ }),
31
+ appName: "CDP React StarterKit",
32
+ appLogoUrl: "http://localhost:3000/logo.svg",
13
33
  authMethods: ["email", "sms"],
14
- };
34
+ } as Config;
@@ -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";
@@ -36,7 +23,10 @@ import { AuthMethod } from "./types";
36
23
  const cdpConfig: Config = {
37
24
  projectId: process.env.EXPO_PUBLIC_CDP_PROJECT_ID,
38
25
  basePath: process.env.EXPO_PUBLIC_CDP_BASE_PATH,
39
- createAccountOnLogin: process.env.EXPO_PUBLIC_CDP_CREATE_ACCOUNT_TYPE,
26
+ ethereum: {
27
+ createOnLogin:
28
+ process.env.EXPO_PUBLIC_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE === "smart" ? "smart" : "eoa",
29
+ },
40
30
  useMock: process.env.EXPO_PUBLIC_CDP_USE_MOCK === "true",
41
31
  };
42
32
 
@@ -66,9 +56,7 @@ function CDPApp() {
66
56
  const { signInWithSms } = useSignInWithSms();
67
57
  const { verifySmsOTP } = useVerifySmsOTP();
68
58
  const { signOut } = useSignOut();
69
- const { config } = useConfig();
70
59
  const { colors, isDarkMode } = useTheme();
71
- const { currentUser } = useCurrentUser();
72
60
 
73
61
  const [authMethod, setAuthMethod] = useState<AuthMethod>("email");
74
62
  const [email, setEmail] = useState("");
@@ -76,8 +64,6 @@ function CDPApp() {
76
64
  const [otp, setOtp] = useState("");
77
65
  const [flowId, setFlowId] = useState("");
78
66
  const [isLoading, setIsLoading] = useState(false);
79
- const [showSignInModal, setShowSignInModal] = useState(false);
80
- const [slideAnim] = useState(new Animated.Value(Dimensions.get("window").height));
81
67
 
82
68
  const handleSignIn = async () => {
83
69
  if (authMethod === "email") {
@@ -132,7 +118,6 @@ function CDPApp() {
132
118
  }
133
119
  setOtp("");
134
120
  setFlowId("");
135
- closeSignInModal();
136
121
  } catch (error) {
137
122
  Alert.alert("Error", error instanceof Error ? error.message : "Failed to verify OTP.");
138
123
  } finally {
@@ -156,31 +141,11 @@ function CDPApp() {
156
141
  setAuthMethod(authMethod === "email" ? "sms" : "email");
157
142
  setEmail("");
158
143
  setPhoneNumber("");
159
- setOtp("");
160
- setFlowId("");
161
144
  };
162
145
 
163
- const openSignInModal = () => {
164
- setShowSignInModal(true);
165
- Animated.timing(slideAnim, {
166
- toValue: 0,
167
- duration: 300,
168
- useNativeDriver: true,
169
- }).start();
170
- };
171
-
172
- const closeSignInModal = () => {
173
- Animated.timing(slideAnim, {
174
- toValue: Dimensions.get("window").height,
175
- duration: 300,
176
- useNativeDriver: true,
177
- }).start(() => {
178
- setShowSignInModal(false);
179
- setEmail("");
180
- setPhoneNumber("");
181
- setOtp("");
182
- setFlowId("");
183
- });
146
+ const handleBack = () => {
147
+ setOtp("");
148
+ setFlowId("");
184
149
  };
185
150
 
186
151
  const createStyles = () =>
@@ -266,7 +231,21 @@ function CDPApp() {
266
231
 
267
232
  <View style={styles.content}>
268
233
  {!isSignedIn ? (
269
- <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
+ />
270
249
  ) : (
271
250
  <>
272
251
  <WalletHeader onSignOut={handleSignOut} />
@@ -283,24 +262,6 @@ function CDPApp() {
283
262
  )}
284
263
  </View>
285
264
 
286
- <SignInModal
287
- visible={showSignInModal}
288
- onClose={closeSignInModal}
289
- authMethod={authMethod}
290
- onAuthMethodToggle={handleAuthMethodToggle}
291
- email={email}
292
- setEmail={setEmail}
293
- phoneNumber={phoneNumber}
294
- setPhoneNumber={setPhoneNumber}
295
- otp={otp}
296
- setOtp={setOtp}
297
- flowId={flowId}
298
- isLoading={isLoading}
299
- onSignIn={handleSignIn}
300
- onVerifyOTP={handleVerifyOTP}
301
- slideAnim={slideAnim}
302
- />
303
-
304
265
  <StatusBar style={isDarkMode ? "light" : "dark"} />
305
266
  </SafeAreaView>
306
267
  );
@@ -312,6 +273,65 @@ function CDPApp() {
312
273
  * @returns {JSX.Element} The rendered main component
313
274
  */
314
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
+
315
335
  return (
316
336
  <CDPHooksProvider config={cdpConfig}>
317
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 && (