@coinbase/create-cdp-app 0.0.43 → 0.0.44
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 +119 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template-nextjs/src/components/{Header.tsx → evm-eoa/Header.tsx} +0 -4
- package/template-nextjs/src/components/evm-eoa/SignedInScreen.tsx +68 -0
- package/template-nextjs/src/components/evm-eoa/SignedInScreenWithOnramp.tsx +117 -0
- package/template-nextjs/src/components/{UserBalance.tsx → evm-eoa/UserBalance.tsx} +3 -4
- package/template-nextjs/src/components/evm-smart/Header.tsx +64 -0
- package/template-nextjs/src/components/evm-smart/SignedInScreen.tsx +68 -0
- package/template-nextjs/src/components/evm-smart/SignedInScreenWithOnramp.tsx +120 -0
- package/template-nextjs/src/components/evm-smart/UserBalance.tsx +43 -0
- package/template-nextjs/src/components/solana/Header.tsx +63 -0
- package/template-nextjs/src/components/solana/SignedInScreen.tsx +74 -0
- package/template-nextjs/src/components/solana/SignedInScreenWithOnramp.tsx +121 -0
- package/template-nextjs/src/components/solana/UserBalance.tsx +43 -0
- package/template-react/src/evm-eoa/Header.tsx +67 -0
- package/template-react/src/evm-eoa/SignedInScreen.tsx +64 -0
- package/template-react/src/{UserBalance.tsx → evm-eoa/UserBalance.tsx} +10 -28
- package/template-react/src/evm-smart/Header.tsx +68 -0
- package/template-react/src/evm-smart/SignedInScreen.tsx +64 -0
- package/template-react/src/evm-smart/UserBalance.tsx +44 -0
- package/template-react/src/{Header.tsx → solana/Header.tsx} +9 -21
- package/template-react/src/solana/SignedInScreen.tsx +70 -0
- package/template-react/src/solana/UserBalance.tsx +44 -0
- package/template-react-native/{components → evm-eoa}/WalletHeader.tsx +10 -21
- package/template-react-native/evm-smart/WalletHeader.tsx +115 -0
- package/template-react-native/solana/WalletHeader.tsx +111 -0
- package/template-nextjs/src/components/SignedInScreen.tsx +0 -116
- package/template-nextjs/src/components/SignedInScreenWithOnramp.tsx +0 -239
- package/template-react/src/SignedInScreen.tsx +0 -116
- package/template-react-native/Transaction.tsx +0 -101
- /package/template-nextjs/src/components/{EOATransaction.tsx → evm-eoa/EOATransaction.tsx} +0 -0
- /package/template-nextjs/src/components/{SmartAccountTransaction.tsx → evm-smart/SmartAccountTransaction.tsx} +0 -0
- /package/template-nextjs/src/components/{SolanaTransaction.tsx → solana/SolanaTransaction.tsx} +0 -0
- /package/template-react/src/{EOATransaction.tsx → evm-eoa/EOATransaction.tsx} +0 -0
- /package/template-react/src/{SmartAccountTransaction.tsx → evm-smart/SmartAccountTransaction.tsx} +0 -0
- /package/template-react/src/{SolanaTransaction.tsx → solana/SolanaTransaction.tsx} +0 -0
- /package/template-react-native/{EOATransaction.tsx → evm-eoa/EOATransaction.tsx} +0 -0
- /package/template-react-native/{SmartAccountTransaction.tsx → evm-smart/SmartAccountTransaction.tsx} +0 -0
- /package/template-react-native/{SolanaTransaction.tsx → solana/SolanaTransaction.tsx} +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { LoadingSkeleton } from "@coinbase/cdp-react/components/ui/LoadingSkeleton";
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
balance?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A component that displays the user's balance.
|
|
9
|
+
*
|
|
10
|
+
* @param {Props} props - The props for the UserBalance component.
|
|
11
|
+
* @param {string} [props.balance] - The user's balance.
|
|
12
|
+
* @returns A component that displays the user's balance.
|
|
13
|
+
*/
|
|
14
|
+
function UserBalance(props: Props) {
|
|
15
|
+
const { balance } = props;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<h2 className="card-title">Available balance</h2>
|
|
20
|
+
<p className="user-balance flex-col-container flex-grow">
|
|
21
|
+
{balance === undefined && <LoadingSkeleton as="span" className="loading--balance" />}
|
|
22
|
+
{balance !== undefined && (
|
|
23
|
+
<span className="flex-row-container">
|
|
24
|
+
<img src="/sol.svg" alt="" className="balance-icon" />
|
|
25
|
+
<span>{balance}</span>
|
|
26
|
+
<span className="sr-only">Solana</span>
|
|
27
|
+
</span>
|
|
28
|
+
)}
|
|
29
|
+
</p>
|
|
30
|
+
<p>
|
|
31
|
+
Get testnet SOL from{" "}
|
|
32
|
+
<a
|
|
33
|
+
href="https://portal.cdp.coinbase.com/products/faucet?network=solana-devnet"
|
|
34
|
+
target="_blank"
|
|
35
|
+
rel="noopener noreferrer"
|
|
36
|
+
>
|
|
37
|
+
Solana Devnet Faucet
|
|
38
|
+
</a>
|
|
39
|
+
</p>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default UserBalance;
|
|
@@ -1,9 +1,9 @@
|
|
|
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
|
|
4
|
+
import { useEvmAddress, useCurrentUser } from "@coinbase/cdp-hooks";
|
|
5
5
|
import { useTheme } from "../theme/ThemeContext";
|
|
6
|
-
import { UserIcon } from "
|
|
6
|
+
import { UserIcon } from "../components/UserIcon";
|
|
7
7
|
|
|
8
8
|
interface WalletHeaderProps {
|
|
9
9
|
onSignOut: () => void;
|
|
@@ -12,26 +12,24 @@ 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();
|
|
16
15
|
const { currentUser } = useCurrentUser();
|
|
17
16
|
|
|
18
17
|
// Use EVM address if available, otherwise fall back to smart account
|
|
19
18
|
const evmWalletAddress = evmAddress || currentUser?.evmSmartAccounts?.[0];
|
|
20
|
-
const solanaWalletAddress = solanaAddress;
|
|
21
19
|
|
|
22
20
|
const formatAddress = (address: string) => {
|
|
23
21
|
if (!address) return "";
|
|
24
22
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
25
23
|
};
|
|
26
24
|
|
|
27
|
-
const copyWalletAddress = async (
|
|
28
|
-
if (!
|
|
25
|
+
const copyWalletAddress = async () => {
|
|
26
|
+
if (!evmWalletAddress) return;
|
|
29
27
|
|
|
30
28
|
try {
|
|
31
|
-
await Clipboard.setStringAsync(
|
|
32
|
-
Alert.alert("Copied!",
|
|
29
|
+
await Clipboard.setStringAsync(evmWalletAddress);
|
|
30
|
+
Alert.alert("Copied!", "EVM address copied to clipboard.");
|
|
33
31
|
} catch (error) {
|
|
34
|
-
Alert.alert("Error",
|
|
32
|
+
Alert.alert("Error", "Failed to copy EVM address.");
|
|
35
33
|
}
|
|
36
34
|
};
|
|
37
35
|
|
|
@@ -97,23 +95,14 @@ export const WalletHeader: React.FC<WalletHeaderProps> = ({ onSignOut }) => {
|
|
|
97
95
|
<UserIcon size={18} color={colors.cardBackground} />
|
|
98
96
|
</View>
|
|
99
97
|
<View style={styles.addressContainer}>
|
|
100
|
-
{evmWalletAddress
|
|
98
|
+
{evmWalletAddress ? (
|
|
101
99
|
<>
|
|
102
100
|
<Text style={styles.addressLabel}>EVM</Text>
|
|
103
|
-
<TouchableOpacity onPress={
|
|
101
|
+
<TouchableOpacity onPress={copyWalletAddress}>
|
|
104
102
|
<Text style={styles.address}>{formatAddress(evmWalletAddress)}</Text>
|
|
105
103
|
</TouchableOpacity>
|
|
106
104
|
</>
|
|
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 && (
|
|
105
|
+
) : (
|
|
117
106
|
<Text style={styles.address}>Loading...</Text>
|
|
118
107
|
)}
|
|
119
108
|
</View>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
|
|
3
|
+
import * as Clipboard from "expo-clipboard";
|
|
4
|
+
import { useEvmAddress, useCurrentUser } from "@coinbase/cdp-hooks";
|
|
5
|
+
import { useTheme } from "../theme/ThemeContext";
|
|
6
|
+
import { UserIcon } from "../components/UserIcon";
|
|
7
|
+
|
|
8
|
+
interface WalletHeaderProps {
|
|
9
|
+
onSignOut: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const WalletHeader: React.FC<WalletHeaderProps> = ({ onSignOut }) => {
|
|
13
|
+
const { colors } = useTheme();
|
|
14
|
+
const { evmAddress } = useEvmAddress();
|
|
15
|
+
const { currentUser } = useCurrentUser();
|
|
16
|
+
|
|
17
|
+
// Use EVM address if available, otherwise fall back to smart account
|
|
18
|
+
const evmWalletAddress = evmAddress || currentUser?.evmSmartAccounts?.[0];
|
|
19
|
+
|
|
20
|
+
const formatAddress = (address: string) => {
|
|
21
|
+
if (!address) return "";
|
|
22
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const copyWalletAddress = async () => {
|
|
26
|
+
if (!evmWalletAddress) return;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await Clipboard.setStringAsync(evmWalletAddress);
|
|
30
|
+
Alert.alert("Copied!", "EVM address copied to clipboard.");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
Alert.alert("Error", "Failed to copy EVM address.");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const createStyles = () =>
|
|
37
|
+
StyleSheet.create({
|
|
38
|
+
container: {
|
|
39
|
+
backgroundColor: colors.cardBackground,
|
|
40
|
+
paddingHorizontal: 20,
|
|
41
|
+
paddingVertical: 16,
|
|
42
|
+
borderBottomWidth: 1,
|
|
43
|
+
borderBottomColor: colors.border,
|
|
44
|
+
flexDirection: "row",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
justifyContent: "space-between",
|
|
47
|
+
},
|
|
48
|
+
leftContent: {
|
|
49
|
+
flexDirection: "row",
|
|
50
|
+
alignItems: "center",
|
|
51
|
+
flex: 1,
|
|
52
|
+
},
|
|
53
|
+
avatar: {
|
|
54
|
+
width: 32,
|
|
55
|
+
height: 32,
|
|
56
|
+
borderRadius: 16,
|
|
57
|
+
backgroundColor: colors.textSecondary,
|
|
58
|
+
marginRight: 12,
|
|
59
|
+
justifyContent: "center",
|
|
60
|
+
alignItems: "center",
|
|
61
|
+
},
|
|
62
|
+
addressContainer: {
|
|
63
|
+
flex: 1,
|
|
64
|
+
},
|
|
65
|
+
address: {
|
|
66
|
+
fontSize: 14,
|
|
67
|
+
fontWeight: "500",
|
|
68
|
+
color: colors.text,
|
|
69
|
+
marginBottom: 2,
|
|
70
|
+
},
|
|
71
|
+
addressLabel: {
|
|
72
|
+
fontSize: 12,
|
|
73
|
+
color: colors.textSecondary,
|
|
74
|
+
marginBottom: 1,
|
|
75
|
+
},
|
|
76
|
+
signOutButton: {
|
|
77
|
+
backgroundColor: colors.accent,
|
|
78
|
+
paddingHorizontal: 16,
|
|
79
|
+
paddingVertical: 8,
|
|
80
|
+
borderRadius: 20,
|
|
81
|
+
},
|
|
82
|
+
signOutButtonText: {
|
|
83
|
+
color: "#ffffff",
|
|
84
|
+
fontSize: 14,
|
|
85
|
+
fontWeight: "600",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const styles = createStyles();
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<View style={styles.container}>
|
|
93
|
+
<View style={styles.leftContent}>
|
|
94
|
+
<View style={styles.avatar}>
|
|
95
|
+
<UserIcon size={18} color={colors.cardBackground} />
|
|
96
|
+
</View>
|
|
97
|
+
<View style={styles.addressContainer}>
|
|
98
|
+
{evmWalletAddress ? (
|
|
99
|
+
<>
|
|
100
|
+
<Text style={styles.addressLabel}>EVM</Text>
|
|
101
|
+
<TouchableOpacity onPress={copyWalletAddress}>
|
|
102
|
+
<Text style={styles.address}>{formatAddress(evmWalletAddress)}</Text>
|
|
103
|
+
</TouchableOpacity>
|
|
104
|
+
</>
|
|
105
|
+
) : (
|
|
106
|
+
<Text style={styles.address}>Loading...</Text>
|
|
107
|
+
)}
|
|
108
|
+
</View>
|
|
109
|
+
</View>
|
|
110
|
+
<TouchableOpacity style={styles.signOutButton} onPress={onSignOut}>
|
|
111
|
+
<Text style={styles.signOutButtonText}>Sign out</Text>
|
|
112
|
+
</TouchableOpacity>
|
|
113
|
+
</View>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
|
|
3
|
+
import * as Clipboard from "expo-clipboard";
|
|
4
|
+
import { useSolanaAddress } from "@coinbase/cdp-hooks";
|
|
5
|
+
import { useTheme } from "../theme/ThemeContext";
|
|
6
|
+
import { UserIcon } from "../components/UserIcon";
|
|
7
|
+
|
|
8
|
+
interface WalletHeaderProps {
|
|
9
|
+
onSignOut: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const WalletHeader: React.FC<WalletHeaderProps> = ({ onSignOut }) => {
|
|
13
|
+
const { colors } = useTheme();
|
|
14
|
+
const { solanaAddress } = useSolanaAddress();
|
|
15
|
+
|
|
16
|
+
const formatAddress = (address: string) => {
|
|
17
|
+
if (!address) return "";
|
|
18
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const copyWalletAddress = async () => {
|
|
22
|
+
if (!solanaAddress) return;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await Clipboard.setStringAsync(solanaAddress);
|
|
26
|
+
Alert.alert("Copied!", "Solana address copied to clipboard.");
|
|
27
|
+
} catch (error) {
|
|
28
|
+
Alert.alert("Error", "Failed to copy Solana address.");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const createStyles = () =>
|
|
33
|
+
StyleSheet.create({
|
|
34
|
+
container: {
|
|
35
|
+
backgroundColor: colors.cardBackground,
|
|
36
|
+
paddingHorizontal: 20,
|
|
37
|
+
paddingVertical: 16,
|
|
38
|
+
borderBottomWidth: 1,
|
|
39
|
+
borderBottomColor: colors.border,
|
|
40
|
+
flexDirection: "row",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "space-between",
|
|
43
|
+
},
|
|
44
|
+
leftContent: {
|
|
45
|
+
flexDirection: "row",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
flex: 1,
|
|
48
|
+
},
|
|
49
|
+
avatar: {
|
|
50
|
+
width: 32,
|
|
51
|
+
height: 32,
|
|
52
|
+
borderRadius: 16,
|
|
53
|
+
backgroundColor: colors.textSecondary,
|
|
54
|
+
marginRight: 12,
|
|
55
|
+
justifyContent: "center",
|
|
56
|
+
alignItems: "center",
|
|
57
|
+
},
|
|
58
|
+
addressContainer: {
|
|
59
|
+
flex: 1,
|
|
60
|
+
},
|
|
61
|
+
address: {
|
|
62
|
+
fontSize: 14,
|
|
63
|
+
fontWeight: "500",
|
|
64
|
+
color: colors.text,
|
|
65
|
+
marginBottom: 2,
|
|
66
|
+
},
|
|
67
|
+
addressLabel: {
|
|
68
|
+
fontSize: 12,
|
|
69
|
+
color: colors.textSecondary,
|
|
70
|
+
marginBottom: 1,
|
|
71
|
+
},
|
|
72
|
+
signOutButton: {
|
|
73
|
+
backgroundColor: colors.accent,
|
|
74
|
+
paddingHorizontal: 16,
|
|
75
|
+
paddingVertical: 8,
|
|
76
|
+
borderRadius: 20,
|
|
77
|
+
},
|
|
78
|
+
signOutButtonText: {
|
|
79
|
+
color: "#ffffff",
|
|
80
|
+
fontSize: 14,
|
|
81
|
+
fontWeight: "600",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const styles = createStyles();
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<View style={styles.container}>
|
|
89
|
+
<View style={styles.leftContent}>
|
|
90
|
+
<View style={styles.avatar}>
|
|
91
|
+
<UserIcon size={18} color={colors.cardBackground} />
|
|
92
|
+
</View>
|
|
93
|
+
<View style={styles.addressContainer}>
|
|
94
|
+
{solanaAddress ? (
|
|
95
|
+
<>
|
|
96
|
+
<Text style={styles.addressLabel}>Solana</Text>
|
|
97
|
+
<TouchableOpacity onPress={copyWalletAddress}>
|
|
98
|
+
<Text style={styles.address}>{formatAddress(solanaAddress)}</Text>
|
|
99
|
+
</TouchableOpacity>
|
|
100
|
+
</>
|
|
101
|
+
) : (
|
|
102
|
+
<Text style={styles.address}>Loading...</Text>
|
|
103
|
+
)}
|
|
104
|
+
</View>
|
|
105
|
+
</View>
|
|
106
|
+
<TouchableOpacity style={styles.signOutButton} onPress={onSignOut}>
|
|
107
|
+
<Text style={styles.signOutButtonText}>Sign out</Text>
|
|
108
|
+
</TouchableOpacity>
|
|
109
|
+
</View>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEvmAddress, useSolanaAddress, useIsSignedIn } from "@coinbase/cdp-hooks";
|
|
4
|
-
import { Connection, clusterApiUrl, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
|
|
5
|
-
import { useCallback, useEffect, useMemo, useState, lazy, Suspense } from "react";
|
|
6
|
-
import { createPublicClient, http, formatEther } from "viem";
|
|
7
|
-
import { baseSepolia } from "viem/chains";
|
|
8
|
-
|
|
9
|
-
import Header from "@/components/Header";
|
|
10
|
-
import UserBalance from "@/components/UserBalance";
|
|
11
|
-
|
|
12
|
-
const isSolana = process.env.NEXT_PUBLIC_CDP_CREATE_SOLANA_ACCOUNT === "true";
|
|
13
|
-
const isSmartAccount = process.env.NEXT_PUBLIC_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE === "smart";
|
|
14
|
-
|
|
15
|
-
// Dynamically import components based on configuration
|
|
16
|
-
const TransactionComponent = lazy(() => {
|
|
17
|
-
if (isSolana) {
|
|
18
|
-
return import("@/components/SolanaTransaction");
|
|
19
|
-
} else if (isSmartAccount) {
|
|
20
|
-
return import("@/components/SmartAccountTransaction");
|
|
21
|
-
} else {
|
|
22
|
-
return import("@/components/EOATransaction");
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Create a viem client to access user's balance on the Base Sepolia network
|
|
28
|
-
*/
|
|
29
|
-
const client = createPublicClient({
|
|
30
|
-
chain: baseSepolia,
|
|
31
|
-
transport: http(),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Create a Solana connection to access user's balance on Solana Devnet
|
|
36
|
-
*/
|
|
37
|
-
const solanaConnection = new Connection(clusterApiUrl("devnet"));
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* The Signed In screen
|
|
41
|
-
*/
|
|
42
|
-
export default function SignedInScreen() {
|
|
43
|
-
const { isSignedIn } = useIsSignedIn();
|
|
44
|
-
const { evmAddress } = useEvmAddress();
|
|
45
|
-
const { solanaAddress } = useSolanaAddress();
|
|
46
|
-
const [balance, setBalance] = useState<bigint | undefined>(undefined);
|
|
47
|
-
|
|
48
|
-
const address = isSolana ? solanaAddress : evmAddress;
|
|
49
|
-
|
|
50
|
-
const formattedBalance = useMemo(() => {
|
|
51
|
-
if (balance === undefined) return undefined;
|
|
52
|
-
if (isSolana) {
|
|
53
|
-
// Convert lamports to SOL
|
|
54
|
-
return formatSol(Number(balance));
|
|
55
|
-
} else {
|
|
56
|
-
// Convert wei to ETH
|
|
57
|
-
return formatEther(balance);
|
|
58
|
-
}
|
|
59
|
-
}, [balance]);
|
|
60
|
-
|
|
61
|
-
const getBalance = useCallback(async () => {
|
|
62
|
-
if (isSolana && solanaAddress) {
|
|
63
|
-
// Get Solana balance in lamports
|
|
64
|
-
const lamports = await solanaConnection.getBalance(new PublicKey(solanaAddress));
|
|
65
|
-
setBalance(BigInt(lamports));
|
|
66
|
-
} else if (!isSolana && evmAddress) {
|
|
67
|
-
// Get EVM balance in wei
|
|
68
|
-
const weiBalance = await client.getBalance({
|
|
69
|
-
address: evmAddress,
|
|
70
|
-
});
|
|
71
|
-
setBalance(weiBalance);
|
|
72
|
-
}
|
|
73
|
-
}, [evmAddress, solanaAddress]);
|
|
74
|
-
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
getBalance();
|
|
77
|
-
const interval = setInterval(getBalance, 500);
|
|
78
|
-
return () => clearInterval(interval);
|
|
79
|
-
}, [getBalance]);
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<>
|
|
83
|
-
<Header />
|
|
84
|
-
<main className="main flex-col-container flex-grow">
|
|
85
|
-
<div className="main-inner flex-col-container">
|
|
86
|
-
<div className="card card--user-balance">
|
|
87
|
-
<UserBalance
|
|
88
|
-
balance={formattedBalance}
|
|
89
|
-
faucetName={isSolana ? "Solana Faucet" : "Base Sepolia Faucet"}
|
|
90
|
-
faucetUrl={`https://portal.cdp.coinbase.com/products/faucet${isSolana ? "?network=solana-devnet" : ""}`}
|
|
91
|
-
/>
|
|
92
|
-
</div>
|
|
93
|
-
<div className="card card--transaction">
|
|
94
|
-
{isSignedIn && address && (
|
|
95
|
-
<Suspense fallback={<div>Loading transaction component...</div>}>
|
|
96
|
-
<TransactionComponent balance={formattedBalance} onSuccess={getBalance} />
|
|
97
|
-
</Suspense>
|
|
98
|
-
)}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
</main>
|
|
102
|
-
</>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Format a Solana balance.
|
|
108
|
-
*
|
|
109
|
-
* @param lamports - The balance in lamports.
|
|
110
|
-
* @returns The formatted balance.
|
|
111
|
-
*/
|
|
112
|
-
function formatSol(lamports: number) {
|
|
113
|
-
const maxDecimalPlaces = 9;
|
|
114
|
-
const roundedStr = (lamports / LAMPORTS_PER_SOL).toFixed(maxDecimalPlaces);
|
|
115
|
-
return roundedStr.replace(/0+$/, "").replace(/\.$/, "");
|
|
116
|
-
}
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEvmAddress, useIsSignedIn, useSolanaAddress } from "@coinbase/cdp-hooks";
|
|
4
|
-
import { Connection, clusterApiUrl, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
|
|
5
|
-
import { useCallback, useEffect, useMemo, useState, lazy, Suspense } from "react";
|
|
6
|
-
import {
|
|
7
|
-
createPublicClient,
|
|
8
|
-
http,
|
|
9
|
-
formatEther,
|
|
10
|
-
type PublicClient,
|
|
11
|
-
type Transport,
|
|
12
|
-
type Address,
|
|
13
|
-
} from "viem";
|
|
14
|
-
import { baseSepolia, base } from "viem/chains";
|
|
15
|
-
|
|
16
|
-
import FundWallet from "@/components/FundWallet";
|
|
17
|
-
import Header from "@/components/Header";
|
|
18
|
-
import UserBalance from "@/components/UserBalance";
|
|
19
|
-
|
|
20
|
-
// Dynamically determine component path for EVM transactions
|
|
21
|
-
const getEVMComponentPath = () => {
|
|
22
|
-
const isSmartAccount = process.env.NEXT_PUBLIC_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE === "smart";
|
|
23
|
-
|
|
24
|
-
if (isSmartAccount) {
|
|
25
|
-
return "@/components/SmartAccountTransaction";
|
|
26
|
-
} else {
|
|
27
|
-
return "@/components/EOATransaction";
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const EVMTransactionComponent = lazy(() => import(/* @vite-ignore */ getEVMComponentPath()));
|
|
32
|
-
const SolanaTransactionComponent = lazy(() => import("@/components/SolanaTransaction"));
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Create a viem client to access user's balance on the Base network
|
|
36
|
-
*/
|
|
37
|
-
const client = createPublicClient({
|
|
38
|
-
chain: base,
|
|
39
|
-
transport: http(),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Create a viem client to access user's balance on the Base Sepolia network
|
|
44
|
-
*/
|
|
45
|
-
const sepoliaClient = createPublicClient({
|
|
46
|
-
chain: baseSepolia,
|
|
47
|
-
transport: http(),
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Create a Solana connection to access user's balance on Solana Mainnet
|
|
52
|
-
*/
|
|
53
|
-
const solanaMainnetConnection = new Connection(clusterApiUrl("mainnet-beta"));
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Create a Solana connection to access user's balance on Solana Devnet
|
|
57
|
-
*/
|
|
58
|
-
const solanaDevnetConnection = new Connection(clusterApiUrl("devnet"));
|
|
59
|
-
|
|
60
|
-
const useEvmBalance = (
|
|
61
|
-
address: Address | null,
|
|
62
|
-
client: PublicClient<Transport, typeof base | typeof baseSepolia, undefined, undefined>,
|
|
63
|
-
poll = false,
|
|
64
|
-
) => {
|
|
65
|
-
const [balance, setBalance] = useState<bigint | undefined>(undefined);
|
|
66
|
-
|
|
67
|
-
const formattedBalance = useMemo(() => {
|
|
68
|
-
if (balance === undefined) return undefined;
|
|
69
|
-
return formatEther(balance);
|
|
70
|
-
}, [balance]);
|
|
71
|
-
|
|
72
|
-
const getBalance = useCallback(async () => {
|
|
73
|
-
if (!address) return;
|
|
74
|
-
const balance = await client.getBalance({ address });
|
|
75
|
-
setBalance(balance);
|
|
76
|
-
}, [address, client]);
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
if (!poll) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
getBalance();
|
|
83
|
-
const interval = setInterval(getBalance, 500);
|
|
84
|
-
return () => clearInterval(interval);
|
|
85
|
-
}, [getBalance, poll]);
|
|
86
|
-
|
|
87
|
-
return { balance, formattedBalance, getBalance };
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const useSolanaBalance = (address: string | null, connection: Connection, poll = false) => {
|
|
91
|
-
const [balance, setBalance] = useState<bigint | undefined>(undefined);
|
|
92
|
-
|
|
93
|
-
const formattedBalance = useMemo(() => {
|
|
94
|
-
if (balance === undefined) return undefined;
|
|
95
|
-
// Convert lamports to SOL
|
|
96
|
-
return formatSol(Number(balance));
|
|
97
|
-
}, [balance]);
|
|
98
|
-
|
|
99
|
-
const getBalance = useCallback(async () => {
|
|
100
|
-
if (!address) return;
|
|
101
|
-
try {
|
|
102
|
-
const lamports = await connection.getBalance(new PublicKey(address));
|
|
103
|
-
setBalance(BigInt(lamports));
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error("Error fetching Solana balance:", error);
|
|
106
|
-
setBalance(BigInt(0));
|
|
107
|
-
}
|
|
108
|
-
}, [address, connection]);
|
|
109
|
-
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
if (!poll) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
getBalance();
|
|
115
|
-
const interval = setInterval(getBalance, 500);
|
|
116
|
-
return () => clearInterval(interval);
|
|
117
|
-
}, [getBalance, poll]);
|
|
118
|
-
|
|
119
|
-
return { balance, formattedBalance, getBalance };
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Format a Solana balance.
|
|
124
|
-
*
|
|
125
|
-
* @param lamports - The balance in lamports.
|
|
126
|
-
* @returns The formatted balance.
|
|
127
|
-
*/
|
|
128
|
-
function formatSol(lamports: number) {
|
|
129
|
-
const maxDecimalPlaces = 9;
|
|
130
|
-
const roundedStr = (lamports / LAMPORTS_PER_SOL).toFixed(maxDecimalPlaces);
|
|
131
|
-
return roundedStr.replace(/0+$/, "").replace(/\.$/, "");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* The Signed In screen with onramp support for both EVM and Solana
|
|
136
|
-
*/
|
|
137
|
-
export default function SignedInScreen() {
|
|
138
|
-
const { isSignedIn } = useIsSignedIn();
|
|
139
|
-
const { evmAddress } = useEvmAddress();
|
|
140
|
-
const { solanaAddress } = useSolanaAddress();
|
|
141
|
-
|
|
142
|
-
const { formattedBalance, getBalance } = useEvmBalance(evmAddress, client, true);
|
|
143
|
-
const { formattedBalance: formattedBalanceSepolia, getBalance: getBalanceSepolia } =
|
|
144
|
-
useEvmBalance(evmAddress, sepoliaClient, true);
|
|
145
|
-
|
|
146
|
-
const { formattedBalance: formattedBalanceSolana, getBalance: getBalanceSolana } =
|
|
147
|
-
useSolanaBalance(solanaAddress, solanaMainnetConnection);
|
|
148
|
-
const { formattedBalance: formattedBalanceSolanaDevnet, getBalance: getBalanceSolanaDevnet } =
|
|
149
|
-
useSolanaBalance(solanaAddress, solanaDevnetConnection, true);
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<>
|
|
153
|
-
<Header />
|
|
154
|
-
<main className="main flex-col-container flex-grow">
|
|
155
|
-
{evmAddress && (
|
|
156
|
-
<>
|
|
157
|
-
<p className="page-heading">Fund your EVM wallet on Base</p>
|
|
158
|
-
<div className="main-inner flex-col-container">
|
|
159
|
-
<div className="card card--user-balance">
|
|
160
|
-
<UserBalance balance={formattedBalance} />
|
|
161
|
-
</div>
|
|
162
|
-
<div className="card card--transaction">
|
|
163
|
-
{isSignedIn && (
|
|
164
|
-
<FundWallet
|
|
165
|
-
onSuccess={getBalance}
|
|
166
|
-
network="base"
|
|
167
|
-
cryptoCurrency="eth"
|
|
168
|
-
destinationAddress={evmAddress}
|
|
169
|
-
/>
|
|
170
|
-
)}
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
<hr className="page-divider" />
|
|
174
|
-
<p className="page-heading">Send an EVM transaction on Base Sepolia</p>
|
|
175
|
-
<div className="main-inner flex-col-container">
|
|
176
|
-
<div className="card card--user-balance">
|
|
177
|
-
<UserBalance
|
|
178
|
-
balance={formattedBalanceSepolia}
|
|
179
|
-
faucetName="Base Sepolia Faucet"
|
|
180
|
-
faucetUrl="https://portal.cdp.coinbase.com/products/faucet"
|
|
181
|
-
/>
|
|
182
|
-
</div>
|
|
183
|
-
<div className="card card--transaction">
|
|
184
|
-
{isSignedIn && (
|
|
185
|
-
<Suspense fallback={<div>Loading transaction component...</div>}>
|
|
186
|
-
<EVMTransactionComponent
|
|
187
|
-
balance={formattedBalanceSepolia}
|
|
188
|
-
onSuccess={getBalanceSepolia}
|
|
189
|
-
/>
|
|
190
|
-
</Suspense>
|
|
191
|
-
)}
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
</>
|
|
195
|
-
)}
|
|
196
|
-
|
|
197
|
-
{solanaAddress && (
|
|
198
|
-
<>
|
|
199
|
-
{evmAddress && <hr className="page-divider" />}
|
|
200
|
-
<p className="page-heading">Fund your Solana wallet on Mainnet</p>
|
|
201
|
-
<div className="main-inner flex-col-container">
|
|
202
|
-
<div className="card card--user-balance">
|
|
203
|
-
<UserBalance balance={formattedBalanceSolana} />
|
|
204
|
-
</div>
|
|
205
|
-
<div className="card card--transaction">
|
|
206
|
-
{isSignedIn && (
|
|
207
|
-
<FundWallet
|
|
208
|
-
onSuccess={getBalanceSolana}
|
|
209
|
-
network="solana"
|
|
210
|
-
cryptoCurrency="sol"
|
|
211
|
-
destinationAddress={solanaAddress}
|
|
212
|
-
/>
|
|
213
|
-
)}
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
<hr className="page-divider" />
|
|
217
|
-
<p className="page-heading">Send a Solana transaction on Devnet</p>
|
|
218
|
-
<div className="main-inner flex-col-container">
|
|
219
|
-
<div className="card card--user-balance">
|
|
220
|
-
<UserBalance
|
|
221
|
-
balance={formattedBalanceSolanaDevnet}
|
|
222
|
-
faucetName="Solana Devnet Faucet"
|
|
223
|
-
faucetUrl="https://portal.cdp.coinbase.com/products/faucet?network=solana-devnet"
|
|
224
|
-
/>
|
|
225
|
-
</div>
|
|
226
|
-
<div className="card card--transaction">
|
|
227
|
-
{isSignedIn && (
|
|
228
|
-
<Suspense fallback={<div>Loading transaction component...</div>}>
|
|
229
|
-
<SolanaTransactionComponent onSuccess={getBalanceSolanaDevnet} />
|
|
230
|
-
</Suspense>
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
</>
|
|
235
|
-
)}
|
|
236
|
-
</main>
|
|
237
|
-
</>
|
|
238
|
-
);
|
|
239
|
-
}
|