@coinbase/create-cdp-app 0.0.42 → 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.
Files changed (40) hide show
  1. package/dist/index.js +119 -41
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/template-nextjs/src/components/{Header.tsx → evm-eoa/Header.tsx} +0 -4
  5. package/template-nextjs/src/components/evm-eoa/SignedInScreen.tsx +68 -0
  6. package/template-nextjs/src/components/evm-eoa/SignedInScreenWithOnramp.tsx +117 -0
  7. package/template-nextjs/src/components/{UserBalance.tsx → evm-eoa/UserBalance.tsx} +3 -4
  8. package/template-nextjs/src/components/evm-smart/Header.tsx +64 -0
  9. package/template-nextjs/src/components/evm-smart/SignedInScreen.tsx +68 -0
  10. package/template-nextjs/src/components/evm-smart/SignedInScreenWithOnramp.tsx +120 -0
  11. package/template-nextjs/src/components/evm-smart/UserBalance.tsx +43 -0
  12. package/template-nextjs/src/components/solana/Header.tsx +63 -0
  13. package/template-nextjs/src/components/solana/SignedInScreen.tsx +74 -0
  14. package/template-nextjs/src/components/solana/SignedInScreenWithOnramp.tsx +121 -0
  15. package/template-nextjs/src/components/solana/UserBalance.tsx +43 -0
  16. package/template-react/src/evm-eoa/Header.tsx +67 -0
  17. package/template-react/src/evm-eoa/SignedInScreen.tsx +64 -0
  18. package/template-react/src/{UserBalance.tsx → evm-eoa/UserBalance.tsx} +10 -28
  19. package/template-react/src/evm-smart/Header.tsx +68 -0
  20. package/template-react/src/evm-smart/SignedInScreen.tsx +64 -0
  21. package/template-react/src/evm-smart/UserBalance.tsx +44 -0
  22. package/template-react/src/{Header.tsx → solana/Header.tsx} +9 -21
  23. package/template-react/src/solana/SignedInScreen.tsx +70 -0
  24. package/template-react/src/solana/UserBalance.tsx +44 -0
  25. package/template-react-native/{components → evm-eoa}/WalletHeader.tsx +10 -21
  26. package/template-react-native/evm-smart/WalletHeader.tsx +115 -0
  27. package/template-react-native/solana/WalletHeader.tsx +111 -0
  28. package/template-nextjs/src/components/SignedInScreen.tsx +0 -116
  29. package/template-nextjs/src/components/SignedInScreenWithOnramp.tsx +0 -239
  30. package/template-react/src/SignedInScreen.tsx +0 -116
  31. package/template-react-native/Transaction.tsx +0 -101
  32. /package/template-nextjs/src/components/{EOATransaction.tsx → evm-eoa/EOATransaction.tsx} +0 -0
  33. /package/template-nextjs/src/components/{SmartAccountTransaction.tsx → evm-smart/SmartAccountTransaction.tsx} +0 -0
  34. /package/template-nextjs/src/components/{SolanaTransaction.tsx → solana/SolanaTransaction.tsx} +0 -0
  35. /package/template-react/src/{EOATransaction.tsx → evm-eoa/EOATransaction.tsx} +0 -0
  36. /package/template-react/src/{SmartAccountTransaction.tsx → evm-smart/SmartAccountTransaction.tsx} +0 -0
  37. /package/template-react/src/{SolanaTransaction.tsx → solana/SolanaTransaction.tsx} +0 -0
  38. /package/template-react-native/{EOATransaction.tsx → evm-eoa/EOATransaction.tsx} +0 -0
  39. /package/template-react-native/{SmartAccountTransaction.tsx → evm-smart/SmartAccountTransaction.tsx} +0 -0
  40. /package/template-react-native/{SolanaTransaction.tsx → solana/SolanaTransaction.tsx} +0 -0
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import { useIsSignedIn, useSolanaAddress } from "@coinbase/cdp-hooks";
4
+ import { Connection, clusterApiUrl, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
5
+ import { useCallback, useEffect, useMemo, useState } from "react";
6
+
7
+ import Header from "./Header";
8
+ import SolanaTransaction from "./SolanaTransaction";
9
+ import UserBalance from "./UserBalance";
10
+
11
+ import FundWallet from "@/components/FundWallet";
12
+
13
+ /**
14
+ * Create a Solana connection to access user's balance on Solana Mainnet
15
+ */
16
+ const solanaMainnetConnection = new Connection(clusterApiUrl("mainnet-beta"));
17
+
18
+ /**
19
+ * Create a Solana connection to access user's balance on Solana Devnet
20
+ */
21
+ const solanaDevnetConnection = new Connection(clusterApiUrl("devnet"));
22
+
23
+ const useSolanaBalance = (address: string | null, connection: Connection, poll = false) => {
24
+ const [balance, setBalance] = useState<bigint | undefined>(undefined);
25
+
26
+ const formattedBalance = useMemo(() => {
27
+ if (balance === undefined) return undefined;
28
+ // Convert lamports to SOL
29
+ return formatSol(Number(balance));
30
+ }, [balance]);
31
+
32
+ const getBalance = useCallback(async () => {
33
+ if (!address) return;
34
+ try {
35
+ const lamports = await connection.getBalance(new PublicKey(address));
36
+ setBalance(BigInt(lamports));
37
+ } catch (error) {
38
+ console.error("Error fetching Solana balance:", error);
39
+ setBalance(BigInt(0));
40
+ }
41
+ }, [address, connection]);
42
+
43
+ useEffect(() => {
44
+ if (!poll) {
45
+ getBalance();
46
+ return;
47
+ }
48
+ const interval = setInterval(getBalance, 500);
49
+ return () => clearInterval(interval);
50
+ }, [getBalance, poll]);
51
+
52
+ return { balance, formattedBalance, getBalance };
53
+ };
54
+
55
+ /**
56
+ * Format a Solana balance.
57
+ *
58
+ * @param lamports - The balance in lamports.
59
+ * @returns The formatted balance.
60
+ */
61
+ function formatSol(lamports: number) {
62
+ const maxDecimalPlaces = 9;
63
+ const roundedStr = (lamports / LAMPORTS_PER_SOL).toFixed(maxDecimalPlaces);
64
+ return roundedStr.replace(/0+$/, "").replace(/\.$/, "");
65
+ }
66
+
67
+ /**
68
+ * The Signed In screen with onramp support
69
+ */
70
+ export default function SignedInScreen() {
71
+ const { isSignedIn } = useIsSignedIn();
72
+ const { solanaAddress } = useSolanaAddress();
73
+
74
+ const { formattedBalance: formattedBalanceSolana, getBalance: getBalanceSolana } =
75
+ useSolanaBalance(solanaAddress, solanaMainnetConnection);
76
+ const { formattedBalance: formattedBalanceSolanaDevnet, getBalance: getBalanceSolanaDevnet } =
77
+ useSolanaBalance(solanaAddress, solanaDevnetConnection, true);
78
+
79
+ return (
80
+ <>
81
+ <Header />
82
+ <main className="main flex-col-container flex-grow">
83
+ <p className="page-heading">Fund your Solana wallet on Mainnet</p>
84
+ <div className="main-inner flex-col-container">
85
+ <div className="card card--user-balance">
86
+ <UserBalance balance={formattedBalanceSolana} />
87
+ </div>
88
+ <div className="card card--transaction">
89
+ {isSignedIn && (
90
+ <FundWallet
91
+ onSuccess={getBalanceSolana}
92
+ network="solana"
93
+ cryptoCurrency="sol"
94
+ destinationAddress={solanaAddress}
95
+ />
96
+ )}
97
+ </div>
98
+ </div>
99
+ <hr className="page-divider" />
100
+ <p className="page-heading">Send a Solana transaction on Devnet</p>
101
+ <div className="main-inner flex-col-container">
102
+ <div className="card card--user-balance">
103
+ <UserBalance
104
+ balance={formattedBalanceSolanaDevnet}
105
+ faucetName="Solana Devnet Faucet"
106
+ faucetUrl="https://portal.cdp.coinbase.com/products/faucet?network=solana-devnet"
107
+ />
108
+ </div>
109
+ <div className="card card--transaction">
110
+ {isSignedIn && (
111
+ <SolanaTransaction
112
+ balance={formattedBalanceSolanaDevnet}
113
+ onSuccess={getBalanceSolanaDevnet}
114
+ />
115
+ )}
116
+ </div>
117
+ </div>
118
+ </main>
119
+ </>
120
+ );
121
+ }
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { LoadingSkeleton } from "@coinbase/cdp-react/components/ui/LoadingSkeleton";
3
+
4
+ interface Props {
5
+ balance?: string;
6
+ faucetUrl?: string;
7
+ faucetName?: string;
8
+ }
9
+
10
+ /**
11
+ * A component that displays the user's balance.
12
+ *
13
+ * @param {Props} props - The props for the UserBalance component.
14
+ * @param {string} [props.balance] - The user's balance.
15
+ * @returns A component that displays the user's balance.
16
+ */
17
+ export default function UserBalance(props: Props) {
18
+ const { balance, faucetUrl, faucetName } = props;
19
+
20
+ return (
21
+ <>
22
+ <h2 className="card-title">Available balance</h2>
23
+ <p className="user-balance flex-col-container flex-grow">
24
+ {balance === undefined && <LoadingSkeleton as="span" className="loading--balance" />}
25
+ {balance !== undefined && (
26
+ <span className="flex-row-container">
27
+ <img src="/sol.svg" alt="" className="balance-icon" />
28
+ <span>{balance}</span>
29
+ <span className="sr-only">Solana</span>
30
+ </span>
31
+ )}
32
+ </p>
33
+ {faucetUrl && faucetName && (
34
+ <p>
35
+ Get testnet SOL from{" "}
36
+ <a href={faucetUrl} target="_blank" rel="noopener noreferrer">
37
+ {faucetName}
38
+ </a>
39
+ </p>
40
+ )}
41
+ </>
42
+ );
43
+ }
@@ -0,0 +1,67 @@
1
+ import { useEvmAddress } from "@coinbase/cdp-hooks";
2
+ import { AuthButton } from "@coinbase/cdp-react/components/AuthButton";
3
+ import { useCallback, useEffect, useState } from "react";
4
+
5
+ import { IconCheck, IconCopy, IconUser } from "./Icons";
6
+
7
+ /**
8
+ * Header component
9
+ */
10
+ function Header() {
11
+ const { evmAddress } = useEvmAddress();
12
+ const [isCopied, setIsCopied] = useState(false);
13
+
14
+ const formatAddress = useCallback((address: string) => {
15
+ if (!address) return "";
16
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
17
+ }, []);
18
+
19
+ const copyAddress = async () => {
20
+ if (!evmAddress) return;
21
+ try {
22
+ await navigator.clipboard.writeText(evmAddress);
23
+ setIsCopied(true);
24
+ } catch (error) {
25
+ console.error(error);
26
+ }
27
+ };
28
+
29
+ useEffect(() => {
30
+ if (!isCopied) return;
31
+ const timeout = setTimeout(() => {
32
+ setIsCopied(false);
33
+ }, 2000);
34
+ return () => clearTimeout(timeout);
35
+ }, [isCopied]);
36
+
37
+ return (
38
+ <header>
39
+ <div className="header-inner">
40
+ <div className="title-container">
41
+ <h1 className="site-title">CDP React StarterKit</h1>
42
+ </div>
43
+ <div className="user-info flex-row-container">
44
+ {evmAddress && (
45
+ <button
46
+ aria-label="copy wallet address"
47
+ className="flex-row-container copy-address-button"
48
+ onClick={copyAddress}
49
+ >
50
+ {!isCopied && (
51
+ <>
52
+ <IconUser className="user-icon user-icon--user" />
53
+ <IconCopy className="user-icon user-icon--copy" />
54
+ </>
55
+ )}
56
+ {isCopied && <IconCheck className="user-icon user-icon--check" />}
57
+ <span className="wallet-address">{formatAddress(evmAddress)}</span>
58
+ </button>
59
+ )}
60
+ <AuthButton />
61
+ </div>
62
+ </div>
63
+ </header>
64
+ );
65
+ }
66
+
67
+ export default Header;
@@ -0,0 +1,64 @@
1
+ import { useEvmAddress, useIsSignedIn } from "@coinbase/cdp-hooks";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { createPublicClient, http, formatEther } from "viem";
4
+ import { baseSepolia } from "viem/chains";
5
+
6
+ import EOATransaction from "./EOATransaction";
7
+ import Header from "./Header";
8
+ import UserBalance from "./UserBalance";
9
+
10
+ /**
11
+ * Create a viem client to access user's balance on the Base Sepolia network
12
+ */
13
+ const client = createPublicClient({
14
+ chain: baseSepolia,
15
+ transport: http(),
16
+ });
17
+
18
+ /**
19
+ * The Signed In screen
20
+ */
21
+ function SignedInScreen() {
22
+ const { isSignedIn } = useIsSignedIn();
23
+ const { evmAddress } = useEvmAddress();
24
+ const [balance, setBalance] = useState<bigint | undefined>(undefined);
25
+
26
+ const formattedBalance = useMemo(() => {
27
+ if (balance === undefined) return undefined;
28
+ return formatEther(balance);
29
+ }, [balance]);
30
+
31
+ const getBalance = useCallback(async () => {
32
+ if (!evmAddress) return;
33
+ const weiBalance = await client.getBalance({
34
+ address: evmAddress,
35
+ });
36
+ setBalance(weiBalance);
37
+ }, [evmAddress]);
38
+
39
+ useEffect(() => {
40
+ getBalance();
41
+ const interval = setInterval(getBalance, 500);
42
+ return () => clearInterval(interval);
43
+ }, [getBalance]);
44
+
45
+ return (
46
+ <>
47
+ <Header />
48
+ <main className="main flex-col-container flex-grow">
49
+ <div className="main-inner flex-col-container">
50
+ <div className="card card--user-balance">
51
+ <UserBalance balance={formattedBalance} />
52
+ </div>
53
+ <div className="card card--transaction">
54
+ {isSignedIn && evmAddress && (
55
+ <EOATransaction balance={formattedBalance} onSuccess={getBalance} />
56
+ )}
57
+ </div>
58
+ </div>
59
+ </main>
60
+ </>
61
+ );
62
+ }
63
+
64
+ export default SignedInScreen;
@@ -1,7 +1,5 @@
1
1
  import { LoadingSkeleton } from "@coinbase/cdp-react/components/ui/LoadingSkeleton";
2
2
 
3
- import { CDP_CONFIG } from "./config";
4
-
5
3
  interface Props {
6
4
  balance?: string;
7
5
  }
@@ -15,7 +13,6 @@ interface Props {
15
13
  */
16
14
  function UserBalance(props: Props) {
17
15
  const { balance } = props;
18
- const isSolana = !!CDP_CONFIG.solana;
19
16
 
20
17
  return (
21
18
  <>
@@ -24,36 +21,21 @@ function UserBalance(props: Props) {
24
21
  {balance === undefined && <LoadingSkeleton as="span" className="loading--balance" />}
25
22
  {balance !== undefined && (
26
23
  <span className="flex-row-container">
27
- <img src={isSolana ? "/sol.svg" : "/eth.svg"} alt="" className="balance-icon" />
24
+ <img src="/eth.svg" alt="" className="balance-icon" />
28
25
  <span>{balance}</span>
29
- <span className="sr-only">{isSolana ? "Solana" : "Ethereum"}</span>
26
+ <span className="sr-only">Ethereum</span>
30
27
  </span>
31
28
  )}
32
29
  </p>
33
30
  <p>
34
- {isSolana ? (
35
- <>
36
- Get testnet SOL from{" "}
37
- <a
38
- href="https://portal.cdp.coinbase.com/products/faucet?network=solana-devnet"
39
- target="_blank"
40
- rel="noopener noreferrer"
41
- >
42
- Solana Devnet Faucet
43
- </a>
44
- </>
45
- ) : (
46
- <>
47
- Get testnet ETH from{" "}
48
- <a
49
- href="https://portal.cdp.coinbase.com/products/faucet"
50
- target="_blank"
51
- rel="noopener noreferrer"
52
- >
53
- Base Sepolia Faucet
54
- </a>
55
- </>
56
- )}
31
+ Get testnet ETH from{" "}
32
+ <a
33
+ href="https://portal.cdp.coinbase.com/products/faucet"
34
+ target="_blank"
35
+ rel="noopener noreferrer"
36
+ >
37
+ Base Sepolia Faucet
38
+ </a>
57
39
  </p>
58
40
  </>
59
41
  );
@@ -0,0 +1,68 @@
1
+ import { useEvmAddress } from "@coinbase/cdp-hooks";
2
+ import { AuthButton } from "@coinbase/cdp-react/components/AuthButton";
3
+ import { useCallback, useEffect, useState } from "react";
4
+
5
+ import { IconCheck, IconCopy, IconUser } from "./Icons";
6
+
7
+ /**
8
+ * Header component
9
+ */
10
+ function Header() {
11
+ const { evmAddress } = useEvmAddress();
12
+ const [isCopied, setIsCopied] = useState(false);
13
+
14
+ const formatAddress = useCallback((address: string) => {
15
+ if (!address) return "";
16
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
17
+ }, []);
18
+
19
+ const copyAddress = async () => {
20
+ if (!evmAddress) return;
21
+ try {
22
+ await navigator.clipboard.writeText(evmAddress);
23
+ setIsCopied(true);
24
+ } catch (error) {
25
+ console.error(error);
26
+ }
27
+ };
28
+
29
+ useEffect(() => {
30
+ if (!isCopied) return;
31
+ const timeout = setTimeout(() => {
32
+ setIsCopied(false);
33
+ }, 2000);
34
+ return () => clearTimeout(timeout);
35
+ }, [isCopied]);
36
+
37
+ return (
38
+ <header>
39
+ <div className="header-inner">
40
+ <div className="title-container">
41
+ <h1 className="site-title">CDP React StarterKit</h1>
42
+ <span className="smart-badge">SMART</span>
43
+ </div>
44
+ <div className="user-info flex-row-container">
45
+ {evmAddress && (
46
+ <button
47
+ aria-label="copy wallet address"
48
+ className="flex-row-container copy-address-button"
49
+ onClick={copyAddress}
50
+ >
51
+ {!isCopied && (
52
+ <>
53
+ <IconUser className="user-icon user-icon--user" />
54
+ <IconCopy className="user-icon user-icon--copy" />
55
+ </>
56
+ )}
57
+ {isCopied && <IconCheck className="user-icon user-icon--check" />}
58
+ <span className="wallet-address">{formatAddress(evmAddress)}</span>
59
+ </button>
60
+ )}
61
+ <AuthButton />
62
+ </div>
63
+ </div>
64
+ </header>
65
+ );
66
+ }
67
+
68
+ export default Header;
@@ -0,0 +1,64 @@
1
+ import { useEvmAddress, useIsSignedIn } from "@coinbase/cdp-hooks";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { createPublicClient, http, formatEther } from "viem";
4
+ import { baseSepolia } from "viem/chains";
5
+
6
+ import Header from "./Header";
7
+ import SmartAccountTransaction from "./SmartAccountTransaction";
8
+ import UserBalance from "./UserBalance";
9
+
10
+ /**
11
+ * Create a viem client to access user's balance on the Base Sepolia network
12
+ */
13
+ const client = createPublicClient({
14
+ chain: baseSepolia,
15
+ transport: http(),
16
+ });
17
+
18
+ /**
19
+ * The Signed In screen
20
+ */
21
+ function SignedInScreen() {
22
+ const { isSignedIn } = useIsSignedIn();
23
+ const { evmAddress } = useEvmAddress();
24
+ const [balance, setBalance] = useState<bigint | undefined>(undefined);
25
+
26
+ const formattedBalance = useMemo(() => {
27
+ if (balance === undefined) return undefined;
28
+ return formatEther(balance);
29
+ }, [balance]);
30
+
31
+ const getBalance = useCallback(async () => {
32
+ if (!evmAddress) return;
33
+ const weiBalance = await client.getBalance({
34
+ address: evmAddress,
35
+ });
36
+ setBalance(weiBalance);
37
+ }, [evmAddress]);
38
+
39
+ useEffect(() => {
40
+ getBalance();
41
+ const interval = setInterval(getBalance, 500);
42
+ return () => clearInterval(interval);
43
+ }, [getBalance]);
44
+
45
+ return (
46
+ <>
47
+ <Header />
48
+ <main className="main flex-col-container flex-grow">
49
+ <div className="main-inner flex-col-container">
50
+ <div className="card card--user-balance">
51
+ <UserBalance balance={formattedBalance} />
52
+ </div>
53
+ <div className="card card--transaction">
54
+ {isSignedIn && evmAddress && (
55
+ <SmartAccountTransaction balance={formattedBalance} onSuccess={getBalance} />
56
+ )}
57
+ </div>
58
+ </div>
59
+ </main>
60
+ </>
61
+ );
62
+ }
63
+
64
+ export default SignedInScreen;
@@ -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="/eth.svg" alt="" className="balance-icon" />
25
+ <span>{balance}</span>
26
+ <span className="sr-only">Ethereum</span>
27
+ </span>
28
+ )}
29
+ </p>
30
+ <p>
31
+ Get testnet ETH from{" "}
32
+ <a
33
+ href="https://portal.cdp.coinbase.com/products/faucet"
34
+ target="_blank"
35
+ rel="noopener noreferrer"
36
+ >
37
+ Base Sepolia Faucet
38
+ </a>
39
+ </p>
40
+ </>
41
+ );
42
+ }
43
+
44
+ export default UserBalance;
@@ -1,34 +1,25 @@
1
- import { useEvmAddress, useSolanaAddress } from "@coinbase/cdp-hooks";
1
+ import { useSolanaAddress } from "@coinbase/cdp-hooks";
2
2
  import { AuthButton } from "@coinbase/cdp-react/components/AuthButton";
