@coinbase/create-cdp-app 0.0.7 → 0.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/create-cdp-app",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Vite + React + TS</title>
7
+ <title>CDP React StarterKit</title>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
@@ -10,11 +10,12 @@
10
10
  "preview": "vite preview"
11
11
  },
12
12
  "dependencies": {
13
+ "@coinbase/cdp-core": "latest",
14
+ "@coinbase/cdp-hooks": "latest",
15
+ "@coinbase/cdp-react": "latest",
13
16
  "react": "^19.1.0",
14
17
  "react-dom": "^19.1.0",
15
- "@coinbase/cdp-react": "latest",
16
- "@coinbase/cdp-hooks": "latest",
17
- "@coinbase/cdp-core": "latest"
18
+ "viem": "^2.33.0"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@eslint/js": "^9.30.1",
@@ -29,4 +30,4 @@
29
30
  "typescript-eslint": "^8.35.1",
30
31
  "vite": "^7.0.4"
31
32
  }
32
- }
33
+ }
@@ -0,0 +1,25 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="32"
4
+ height="32"
5
+ viewBox="0 0 32 32"
6
+ aria-hidden="true"
7
+ >
8
+ <g fill="none" fillRule="evenodd">
9
+ <circle cx="16" cy="16" r="16" fill="#627EEA" />
10
+ <g fill="#FFF" fillRule="nonzero">
11
+ <path fillOpacity=".602" d="M16.498 4v8.87l7.497 3.35z" />
12
+ <path d="M16.498 4L9 16.22l7.498-3.35z" />
13
+ <path
14
+ fillOpacity=".602"
15
+ d="M16.498 21.968v6.027L24 17.616z"
16
+ />
17
+ <path d="M16.498 27.995v-6.028L9 17.616z" />
18
+ <path
19
+ fillOpacity=".2"
20
+ d="M16.498 20.573l7.497-4.353-7.497-3.348z"
21
+ />
22
+ <path fillOpacity=".602" d="M9 16.22l7.498 4.353v-7.701z" />
23
+ </g>
24
+ </g>
25
+ </svg>
@@ -1,7 +1,8 @@
1
- import { useIsInitialized, useIsSignedIn, useEvmAddress } from "@coinbase/cdp-hooks";
2
- import { AuthButton } from "@coinbase/cdp-react";
1
+ import { useIsInitialized, useIsSignedIn } from "@coinbase/cdp-hooks";
3
2
 
4
- import Transaction from "./Transaction";
3
+ import Loading from "./Loading";
4
+ import SignedInScreen from "./SignedInScreen";
5
+ import SignInScreen from "./SignInScreen";
5
6
 
6
7
  /**
7
8
  * This component how to use the useIsIntialized, useEvmAddress, and useIsSignedIn hooks.
@@ -9,17 +10,15 @@ import Transaction from "./Transaction";
9
10
  */
