@coinbase/create-cdp-app 0.0.27 → 0.0.29

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.
@@ -1,10 +1,18 @@
1
1
  import { useEvmAddress, useSendEvmTransaction } from "@coinbase/cdp-hooks";
2
2
  import * as Clipboard from "expo-clipboard";
3
3
  import React, { useCallback, useEffect, useMemo, useState } from "react";
4
- // eslint-disable-next-line import/namespace
5
- import { View, Text, TouchableOpacity, StyleSheet, Alert, ActivityIndicator } from "react-native";
4
+ import {
5
+ View,
6
+ Text,
7
+ TouchableOpacity,
8
+ StyleSheet,
9
+ Alert,
10
+ ActivityIndicator,
11
+ Linking,
12
+ } from "react-native";
6
13
  import { createPublicClient, http, formatEther } from "viem";
7
14
  import { baseSepolia } from "viem/chains";
15
+ import { useTheme } from "./theme/ThemeContext";
8
16
 
9
17
  const client = createPublicClient({
10
18
  chain: baseSepolia,
@@ -15,15 +23,9 @@ interface Props {
15
23
  onSuccess?: () => void;
16
24
  }
17
25
 
18
- /**
19
- * This component demonstrates how to send an EVM transaction using EOA (Externally Owned Accounts).
20
- *
21
- * @param {Props} props - The props for the EOATransaction component.
22
- * @param {() => void} [props.onSuccess] - A function to call when the transaction is successful.
23
- * @returns A component that displays a transaction form and result.
24
- */
25
26
  function EOATransaction(props: Props) {
26
27
  const { onSuccess } = props;
28
+ const { colors } = useTheme();
27
29
  const { evmAddress } = useEvmAddress();
28
30
  const [balance, setBalance] = useState<bigint | undefined>(undefined);
29
31
  const [error, setError] = useState("");
@@ -55,7 +57,7 @@ function EOATransaction(props: Props) {
55
57
 
56
58
  const handleSendTransaction = async () => {
57
59
  if (!evmAddress || !hasBalance) {
58
- Alert.alert("Error", "Insufficient balance or no address available");
60
+ Alert.alert("Error", "Insufficient balance or no address available.");
59
61
  return;
60
62
  }
61
63
 
@@ -68,21 +70,19 @@ function EOATransaction(props: Props) {
68
70
  evmAccount: evmAddress,
69
71
  transaction: {
70
72
  to: evmAddress,
71
- value: 1000000000000n,
73
+ value: 1000000000n,
72
74
  gas: 21000n,
73
75
  chainId: 84532,
74
76
  type: "eip1559",
75
77
  },
76
78
  });
77
79
 
78
- Alert.alert("Success", "Transaction sent successfully!");
79
-
80
80
  onSuccess?.();
81
81
  getBalance();
82
82
  } catch (err) {
83
83
  const errorMessage = err instanceof Error ? err.message : "Transaction failed";
84
84
  setError(errorMessage);
85
- Alert.alert("Transaction Failed", errorMessage);
85
+ Alert.alert("Transaction Failed", errorMessage + (errorMessage.endsWith(".") ? "" : "."));
86
86
  } finally {
87
87
  setIsLoading(false);
88
88
  }
@@ -91,270 +91,229 @@ function EOATransaction(props: Props) {
91
91
  const copyToClipboard = async (text: string, label: string) => {
92
92
  try {
93
93
  await Clipboard.setStringAsync(text);
94
- Alert.alert("Copied!", `${label} copied to clipboard`);
94
+ Alert.alert("Copied!", `${label} copied to clipboard.`);
95
95
  } catch (error) {
96
- Alert.alert("Error", "Failed to copy to clipboard");
96
+ Alert.alert("Error", "Failed to copy to clipboard.");
97
97
  }
98
98
  };
99
99
 
100
- return (
101
- <View style={styles.container}>
102
- <Text style={styles.title}>EOA Transaction</Text>
100
+ const createStyles = () =>
101
+ StyleSheet.create({
102
+ container: {
103
+ flex: 1,
104
+ paddingHorizontal: 20,
105
+ paddingVertical: 20,
106
+ },
107
+ balanceSection: {
108
+ backgroundColor: colors.cardBackground,
109
+ borderRadius: 12,
110
+ padding: 24,
111
+ marginBottom: 16,
112
+ alignItems: "center",
113
+ borderWidth: 1,
114
+ borderColor: colors.border,
115
+ },
116
+ balanceTitle: {
117
+ fontSize: 16,
118
+ fontWeight: "500",
119
+ color: colors.textSecondary,
120
+ marginBottom: 8,
121
+ },
122
+ balanceAmount: {
123
+ fontSize: 32,
124
+ fontWeight: "bold",
125
+ color: colors.text,
126
+ marginBottom: 16,
127
+ },
128
+ faucetButton: {
129
+ backgroundColor: colors.accent,
130
+ paddingHorizontal: 20,
131
+ paddingVertical: 12,
132
+ borderRadius: 8,
133
+ alignItems: "center",
134
+ width: "100%",
135
+ },
136
+ faucetButtonText: {
137
+ color: "#ffffff",
138
+ fontSize: 16,
139
+ fontWeight: "600",
140
+ },
141
+ transactionSection: {
142
+ backgroundColor: colors.cardBackground,
143
+ borderRadius: 12,
144
+ padding: 20,
145
+ borderWidth: 1,
146
+ borderColor: colors.border,
147
+ },
148
+ sectionTitle: {
149
+ fontSize: 20,
150
+ fontWeight: "600",
151
+ color: colors.text,
152
+ marginBottom: 8,
153
+ },
154
+ sectionDescription: {
155
+ fontSize: 14,
156
+ color: colors.textSecondary,
157
+ marginBottom: 20,
158
+ lineHeight: 20,
159
+ },
160
+ sendButton: {
161
+ backgroundColor: colors.accent,
162
+ paddingVertical: 16,
163
+ borderRadius: 8,
164
+ alignItems: "center",
165
+ marginBottom: 16,
166
+ },
167
+ sendButtonDisabled: {
168
+ opacity: 0.6,
169
+ },
170
+ sendButtonText: {
171
+ color: "#ffffff",
172
+ fontSize: 16,
173
+ fontWeight: "600",
174
+ },
175
+ errorContainer: {
176
+ backgroundColor: "rgba(255, 0, 0, 0.1)",
177
+ padding: 12,
178
+ borderRadius: 8,
179
+ borderColor: "rgba(255, 0, 0, 0.3)",
180
+ borderWidth: 1,
181
+ marginBottom: 16,
182
+ },
183
+ errorText: {
184
+ color: "#cc0000",
185
+ fontSize: 14,
186
+ },
187
+ successContainer: {
188
+ backgroundColor: colors.accent + "20",
189
+ padding: 16,
190
+ borderRadius: 8,
191
+ borderColor: colors.accent + "50",
192
+ borderWidth: 1,
193
+ marginBottom: 16,
194
+ },
195
+ successTitle: {
196
+ color: colors.accent,
197
+ fontSize: 16,
198
+ fontWeight: "600",
199
+ marginBottom: 12,
200
+ },
201
+ hashContainer: {
202
+ marginBottom: 12,
203
+ },
204
+ hashLabel: {
205
+ color: colors.accent,
206
+ fontSize: 14,
207
+ fontWeight: "600",
208
+ marginBottom: 6,
209
+ },
210
+ hashButton: {
211
+ backgroundColor: colors.accent + "20",
212
+ borderRadius: 6,
213
+ padding: 12,
214
+ borderWidth: 1,
215
+ borderColor: colors.accent + "50",
216
+ },
217
+ hashText: {
218
+ color: colors.accent,
219
+ fontSize: 12,
220
+ fontFamily: "monospace",
221
+ marginBottom: 4,
222
+ },
223
+ copyHint: {
224
+ color: colors.accent,
225
+ fontSize: 10,
226
+ fontStyle: "italic",
227
+ textAlign: "center",
228
+ },
229
+ });
103
230
 
104
- <View style={styles.infoContainer}>
105
- <Text style={styles.label}>EVM Address:</Text>
106
- <TouchableOpacity
107
- style={styles.copyableInfo}
108
- onPress={() => copyToClipboard(evmAddress!, "EVM Address")}
109
- >
110
- <Text style={styles.copyableText}>{evmAddress || "Not available"}</Text>
111
- <Text style={styles.addressCopyHint}>Tap to copy</Text>
112
- </TouchableOpacity>
113
- </View>
231
+ const styles = createStyles();
114
232
 
115
- <View style={styles.infoContainer}>
116
- <Text style={styles.label}>Balance:</Text>
117
- <Text style={styles.value}>
118
- {formattedBalance === undefined ? "Loading..." : `${formattedBalance} ETH`}
233
+ return (
234
+ <View style={styles.container}>
235
+ {/* Balance Section */}
236
+ <View style={styles.balanceSection}>
237
+ <Text style={styles.balanceTitle}>Current Balance</Text>
238
+ <Text style={styles.balanceAmount}>
239
+ {formattedBalance === undefined
240
+ ? "Loading..."
241
+ : `${parseFloat(formattedBalance).toFixed(8)} ETH`}
119
242
  </Text>
120
- </View>
121
-
122
- <TouchableOpacity
123
- style={[styles.button, (!hasBalance || isLoading) && styles.buttonDisabled]}
124
- onPress={handleSendTransaction}
125
- disabled={!hasBalance || isLoading}
126
- >
127
- {isLoading ? (
128
- <ActivityIndicator color="#fff" />
129
- ) : (
130
- <Text style={styles.buttonText}>Send Transaction</Text>
131
- )}
132
- </TouchableOpacity>
133
-
134
- {error ? (
135
- <View style={styles.errorContainer}>
136
- <Text style={styles.errorText}>{error}</Text>
137
- </View>
138
- ) : null}
139
-
140
- {transactionData.status === "success" ? (
141
- <View style={styles.successContainer}>
142
- <Text style={styles.successTitle}>Transaction Sent!</Text>
143
- <View style={styles.hashContainer}>
144
- <Text style={styles.hashLabel}>Transaction Hash:</Text>
145
- <TouchableOpacity
146
- style={styles.hashButton}
147
- onPress={() =>
148
- copyToClipboard(transactionData.receipt.transactionHash, "Transaction Hash")
149
- }
150
- >
151
- <Text style={styles.hashText}>{transactionData.receipt.transactionHash}</Text>
152
- <Text style={styles.copyHint}>📋 Tap to copy</Text>
153
- </TouchableOpacity>
154
- </View>
155
- </View>
156
- ) : null}
157
-
158
- {!hasBalance && (
159
- <View style={styles.fundingContainer}>
160
- <Text style={styles.fundingTitle}>Get Testnet Funds</Text>
161
- <Text style={styles.fundingText}>
162
- This example transaction sends a tiny amount of ETH from your wallet to itself.
163
- </Text>
164
- <Text style={styles.fundingText}>Get some testnet ETH from the Base Sepolia Faucet:</Text>
243
+ {!hasBalance && (
165
244
  <TouchableOpacity
166
245
  style={styles.faucetButton}
167
246
  onPress={() => {
168
- Alert.alert(
169
- "Base Sepolia Faucet",
170
- `Visit: https://portal.cdp.coinbase.com/products/faucet?token=ETH&address=${evmAddress}`,
171
- [
172
- {
173
- text: "Copy URL",
174
- onPress: async () => {
175
- try {
176
- await Clipboard.setStringAsync(
177
- `https://portal.cdp.coinbase.com/products/faucet?token=ETH&address=${evmAddress}`,
178
- );
179
- Alert.alert("Copied!", "Faucet URL copied to clipboard");
180
- } catch (error) {
181
- Alert.alert("Error", "Failed to copy URL");
182
- }
183
- },
184
- },
185
- { text: "OK", style: "default" },
186
- ],
187
- );
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
+ ]);
188
260
  }}
189
261
  >
190
- <Text style={styles.faucetButtonText}>Base Sepolia Faucet</Text>
262
+ <Text style={styles.faucetButtonText}>Get funds from faucet</Text>
191
263
  </TouchableOpacity>
192
- </View>
193
- )}
264
+ )}
265
+ </View>
266
+
267
+ {/* Transaction Section */}
268
+ <View style={styles.transactionSection}>
269
+ <Text style={styles.sectionTitle}>Transfer ETH</Text>
270
+ <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.
274
+ </Text>
275
+
276
+ <TouchableOpacity
277
+ style={[styles.sendButton, (!hasBalance || isLoading) && styles.sendButtonDisabled]}
278
+ onPress={handleSendTransaction}
279
+ disabled={!hasBalance || isLoading}
280
+ >
281
+ {isLoading ? (
282
+ <ActivityIndicator color="#fff" />
283
+ ) : (
284
+ <Text style={styles.sendButtonText}>Transfer</Text>
285
+ )}
286
+ </TouchableOpacity>
287
+
288
+ {error ? (
289
+ <View style={styles.errorContainer}>
290
+ <Text style={styles.errorText}>{error}</Text>
291
+ </View>
292
+ ) : null}
293
+
294
+ {transactionData.status === "success" ? (
295
+ <View style={styles.successContainer}>
296
+ <Text style={styles.successTitle}>Transfer Complete</Text>
297
+ <View style={styles.hashContainer}>
298
+ <Text style={styles.hashLabel}>Transaction Hash:</Text>
299
+ <TouchableOpacity
300
+ style={styles.hashButton}
301
+ onPress={() =>
302
+ copyToClipboard(
303
+ `https://sepolia.basescan.org/tx/${transactionData.receipt.transactionHash}`,
304
+ "Block Explorer Link",
305
+ )
306
+ }
307
+ >
308
+ <Text style={styles.hashText}>{transactionData.receipt.transactionHash}</Text>
309
+ <Text style={styles.copyHint}>Tap to copy block explorer link</Text>
310
+ </TouchableOpacity>
311
+ </View>
312
+ </View>
313
+ ) : null}
314
+ </View>
194
315
  </View>
195
316
  );
196
317
  }
197
318
 
198
- const styles = StyleSheet.create({
199
- container: {
200
- padding: 32,
201
- backgroundColor: "#ffffff",
202
- borderRadius: 16,
203
- borderWidth: 1,
204
- borderColor: "#dcdcdc",
205
- marginHorizontal: 0,
206
- marginVertical: 8,
207
- alignItems: "stretch",
208
- },
209
- title: {
210
- fontSize: 20,
211
- fontWeight: "500",
212
- marginBottom: 16,
213
- textAlign: "center",
214
- color: "#111111",
215
- },
216
- infoContainer: {
217
- marginBottom: 16,
218
- width: "100%",
219
- },
220
- label: {
221
- fontSize: 14,
222
- fontWeight: "600",
223
- color: "#757575",
224
- marginBottom: 4,
225
- },
226
- value: {
227
- fontSize: 14,
228
- color: "#111111",
229
- fontFamily: "monospace",
230
- textAlign: "left",
231
- },
232
- button: {
233
- backgroundColor: "#008080",
234
- padding: 15,
235
- borderRadius: 8,
236
- alignItems: "center",
237
- alignSelf: "center",
238
- marginVertical: 16,
239
- minWidth: 188,
240
- paddingHorizontal: 32,
241
- },
242
- buttonDisabled: {
243
- opacity: 0.6,
244
- },
245
- buttonText: {
246
- color: "#ffffff",
247
- fontSize: 16,
248
- fontWeight: "600",
249
- },
250
- errorContainer: {
251
- backgroundColor: "#ffebee",
252
- padding: 12,
253
- borderRadius: 8,
254
- borderColor: "#f44336",
255
- borderWidth: 1,
256
- width: "100%",
257
- },
258
- errorText: {
259
- color: "#c62828",
260
- fontSize: 14,
261
- },
262
- successContainer: {
263
- backgroundColor: "rgba(76, 175, 80, 0.1)",
264
- padding: 12,
265
- borderRadius: 8,
266
- borderColor: "rgba(76, 175, 80, 0.3)",
267
- borderWidth: 1,
268
- width: "100%",
269
- },
270
- successTitle: {
271
- color: "#4caf50",
272
- fontSize: 16,
273
- fontWeight: "600",
274
- marginBottom: 8,
275
- },
276
- hashText: {
277
- color: "#4caf50",
278
- fontSize: 12,
279
- fontFamily: "monospace",
280
- marginBottom: 4,
281
- },
282
- fundingContainer: {
283
- marginTop: 20,
284
- padding: 16,
285
- backgroundColor: "rgba(255, 193, 7, 0.1)",
286
- borderRadius: 8,
287
- borderWidth: 1,
288
- borderColor: "rgba(255, 193, 7, 0.3)",
289
- width: "100%",
290
- },
291
- fundingTitle: {
292
- fontSize: 16,
293
- fontWeight: "600",
294
- color: "#111111",
295
- marginBottom: 8,
296
- textAlign: "center",
297
- },
298
- fundingText: {
299
- fontSize: 14,
300
- color: "#757575",
301
- marginBottom: 8,
302
- textAlign: "center",
303
- lineHeight: 20,
304
- },
305
- faucetButton: {
306
- backgroundColor: "#008080",
307
- padding: 12,
308
- borderRadius: 8,
309
- alignItems: "center",
310
- marginTop: 8,
311
- },
312
- faucetButtonText: {
313
- color: "#ffffff",
314
- fontSize: 14,
315
- fontWeight: "600",
316
- },
317
- hashContainer: {
318
- marginBottom: 12,
319
- },
320
- hashLabel: {
321
- color: "#4caf50",
322
- fontSize: 14,
323
- fontWeight: "600",
324
- marginBottom: 4,
325
- },
326
- hashButton: {
327
- backgroundColor: "rgba(76, 175, 80, 0.1)",
328
- borderRadius: 8,
329
- padding: 8,
330
- borderWidth: 1,
331
- borderColor: "rgba(76, 175, 80, 0.3)",
332
- },
333
- copyHint: {
334
- color: "#4caf50",
335
- fontSize: 10,
336
- fontStyle: "italic",
337
- textAlign: "center",
338
- },
339
- copyableInfo: {
340
- backgroundColor: "rgba(0, 128, 128, 0.1)",
341
- borderRadius: 8,
342
- padding: 12,
343
- borderWidth: 1,
344
- borderColor: "rgba(0, 128, 128, 0.3)",
345
- },
346
- copyableText: {
347
- fontSize: 12,
348
- fontFamily: "monospace",
349
- color: "#008080",
350
- textAlign: "center",
351
- },
352
- addressCopyHint: {
353
- fontSize: 10,
354
- color: "#008080",
355
- fontStyle: "italic",
356
- textAlign: "center",
357
- },
358
- });
359
-
360
319
  export default EOATransaction;