@coinbase/create-cdp-app 0.0.38 → 0.0.39
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/dist/index.js +14 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template-nextjs/README.md +1 -1
- package/template-nextjs/src/app/api/onramp/buy-options/route.ts +0 -2
- package/template-nextjs/src/components/EOATransaction.tsx +7 -7
- package/template-nextjs/src/components/FundWallet.tsx +17 -3
- package/template-nextjs/src/components/SignedInScreen.tsx +2 -2
- package/template-nextjs/src/components/SignedInScreenWithOnramp.tsx +149 -41
- package/template-nextjs/src/components/SolanaTransaction.tsx +106 -96
- package/template-nextjs/src/lib/onramp-api.ts +6 -2
- package/template-react/src/EOATransaction.tsx +7 -7
- package/template-react/src/SignedInScreen.tsx +2 -3
- package/template-react/src/SolanaTransaction.tsx +105 -96
- package/template-react-native/App.tsx +28 -6
- package/template-react-native/SolanaTransaction.tsx +368 -0
- package/template-react-native/Transaction.tsx +101 -1
- package/template-react-native/components/WalletHeader.tsx +37 -13
- package/template-react-native/env.example +1 -0
- package/template-react-native/index.ts +7 -0
- package/template-react-native/metro.config.js +19 -0
- package/template-react-native/package.json +3 -0
|
@@ -1 +1,101 @@
|
|
|
1
|
-
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
+
import { useEvmAddress, useSolanaAddress } from "@coinbase/cdp-hooks";
|
|
4
|
+
import { useTheme } from "./theme/ThemeContext";
|
|
5
|
+
import EOATransaction from "./EOATransaction";
|
|
6
|
+
import SolanaTransaction from "./SolanaTransaction";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
onSuccess?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function Transaction(props: Props) {
|
|
13
|
+
const { onSuccess } = props;
|
|
14
|
+
const { colors } = useTheme();
|
|
15
|
+
const { evmAddress } = useEvmAddress();
|
|
16
|
+
const { solanaAddress } = useSolanaAddress();
|
|
17
|
+
const [activeTab, setActiveTab] = useState<"evm" | "solana">("evm");
|
|
18
|
+
|
|
19
|
+
// Show tabs only if both EVM and Solana addresses are available
|
|
20
|
+
const showTabs = !!evmAddress && !!solanaAddress;
|
|
21
|
+
|
|
22
|
+
const createStyles = () =>
|
|
23
|
+
StyleSheet.create({
|
|
24
|
+
container: {
|
|
25
|
+
flex: 1,
|
|
26
|
+
width: "100%",
|
|
27
|
+
},
|
|
28
|
+
tabContainer: {
|
|
29
|
+
flexDirection: "row",
|
|
30
|
+
marginBottom: 16,
|
|
31
|
+
backgroundColor: colors.cardBackground,
|
|
32
|
+
borderRadius: 8,
|
|
33
|
+
padding: 4,
|
|
34
|
+
borderWidth: 1,
|
|
35
|
+
borderColor: colors.border,
|
|
36
|
+
},
|
|
37
|
+
tab: {
|
|
38
|
+
flex: 1,
|
|
39
|
+
paddingVertical: 12,
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
borderRadius: 6,
|
|
42
|
+
},
|
|
43
|
+
activeTab: {
|
|
44
|
+
backgroundColor: colors.accent,
|
|
45
|
+
},
|
|
46
|
+
tabText: {
|
|
47
|
+
fontSize: 16,
|
|
48
|
+
fontWeight: "600",
|
|
49
|
+
color: colors.textSecondary,
|
|
50
|
+
},
|
|
51
|
+
activeTabText: {
|
|
52
|
+
color: "#ffffff",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const styles = createStyles();
|
|
57
|
+
|
|
58
|
+
// If only one address type is available, show that component directly
|
|
59
|
+
if (!showTabs) {
|
|
60
|
+
if (evmAddress) {
|
|
61
|
+
return <EOATransaction onSuccess={onSuccess} />;
|
|
62
|
+
}
|
|
63
|
+
if (solanaAddress) {
|
|
64
|
+
return <SolanaTransaction onSuccess={onSuccess} />;
|
|
65
|
+
}
|
|
66
|
+
return null; // Loading state handled by parent
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<View style={styles.container}>
|
|
71
|
+
{/* Tab selector */}
|
|
72
|
+
<View style={styles.tabContainer}>
|
|
73
|
+
<TouchableOpacity
|
|
74
|
+
style={[styles.tab, activeTab === "evm" && styles.activeTab]}
|
|
75
|
+
onPress={() => setActiveTab("evm")}
|
|
76
|
+
>
|
|
77
|
+
<Text style={[styles.tabText, activeTab === "evm" && styles.activeTabText]}>
|
|
78
|
+
Ethereum
|
|
79
|
+
</Text>
|
|
80
|
+
</TouchableOpacity>
|
|
81
|
+
<TouchableOpacity
|
|
82
|
+
style={[styles.tab, activeTab === "solana" && styles.activeTab]}
|
|
83
|
+
onPress={() => setActiveTab("solana")}
|
|
84
|
+
>
|
|
85
|
+
<Text style={[styles.tabText, activeTab === "solana" && styles.activeTabText]}>
|
|
86
|
+
Solana
|
|
87
|
+
</Text>
|
|
88
|
+
</TouchableOpacity>
|
|
89
|
+
</View>
|
|
90
|
+
|
|
91
|
+
{/* Content */}
|
|
92
|
+
{activeTab === "evm" ? (
|
|
93
|
+
<EOATransaction onSuccess={onSuccess} />
|
|
94
|
+
) : (
|
|
95
|
+
<SolanaTransaction onSuccess={onSuccess} />
|
|
96
|
+
)}
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default Transaction;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
|
|
3
3
|
import * as Clipboard from "expo-clipboard";
|
|
4
|
-
import { useEvmAddress, useCurrentUser } from "@coinbase/cdp-hooks";
|
|
4
|
+
import { useEvmAddress, useCurrentUser, useSolanaAddress } from "@coinbase/cdp-hooks";
|
|
5
5
|
import { useTheme } from "../theme/ThemeContext";
|
|
6
6
|
import { UserIcon } from "./UserIcon";
|
|
7
7
|
|
|
@@ -12,24 +12,26 @@ interface WalletHeaderProps {
|
|
|
12
12
|
export const WalletHeader: React.FC<WalletHeaderProps> = ({ onSignOut }) => {
|
|
13
13
|
const { colors } = useTheme();
|
|
14
14
|
const { evmAddress } = useEvmAddress();
|
|
15
|
+
const { solanaAddress } = useSolanaAddress();
|
|
15
16
|
const { currentUser } = useCurrentUser();
|
|
16
17
|
|
|
17
18
|
// Use EVM address if available, otherwise fall back to smart account
|
|
18
|
-
const
|
|
19
|
+
const evmWalletAddress = evmAddress || currentUser?.evmSmartAccounts?.[0];
|
|
20
|
+
const solanaWalletAddress = solanaAddress;
|
|
19
21
|
|
|
20
22
|
const formatAddress = (address: string) => {
|
|
21
23
|
if (!address) return "";
|
|
22
24
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
|
-
const copyWalletAddress = async () => {
|
|
26
|
-
if (!
|
|
27
|
+
const copyWalletAddress = async (address: string, type: "EVM" | "Solana") => {
|
|
28
|
+
if (!address) return;
|
|
27
29
|
|
|
28
30
|
try {
|
|
29
|
-
await Clipboard.setStringAsync(
|
|
30
|
-
Alert.alert("Copied!",
|
|
31
|
+
await Clipboard.setStringAsync(address);
|
|
32
|
+
Alert.alert("Copied!", `${type} address copied to clipboard.`);
|
|
31
33
|
} catch (error) {
|
|
32
|
-
Alert.alert("Error",
|
|
34
|
+
Alert.alert("Error", `Failed to copy ${type} address.`);
|
|
33
35
|
}
|
|
34
36
|
};
|
|
35
37
|
|
|
@@ -63,9 +65,15 @@ export const WalletHeader: React.FC<WalletHeaderProps> = ({ onSignOut }) => {
|
|
|
63
65
|
flex: 1,
|
|
64
66
|
},
|
|
65
67
|
address: {
|
|
66
|
-
fontSize:
|
|
68
|
+
fontSize: 14,
|
|
67
69
|
fontWeight: "500",
|
|
68
70
|
color: colors.text,
|
|
71
|
+
marginBottom: 2,
|
|
72
|
+
},
|
|
73
|
+
addressLabel: {
|
|
74
|
+
fontSize: 12,
|
|
75
|
+
color: colors.textSecondary,
|
|
76
|
+
marginBottom: 1,
|
|
69
77
|
},
|
|
70
78
|
signOutButton: {
|
|
71
79
|
backgroundColor: colors.accent,
|
|
@@ -84,16 +92,32 @@ export const WalletHeader: React.FC<WalletHeaderProps> = ({ onSignOut }) => {
|
|
|
84
92
|
|
|
85
93
|
return (
|
|
86
94
|
<View style={styles.container}>
|
|
87
|
-
<
|
|
95
|
+
<View style={styles.leftContent}>
|
|
88
96
|
<View style={styles.avatar}>
|
|
89
97
|
<UserIcon size={18} color={colors.cardBackground} />
|
|
90
98
|
</View>
|
|
91
99
|
<View style={styles.addressContainer}>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
{evmWalletAddress && (
|
|
101
|
+
<>
|
|
102
|
+
<Text style={styles.addressLabel}>EVM</Text>
|
|
103
|
+
<TouchableOpacity onPress={() => copyWalletAddress(evmWalletAddress, "EVM")}>
|
|
104
|
+
<Text style={styles.address}>{formatAddress(evmWalletAddress)}</Text>
|
|
105
|
+
</TouchableOpacity>
|
|
106
|
+
</>
|
|
107
|
+
)}
|
|
108
|
+
{solanaWalletAddress && (
|
|
109
|
+
<>
|
|
110
|
+
<Text style={styles.addressLabel}>Solana</Text>
|
|
111
|
+
<TouchableOpacity onPress={() => copyWalletAddress(solanaWalletAddress, "Solana")}>
|
|
112
|
+
<Text style={styles.address}>{formatAddress(solanaWalletAddress)}</Text>
|
|
113
|
+
</TouchableOpacity>
|
|
114
|
+
</>
|
|
115
|
+
)}
|
|
116
|
+
{!evmWalletAddress && !solanaWalletAddress && (
|
|
117
|
+
<Text style={styles.address}>Loading...</Text>
|
|
118
|
+
)}
|
|
95
119
|
</View>
|
|
96
|
-
</
|
|
120
|
+
</View>
|
|
97
121
|
<TouchableOpacity style={styles.signOutButton} onPress={onSignOut}>
|
|
98
122
|
<Text style={styles.signOutButtonText}>Sign out</Text>
|
|
99
123
|
</TouchableOpacity>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# CDP Configuration for the main app
|
|
2
2
|
EXPO_PUBLIC_CDP_PROJECT_ID=your-project-id-here
|
|
3
3
|
EXPO_PUBLIC_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE=smart
|
|
4
|
+
EXPO_PUBLIC_CDP_CREATE_SOLANA_ACCOUNT=false
|
|
4
5
|
EXPO_PUBLIC_CDP_BASE_PATH=https://api.cdp.coinbase.com/platform
|
|
5
6
|
EXPO_PUBLIC_USE_MOCK=false
|
|
@@ -2,12 +2,19 @@ import structuredClone from "@ungap/structured-clone";
|
|
|
2
2
|
import { registerRootComponent } from "expo";
|
|
3
3
|
import { install } from "react-native-quick-crypto";
|
|
4
4
|
import "react-native-get-random-values";
|
|
5
|
+
import { Buffer } from "buffer";
|
|
5
6
|
|
|
6
7
|
if (!("structuredClone" in globalThis)) {
|
|
7
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
9
|
globalThis.structuredClone = structuredClone as any;
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
// Setup Buffer global for Solana Web3.js compatibility
|
|
13
|
+
if (!("Buffer" in globalThis)) {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
globalThis.Buffer = Buffer as any;
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
install();
|
|
12
19
|
|
|
13
20
|
import App from "./App";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const { getDefaultConfig } = require("expo/metro-config");
|
|
2
|
+
|
|
3
|
+
const config = getDefaultConfig(__dirname);
|
|
4
|
+
|
|
5
|
+
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
|
6
|
+
if (moduleName.includes("zustand")) {
|
|
7
|
+
const result = require.resolve(moduleName);
|
|
8
|
+
return context.resolveRequest(context, result, platform);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (moduleName.includes("rpc-websockets")) {
|
|
12
|
+
const result = require.resolve(moduleName);
|
|
13
|
+
return context.resolveRequest(context, result, platform);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = config;
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@coinbase/cdp-hooks": "latest",
|
|
15
15
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
16
|
+
"@solana/web3.js": "^1.98.4",
|
|
16
17
|
"@ungap/structured-clone": "^1.3.0",
|
|
18
|
+
"buffer": "^6.0.3",
|
|
17
19
|
"expo": "~53.0.22",
|
|
18
20
|
"expo-asset": "~11.1.7",
|
|
19
21
|
"expo-clipboard": "~7.1.5",
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
"react-native": "0.79.6",
|
|
25
27
|
"react-native-get-random-values": "^1.11.0",
|
|
26
28
|
"react-native-quick-crypto": "^0.7.17",
|
|
29
|
+
"readable-stream": "^4.6.0",
|
|
27
30
|
"viem": "^2.21.45"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|