10
11
  function App() {
11
12
  const isInitialized = useIsInitialized();
12
- const evmAddress = useEvmAddress();
13
13
  const isSignedIn = useIsSignedIn();
14
14
 
15
15
  return (
16
- <div className="app">
17
- <h1>CDP React Demo</h1>
18
- {!isInitialized && <div>Loading...</div>}
16
+ <div className="app flex-col-container flex-grow">
17
+ {!isInitialized && <Loading />}
19
18
  {isInitialized && (
20
19
  <>
21
- <AuthButton />
22
- {isSignedIn && evmAddress && <Transaction />}
20
+ {!isSignedIn && <SignInScreen />}
21
+ {isSignedIn && <SignedInScreen />}
23
22
  </>
24
23
  )}
25
24
  </div>
@@ -0,0 +1,62 @@
1
+ import { useEvmAddress } from "@coinbase/cdp-hooks";
2
+ import { AuthButton } from "@coinbase/cdp-react/components/AuthButton";
3
+ import { 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 copyAddress = async () => {
15
+ if (!evmAddress) return;
16
+ try {
17
+ await navigator.clipboard.writeText(evmAddress);
18
+ setIsCopied(true);
19
+ } catch (error) {
20
+ console.error(error);
21
+ }
22
+ };
23
+
24
+ useEffect(() => {
25
+ if (!isCopied) return;
26
+ const timeout = setTimeout(() => {
27
+ setIsCopied(false);
28
+ }, 2000);
29
+ return () => clearTimeout(timeout);
30
+ }, [isCopied]);
31
+
32
+ return (
33
+ <header>
34
+ <div className="header-inner">
35
+ <h1 className="site-title">CDP React StarterKit</h1>
36
+ <div className="user-info flex-row-container">
37
+ {evmAddress && (
38
+ <button
39
+ aria-label="copy wallet address"
40
+ className="flex-row-container copy-address-button"
41
+ onClick={copyAddress}
42
+ >
43
+ {!isCopied && (
44
+ <>
45
+ <IconUser className="user-icon user-icon--user" />
46
+ <IconCopy className="user-icon user-icon--copy" />
47
+ </>
48
+ )}
49
+ {isCopied && <IconCheck className="user-icon user-icon--check" />}
50
+ <span className="wallet-address">
51
+ {evmAddress.slice(0, 6)}...{evmAddress.slice(-4)}
52
+ </span>
53
+ </button>
54
+ )}
55
+ <AuthButton />
56
+ </div>
57
+ </div>
58
+ </header>
59
+ );
60
+ }
61
+
62
+ export default Header;
@@ -0,0 +1,66 @@
1
+ import { type ReactNode, type SVGProps } from "react";
2
+
3
+ const SvgIcon = ({ children, ...props }: SVGProps<SVGSVGElement> & { children: ReactNode }) => {
4
+ return (
5
+ <svg
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ fill="currentColor"
8
+ role="img"
9
+ aria-hidden={props["aria-label"] ? undefined : true}
10
+ {...props}
11
+ >
12
+ {children}
13
+ </svg>
14
+ );
15
+ };
16
+
17
+ /**
18
+ * Check icon
19
+ *
20
+ * @param props - SVG props
21
+ * @returns SVG element
22
+ */
23
+ export const IconCheck = (props: Omit<SVGProps<SVGSVGElement>, "viewBox">) => {
24
+ return (
25
+ <SvgIcon width="24" height="24" viewBox="0 0 24 24" {...props}>
26
+ <path
27
+ fillRule="evenodd"
28
+ d="M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z"
29
+ clipRule="evenodd"
30
+ />
31
+ </SvgIcon>
32
+ );
33
+ };
34
+
35
+ /**
36
+ * Copy icon
37
+ *
38
+ * @param props - SVG props
39
+ * @returns SVG element
40
+ */
41
+ export const IconCopy = (props: Omit<SVGProps<SVGSVGElement>, "viewBox">) => {
42
+ return (
43
+ <SvgIcon width="24" height="24" viewBox="0 0 24 24" {...props}>
44
+ <path d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z" />
45
+ <path d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z" />
46
+ </SvgIcon>
47
+ );
48
+ };
49
+
50
+ /**
51
+ * User icon
52
+ *
53
+ * @param props - SVG props
54
+ * @returns SVG element
55
+ */
56
+ export const IconUser = (props: Omit<SVGProps<SVGSVGElement>, "viewBox">) => {
57
+ return (
58
+ <SvgIcon width="24" height="24" viewBox="0 0 24 24" {...props}>
59
+ <path
60
+ fillRule="evenodd"
61
+ d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"
62
+ clipRule="evenodd"
63
+ />
64
+ </SvgIcon>
65
+ );
66
+ };
@@ -0,0 +1,15 @@
1
+ import { LoadingSpinner } from "@coinbase/cdp-react/components/LoadingSpinner";
2
+
3
+ /**
4
+ * App loading screen
5
+ */
6
+ function Loading() {
7
+ return (
8
+ <main>
9
+ <h1 className="sr-only">Loading</h1>
10
+ <LoadingSpinner />
11
+ </main>
12
+ );
13
+ }
14
+
15
+ export default Loading;
@@ -0,0 +1,17 @@
1
+ import { AuthButton } from "@coinbase/cdp-react/components/AuthButton";
2
+
3
+ /**
4
+ * The Sign In screen
5
+ */
6
+ function SignInScreen() {
7
+ return (
8
+ <main className="card card--login">
9
+ <h1 className="sr-only">Sign in</h1>
10
+ <p className="card-title">Welcome!</p>
11
+ <p>Please sign in to continue.</p>
12
+ <AuthButton />
13
+ </main>
14
+ );
15
+ }
16
+
17
+ export default SignInScreen;
@@ -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 Transaction from "./Transaction";
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 balance = await client.getBalance({
34
+ address: evmAddress,
35
+ });
36
+ setBalance(balance);
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
+ <Transaction balance={formattedBalance} onSuccess={getBalance} />
56
+ )}
57
+ </div>
58
+ </div>
59
+ </main>
60
+ </>
61
+ );
62
+ }
63
+
64
+ export default SignedInScreen;
@@ -1,14 +1,31 @@
1
1
  import { useSendEvmTransaction, useEvmAddress } from "@coinbase/cdp-hooks";