3
3
  import { useCallback, useEffect, useState } from "react";
4
4
 
5
- import { CDP_CONFIG } from "./config";
6
5
  import { IconCheck, IconCopy, IconUser } from "./Icons";
7
6
 
8
7
  /**
9
8
  * Header component
10
9
  */
11
10
  function Header() {
12
- const { evmAddress } = useEvmAddress();
13
11
  const { solanaAddress } = useSolanaAddress();
14
- const isSolana = !!CDP_CONFIG.solana;
15
- const address = isSolana ? solanaAddress : evmAddress;
16
12
  const [isCopied, setIsCopied] = useState(false);
17
13
 
18
- const formatAddress = useCallback(
19
- (address: string) => {
20
- if (!address) return "";
21
- return isSolana
22
- ? `${address.slice(0, 4)}...${address.slice(-4)}`
23
- : `${address.slice(0, 6)}...${address.slice(-4)}`;
24
- },
25
- [isSolana],
26
- );
14
+ const formatAddress = useCallback((address: string) => {
15
+ if (!address) return "";
16
+ return `${address.slice(0, 4)}...${address.slice(-4)}`;
17
+ }, []);
27
18
 
28
19
  const copyAddress = async () => {
29
- if (!address) return;
20
+ if (!solanaAddress) return;
30
21
  try {
31
- await navigator.clipboard.writeText(address);
22
+ await navigator.clipboard.writeText(solanaAddress);
32
23
  setIsCopied(true);
33
24
  } catch (error) {
34
25
  console.error(error);
@@ -43,17 +34,14 @@ function Header() {
43
34
  return () => clearTimeout(timeout);
44
35
  }, [isCopied]);
45
36
 
46
- const isSmartAccountsEnabled = import.meta.env.VITE_CDP_CREATE_ETHEREUM_ACCOUNT_TYPE === "smart";
47
-
48
37
  return (
49
38
  <header>
50
39
  <div className="header-inner">
51
40
  <div className="title-container">
52
41
  <h1 className="site-title">CDP React StarterKit</h1>
53
- {isSmartAccountsEnabled && <span className="smart-badge">SMART</span>}
54
42
  </div>
55
43
  <div className="user-info flex-row-container">
56
- {address && (
44
+ {solanaAddress && (
57
45
  <button
58
46
  aria-label="copy wallet address"
59
47
  className="flex-row-container copy-address-button"
@@ -66,7 +54,7 @@ function Header() {
66
54
  </>
67
55
  )}
68
56
  {isCopied && <IconCheck className="user-icon user-icon--check" />}
69
- <span className="wallet-address">{formatAddress(address)}</span>
57
+ <span className="wallet-address">{formatAddress(solanaAddress)}</span>
70
58
  </button>
71
59
  )}
72
60
  <AuthButton />
@@ -0,0 +1,70 @@
1
+ import { useIsSignedIn, useSolanaAddress } from "@coinbase/cdp-hooks";
2
+ import { Connection, clusterApiUrl, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+
5
+ import Header from "./Header";
6
+ import SolanaTransaction from "./SolanaTransaction";
7
+ import UserBalance from "./UserBalance";
8
+
9
+ /**
10
+ * Create a Solana connection to access user's balance on Solana Devnet
11
+ */
12
+ const solanaConnection = new Connection(clusterApiUrl("devnet"));
13
+
14
+ /**
15
+ * The Signed In screen
16
+ */
17
+ function SignedInScreen() {
18
+ const { isSignedIn } = useIsSignedIn();
19
+ const { solanaAddress } = useSolanaAddress();
20
+ const [balance, setBalance] = useState<bigint | undefined>(undefined);
21
+
22
+ const formattedBalance = useMemo(() => {
23
+ if (balance === undefined) return undefined;
24
+ return formatSol(Number(balance));
25
+ }, [balance]);
26
+
27
+ const getBalance = useCallback(async () => {
28
+ if (!solanaAddress) return;
29
+ const lamports = await solanaConnection.getBalance(new PublicKey(solanaAddress));
30
+ setBalance(BigInt(lamports));
31
+ }, [solanaAddress]);
32
+
33
+ useEffect(() => {
34
+ getBalance();
35
+ const interval = setInterval(getBalance, 500);
36
+ return () => clearInterval(interval);
37
+ }, [getBalance]);
38
+
39
+ return (
40
+ <>
41
+ <Header />
42
+ <main className="main flex-col-container flex-grow">
43
+ <div className="main-inner flex-col-container">
44
+ <div className="card card--user-balance">
45
+ <UserBalance balance={formattedBalance} />
46
+ </div>
47
+ <div className="card card--transaction">
48
+ {isSignedIn && solanaAddress && (
49
+ <SolanaTransaction balance={formattedBalance} onSuccess={getBalance} />
50
+ )}
51
+ </div>
52
+ </div>
53
+ </main>
54
+ </>
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Format a Solana balance.
60
+ *
61
+ * @param lamports - The balance in lamports.
62
+ * @returns The formatted balance.
63
+ */
64
+ function formatSol(lamports: number) {
65
+ const maxDecimalPlaces = 9;
66
+ const roundedStr = (lamports / LAMPORTS_PER_SOL).toFixed(maxDecimalPlaces);
67
+ return roundedStr.replace(/0+$/, "").replace(/\.$/, "");
68
+ }
69
+
70
+ export default SignedInScreen;