@coinbase/create-cdp-app 0.0.43 → 0.0.45

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