2
- import { type MouseEvent, useCallback, useState } from "react";
2
+ import { Button } from "@coinbase/cdp-react/components/Button";
3
+ import { LoadingSkeleton } from "@coinbase/cdp-react/components/LoadingSkeleton";
4
+ import { type MouseEvent, useCallback, useMemo, useState } from "react";
5
+
6
+ interface Props {
7
+ balance?: string;
8
+ onSuccess?: () => void;
9
+ }
3
10
 
4
11
  /**
5
12
  * This component demonstrates how to send an EVM transaction using the CDP hooks.
13
+ *
14
+ * @param {Props} props - The props for the Transaction component.
15
+ * @param {string} [props.balance] - The user's balance.
16
+ * @param {() => void} [props.onSuccess] - A function to call when the transaction is successful.
17
+ * @returns A component that displays a transaction form and a transaction hash.
6
18
  */
7
- function Transaction() {
19
+ function Transaction(props: Props) {
20
+ const { balance, onSuccess } = props;
8
21
  const sendEvmTransaction = useSendEvmTransaction();
9
22
  const evmAddress = useEvmAddress();
10
23
 
24
+ const [isPending, setIsPending] = useState(false);
11
25
  const [transactionHash, setTransactionHash] = useState<string | null>(null);
26
+ const hasBalance = useMemo(() => {
27
+ return balance && balance !== "0";
28
+ }, [balance]);
12
29
 
13
30
  const handleSendTransaction = useCallback(
14
31
  async (e: MouseEvent<HTMLButtonElement>) => {
@@ -17,6 +34,7 @@ function Transaction() {
17
34
  }
18
35
 
19
36
  e.preventDefault();
37
+ setIsPending(true);
20
38
 
21
39
  const { transactionHash } = await sendEvmTransaction({
22
40
  transaction: {
@@ -31,51 +49,80 @@ function Transaction() {
31
49
  });
32
50
 
33
51
  setTransactionHash(transactionHash);
52
+ setIsPending(false);
53
+ onSuccess?.();
34
54
  },
35
- [evmAddress, sendEvmTransaction],
55
+ [evmAddress, sendEvmTransaction, onSuccess],
36
56
  );
37
57
 
38
58
  return (
39
- <div className="transaction-form">
40
- <h2>Wallet Address</h2>
41
- <span className="wallet-address">{evmAddress}</span>
42
- <p>
43
- Get testnet ETH from{" "}
44
- <a
45
- href="https://portal.cdp.coinbase.com/products/faucet"
46
- target="_blank"
47
- rel="noopener noreferrer"
48
- >
49
- Base Sepolia Faucet
50
- </a>
51
- </p>
52
- {!transactionHash && (
59
+ <>
60
+ {balance === undefined && (
53
61
  <>
54
- <h2>Send 0.0001 ETH to yourself on Base Sepolia</h2>
55
- <button className="button" onClick={handleSendTransaction}>
56
- Send Transaction
57
- </button>
62
+ <h2 className="card-title">Send a transaction</h2>
63
+ <LoadingSkeleton className="loading--text" />
64
+ <LoadingSkeleton className="loading--btn" />
58
65
  </>
59
66
  )}
60
- {transactionHash && (
61
- <div className="transaction-result">
62
- <h2>Transaction Sent</h2>
63
- <p>
64
- Transaction Hash:{" "}
65
- <a
66
- href={`https://sepolia.basescan.org/tx/${transactionHash}`}
67
- target="_blank"
68
- rel="noopener noreferrer"
69
- >
70
- {transactionHash}
71
- </a>
72
- </p>
73
- <button className="button" onClick={() => setTransactionHash(null)}>
74
- Send Another Transaction
75
- </button>
76
- </div>
67
+ {balance !== undefined && (
68
+ <>
69
+ {!transactionHash && (
70
+ <>
71
+ <h2 className="card-title">Send a transaction</h2>
72
+ {hasBalance && (
73
+ <>
74
+ <p>Send 0.000001 ETH to yourself on Base Sepolia</p>
75
+ <Button
76
+ className="tx-button"
77
+ onClick={handleSendTransaction}
78
+ isPending={isPending}
79
+ >
80
+ Send Transaction
81
+ </Button>
82
+ </>
83
+ )}
84
+ {!hasBalance && (
85
+ <>
86
+ <p>You need ETH to send a transaction, but you have none.</p>
87
+ <p>
88
+ Get some from{" "}
89
+ <a
90
+ href="https://portal.cdp.coinbase.com/products/faucet"
91
+ target="_blank"
92
+ rel="noopener noreferrer"
93
+ >
94
+ Base Sepolia Faucet
95
+ </a>
96
+ </p>
97
+ </>
98
+ )}
99
+ </>
100
+ )}
101
+ {transactionHash && (
102
+ <>
103
+ <h2 className="card-title">Transaction sent</h2>
104
+ <p>
105
+ Transaction hash:{" "}
106
+ <a
107
+ href={`https://sepolia.basescan.org/tx/${transactionHash}`}
108
+ target="_blank"
109
+ rel="noopener noreferrer"
110
+ >
111
+ {transactionHash.slice(0, 6)}...{transactionHash.slice(-4)}
112
+ </a>
113
+ </p>
114
+ <Button
115
+ variant="secondary"
116
+ className="tx-button"
117
+ onClick={() => setTransactionHash(null)}
118
+ >
119
+ Send another transaction
120
+ </Button>
121
+ </>
122
+ )}
123
+ </>
77
124
  )}
78
- </div>
125
+ </>
79
126
  );
80
127
  }
81
128
 
@@ -0,0 +1,43 @@
1
+ import { LoadingSkeleton } from "@coinbase/cdp-react/components/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
+ return (
17
+ <>
18
+ <h2 className="card-title">Available balance</h2>
19
+ <p className="user-balance flex-col-container flex-grow">
20
+ {balance === undefined && <LoadingSkeleton as="span" className="loading--balance" />}
21
+ {balance !== undefined && (
22
+ <span className="flex-row-container">
23
+ <img src="/eth.svg" alt="" className="balance-icon" />
24
+ <span>{balance}</span>
25
+ <span className="sr-only">Ethereum</span>
26
+ </span>
27
+ )}
28
+ </p>
29
+ <p>
30
+ Get testnet ETH from{" "}
31
+ <a
32
+ href="https://portal.cdp.coinbase.com/products/faucet"
33
+ target="_blank"
34
+ rel="noopener noreferrer"
35
+ >
36
+ Base Sepolia Faucet
37
+ </a>
38
+ </p>
39
+ </>
40
+ );
41
+ }
42
+
43
+ export default UserBalance;
@@ -1,119 +1,316 @@
1
1
  :root {
2
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
- line-height: 1.5;
4
- font-weight: 400;
2
+ --cdp-example-page-bg-color: #eaeaea;
3
+ --cdp-example-bg-overlay-color: rgba(0, 0, 0, 0.25);
4
+ --cdp-example-bg-skeleton-color: rgba(0, 0, 0, 0.1);
5
+ --cdp-example-text-color: #111111;
6
+ --cdp-example-text-secondary-color: #757575;
7
+ --cdp-example-accent-color: #0052ff;
8
+ --cdp-example-accent-hover-color: #0044d6;
9
+ --cdp-example-accent-foreground-color: #ffffff;
10
+ --cdp-example-bg-low-contrast-color: #eeeeee;
11
+ --cdp-example-card-bg-color: #ffffff;
12
+ --cdp-example-card-border-color: #dcdcdc;
13
+ --cdp-example-card-max-width: 30rem;
14
+ --cdp-example-base-font-size: 16px;
15
+ --cdp-example-font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
5
16
 
17
+ background-color: var(--cdp-example-page-bg-color);
18
+ color: var(--cdp-example-card-border-color);
6
19
  color-scheme: light dark;
7
- color: rgba(255, 255, 255, 0.87);
8
- background-color: #242424;
9
-
20
+ font-family: var(--cdp-example-font-family);
21
+ font-size: var(--cdp-example-base-font-size);
10
22
  font-synthesis: none;
23
+ font-weight: 400;
24
+ line-height: 1.5;
11
25
  text-rendering: optimizeLegibility;
12
26
  -webkit-font-smoothing: antialiased;
13
27
  -moz-osx-font-smoothing: grayscale;
14
28
  }
15
29
 
30
+ @media (prefers-color-scheme: dark) {
31
+ :root {
32
+ --cdp-example-page-bg-color: #0a0b0d;
33
+ --cdp-example-bg-overlay-color: rgba(0, 0, 0, 0.25);
34
+ --cdp-example-bg-skeleton-color: rgba(255, 255, 255, 0.1);
35
+ --cdp-example-text-color: #fafafa;
36
+ --cdp-example-text-secondary-color: #8a919e;
37
+ --cdp-example-accent-color: #578bfa;
38
+ --cdp-example-accent-hover-color: #3e79f9;
39
+ --cdp-example-accent-foreground-color: #0a0b0d;
40
+ --cdp-example-bg-low-contrast-color: #32353d;
41
+ --cdp-example-card-bg-color: #141519;
42
+ --cdp-example-card-border-color: #24262a;
43
+ }
44
+ }
45
+
46
+ ::selection {
47
+ background-color: color(from var(--cdp-example-accent-color) srgb r g b / 0.3);
48
+ }
49
+
16
50
  html,
17
51
  html * {
18
52
  box-sizing: border-box;
19
53
  }
20
54
 
21
- #root {
55
+ body {
22
56
  margin: 0;
23
- padding: 0;
24
- width: 100vw;
25
- height: 100vh;
57
+ min-height: 100vh;
58
+ min-width: 320px;
26
59
  }
27
60
 
28
- .app {
61
+ body,
62
+ #root,
63
+ #root > div,
64
+ .flex-col-container {
29
65
  display: flex;
30
66
  flex-direction: column;
67
+ justify-content: center;
68
+ place-items: center;
69
+ }
70
+
71
+ .flex-row-container {
31
72
  align-items: center;
73
+ display: flex;
74
+ flex-direction: row;
75
+ }
76
+
77
+ .flex-grow {
78
+ flex-grow: 1;
79
+ }
80
+
81
+ .sr-only {
82
+ border-width: 0;
83
+ clip: rect(0, 0, 0, 0);
84
+ height: 1px;
85
+ margin: -1px;
86
+ overflow: hidden;
87
+ padding: 0;
88
+ position: absolute;
89
+ white-space: nowrap;
90
+ width: 1px;
91
+ }
92
+
93
+ #root {
94
+ flex-grow: 1;
95
+ margin: 0;
96
+ padding: 0rem;
32
97
  width: 100%;
33
- height: 100%;
34
- color: var(--cdp-web-colors-text);
35
- margin: 0 auto;
36
98
  }
37
99
 
38
- .button {
39
- --cdp-web-button-ring-color: transparent;
40
- --cdp-web-button-ring-width: 2px;
41
- --cdp-web-button-ring-inset-color: transparent;
42
- --cdp-web-button-ring-inset-width: 2px;
100
+ #root > div {
101
+ flex-grow: 1;
102
+ }
43
103
 
44
- box-shadow:
45
- inset 0 0 0 var(--cdp-web-button-ring-inset-width) var(--cdp-web-button-ring-inset-color),
46
- 0 0 0 var(--cdp-web-button-ring-width) var(--cdp-web-button-ring-color);
104
+ p {
105
+ margin: 0;
106
+ }
47
107
 
48
- display: inline-flex;
49
- align-items: center;
50
- justify-content: center;
51
- gap: 0.5em;
52
- padding: 1em;
53
- border-radius: 9999em;
54
- border: 0;
55
- background-color: var(--cdp-web-colors-primary);
56
- color: var(--cdp-web-colors-primaryText);
57
- font-size: 1em;
58
- font-weight: 500;
59
- line-height: 1.5;
60
- text-decoration: none;
61
- cursor: pointer;
62
- transition: all 0.15s ease-in-out;
63
- user-select: none;
108
+ a {
109
+ color: var(--cdp-example-accent-color);
64
110
  }
65
111
 
66
- .transaction-button:hover {
67
- background-color: var(--cdp-web-colors-primaryHoverBackground);
68
- color: var(--cdp-web-colors-primaryHoverText);
112
+ a:hover {
113
+ color: var(--cdp-example-accent-hover-color);
69
114
  }
70
115
 
71
- .transaction-button:focus-visible {
72
- --cdp-web-button-ring-color: var(--cdp-web-colors-primaryFocusRing);
73
- --cdp-web-button-ring-inset-color: var(--cdp-web-colors-background);
74
- outline: none;
116
+ hr {
117
+ border: 0 solid var(--cdp-example-card-border-color);
118
+ border-bottom-width: 1px;
119
+ margin: 1rem 0;
75
120
  }
76
121
 
77
- .transaction-form {
122
+ .app {
123
+ height: 100%;
124
+ color: var(--cdp-web-colors-text);
125
+ margin: 0 auto;
78
126
  width: 100%;
127
+ }
128
+
129
+ header {
130
+ background-color: var(--cdp-example-card-bg-color);
131
+ border-bottom: 1px solid var(--cdp-web-colors-border);
132
+ padding: 0.5rem 1rem;
133
+ width: 100%;
134
+ }
135
+
136
+ header .wallet-address {
137
+ margin-right: 1rem;
138
+ }
139
+
140
+ .header-inner {
141
+ align-items: center;
79
142
  display: flex;
80
143
  flex-direction: column;
81
- align-items: center;
144
+ justify-content: space-between;
145
+ margin: 0 auto;
146
+ max-width: 75rem;
147
+ text-align: center;
82
148
  }
83
149
 
84
- .transaction-info {
85
- color: #666;
86
- text-align: center;
150
+ .user-info {
151
+ justify-content: space-between;
152
+ width: 100%;
153
+ }
154
+
155
+ .user-icon {
156
+ flex-grow: 0;
157
+ flex-shrink: 0;
158
+ height: 1.25rem;
159
+ margin-right: 0.25rem;
160
+ width: auto;
87
161
  }
88
162
 
89
163
  .wallet-address {
90
164
  font-family: monospace;
91
- background: #f5f5f5;
92
- padding: 0.5rem;
93
- border-radius: 4px;
94
- font-size: 0.9rem;
165
+ font-size: 0.875rem;
95
166
  word-break: break-all;
96
167
  }
97
168
 
98
- .transaction-result {
99
- text-align: center;
169
+ .main {
170
+ padding: 0.5rem;
171
+ width: 100%;
100
172
  }
101
173
 
102
- .transaction-result a {
103
- color: #0052ff;
104
- text-decoration: none;
174
+ .main-inner {
175
+ gap: 1rem;
176
+ width: 100%;
105
177
  }
106
178
 
107
- .transaction-result a:hover {
108
- text-decoration: underline;
179
+ .main-inner > .card {
180
+ width: 100%;
109
181
  }
110
182
 
111
-
112
- body {
183
+ .site-title {
184
+ font-size: 1.2rem;
185
+ font-weight: 400;
186
+ line-height: 1.2;
113
187
  margin: 0;
188
+ margin-bottom: 0.5rem;
189
+ }
190
+
191
+ .site-title br {
192
+ display: none;
193
+ }
194
+
195
+ .card {
196
+ align-items: center;
197
+ background-color: var(--cdp-example-card-bg-color);
198
+ border: 1px solid var(--cdp-web-colors-border);
199
+ border-radius: 1rem;
114
200
  display: flex;
115
- place-items: center;
116
- background-color: #ffffff;
117
- min-width: 320px;
118
- min-height: 100vh;
201
+ flex-direction: column;
202
+ gap: 1rem;
203
+ justify-content: space-between;
204
+ max-width: var(--cdp-example-card-max-width);
205
+ padding: 2rem 1rem;
206
+ text-align: center;
207
+ width: 100%;
208
+ }
209
+
210
+ .card-title {
211
+ font-size: 1.25rem;
212
+ font-weight: 500;
213
+ line-height: 1.2;
214
+ margin: 0;
215
+ }
216
+
217
+ .loading--balance {
218
+ border-radius: 9999em;
219
+ display: inline-block;
220
+ height: 2.25rem;
221
+ width: 7rem;
222
+ }
223
+
224
+ .loading--text {
225
+ height: 1rem;
226
+ border-radius: 9999em;
227
+ margin: 0.25rem 0;
228
+ width: 20rem;
229
+ }
230
+
231
+ .loading--btn {
232
+ border-radius: 9999em;
233
+ height: 3.5rem;
234
+ width: 9.375rem;
235
+ }
236
+
237
+ .user-balance {
238
+ font-size: 1.5rem;
239
+ font-weight: 400;
240
+ }
241
+
242
+ .user-balance .flex-row-container {
243
+ justify-content: center;
244
+ }
245
+
246
+ .balance-icon {
247
+ flex-grow: 0;
248
+ flex-shrink: 0;
249
+ height: 1.5rem;
250
+ margin-right: 0.5rem;
251
+ width: auto;
252
+ }
253
+
254
+ .copy-address-button {
255
+ background-color: transparent;
256
+ border: 0;
257
+ color: var(--cdp-example-text-color);
258
+ cursor: pointer;
259
+ padding: 0;
260
+ }
261
+
262
+ .copy-address-button:hover .user-icon--user,
263
+ .copy-address-button .user-icon--copy {
264
+ display: none;
265
+ }
266
+
267
+ .copy-address-button .user-icon--user,
268
+ .copy-address-button:hover .user-icon--copy {
269
+ display: inline;
270
+ }
271
+
272
+ .tx-button {
273
+ padding-left: 2rem;
274
+ padding-right: 2rem;
275
+ min-width: 11.75rem;
276
+ }
277
+
278
+ @media (min-width: 540px) {
279
+ .header-inner {
280
+ flex-direction: row;
281
+ text-align: left;
282
+ }
283
+
284
+ .user-info {
285
+ width: auto;
286
+ }
287
+
288
+ .site-title {
289
+ margin-bottom: 0;
290
+ }
291
+
292
+ .site-title br {
293
+ display: inline;
294
+ }
295
+
296
+ .main {
297
+ padding: 1rem;
298
+ }
299
+ }
300
+
301
+ @media (min-width: 860px) {
302
+ :root {
303
+ --cdp-example-card-max-width: 35rem;
304
+ }
305
+
306
+ .main-inner {
307
+ align-items: stretch;
308
+ flex-direction: row;
309
+ }
310
+ }
311
+
312
+ @media (min-width: 1920px) {
313
+ :root {
314
+ --cdp-base-font-size: 20px;
315
+ }
119
316
  }
@@ -4,11 +4,12 @@ import { createRoot } from "react-dom/client";
4
4
 
5
5
  import App from "./App.tsx";
6
6
  import { CDP_CONFIG } from "./config.ts";
7
+ import { theme } from "./theme.ts";
7
8
  import "./index.css";
8
9
 
9
10
  createRoot(document.getElementById("root")!).render(
10
11
  <StrictMode>
11
- <CDPReactProvider config={CDP_CONFIG}>
12
+ <CDPReactProvider config={CDP_CONFIG} theme={theme}>
12
13
  <App />
13
14
  </CDPReactProvider>
14
15
  </StrictMode>,
@@ -0,0 +1,32 @@
1
+ import { type Theme } from "@coinbase/cdp-react/theme";
2
+
3
+ export const theme: Partial<Theme> = {
4
+ "colors-background": "var(--cdp-example-card-bg-color)",
5
+ "colors-backgroundOverlay": "var(--cdp-example-bg-overlay-color)",
6
+ "colors-backgroundSkeleton": "var(--cdp-example-bg-skeleton-color)",
7
+ "colors-text": "var(--cdp-example-text-color)",
8
+ "colors-textSecondary": "var(--cdp-example-text-secondary-color)",
9
+ "colors-border": "var(--cdp-example-card-border-color)",
10
+ "colors-primary": "var(--cdp-example-accent-color)",
11
+ "colors-primaryText": "var(--cdp-example-accent-foreground-color)",
12
+ "colors-primaryHoverBackground": "var(--cdp-example-accent-hover-color)",
13
+ "colors-primaryHoverText": "var(--cdp-example-accent-foreground-color)",
14
+ "colors-primaryFocusRing": "var(--cdp-example-accent-color)",
15
+ "colors-secondary": "var(--cdp-example-bg-low-contrast-color)",
16
+ "colors-secondaryText": "var(--cdp-example-text-color)",
17
+ "colors-secondaryHoverBackground": "var(--cdp-example-bg-low-contrast-color)",
18
+ "colors-secondaryHoverText": "var(--cdp-example-text-color)",
19
+ "colors-secondaryFocusRing": "var(--cdp-example-accent-color)",
20
+ "colors-inputBackground": "var(--cdp-example-card-bg-color)",
21
+ "colors-inputBorder": "var(--cdp-example-text-secondary-color)",
22
+ "colors-inputText": "var(--cdp-example-text-color)",
23
+ "colors-inputLabel": "var(--cdp-example-text-color)",
24
+ "colors-inputPlaceholder": "var(--cdp-example-text-secondary-color)",
25
+ "colors-inputFocusBorder": "var(--cdp-example-accent-color)",
26
+ "colors-linkText": "var(--cdp-example-accent-color)",
27
+ "colors-linkHover": "var(--cdp-example-accent-hover-color)",
28
+ "colors-linkSecondaryText": "var(--cdp-example-text-color)",
29
+ "colors-linkSecondaryHover": "var(--cdp-example-text-color)",
30
+ "fontFamily-sans": "var(--cdp-example-font-family)",
31
+ fontSizeBase: "var(--cdp-example-base-font-size)",
32
+ };