@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.
- package/README.md +22 -9
- package/dist/index.js +168 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template-nextjs/README.md +3 -2
- package/template-nextjs/env.example +2 -0
- package/template-nextjs/package.json +3 -1
- package/template-nextjs/public/sol.svg +13 -0
- package/template-nextjs/src/app/globals.css +6 -0
- package/template-nextjs/src/components/Providers.tsx +32 -14
- package/template-nextjs/src/components/SignedInScreen.tsx +63 -15
- package/template-nextjs/src/components/SignedInScreenWithOnramp.tsx +22 -3
- package/template-nextjs/src/components/SolanaTransaction.tsx +157 -0
- package/template-nextjs/src/components/UserBalance.tsx +5 -3
- package/template-react/README.md +2 -1
- package/template-react/env.example +3 -0
- package/template-react/package.json +3 -1
- package/template-react/public/sol.svg +13 -0
- package/template-react/src/Header.tsx +20 -8
- package/template-react/src/SignedInScreen.tsx +66 -13
- package/template-react/src/SolanaTransaction.tsx +158 -0
- package/template-react/src/UserBalance.tsx +29 -10
- package/template-react/src/config.ts +25 -11
- package/template-react/src/index.css +6 -0
- package/template-react/src/main.tsx +2 -2
- package/template-react-native/App.tsx +79 -62
- package/template-react-native/EOATransaction.tsx +35 -22
- package/template-react-native/SmartAccountTransaction.tsx +9 -9
- package/template-react-native/components/SignInForm.tsx +433 -0
- package/template-react-native/types.ts +0 -22
- package/template-react-native/components/SignInModal.tsx +0 -342
|
@@ -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 {
|
|
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}
|
|
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 {
|
|
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
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
<
|
|
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:
|
|
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
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
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: [
|
|
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
|
|
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 && (
|