@coinbase/create-cdp-app 0.0.9 → 0.0.11
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/README.md +8 -1
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template-nextjs/README.md +85 -0
- package/template-nextjs/_gitignore +45 -0
- package/template-nextjs/env.example +3 -0
- package/template-nextjs/eslint.config.mjs +16 -0
- package/template-nextjs/next-env.d.ts +5 -0
- package/template-nextjs/next.config.ts +7 -0
- package/template-nextjs/package.json +28 -0
- package/template-nextjs/public/logo.svg +5 -0
- package/template-nextjs/src/app/globals.css +306 -0
- package/template-nextjs/src/app/icon.svg +5 -0
- package/template-nextjs/src/app/layout.tsx +22 -0
- package/template-nextjs/src/app/page.tsx +16 -0
- package/template-nextjs/src/components/ClientApp.tsx +27 -0
- package/template-nextjs/src/components/Header.tsx +61 -0
- package/template-nextjs/src/components/Icons.tsx +68 -0
- package/template-nextjs/src/components/Loading.tsx +15 -0
- package/template-nextjs/src/components/Providers.tsx +33 -0
- package/template-nextjs/src/components/SignInScreen.tsx +17 -0
- package/template-nextjs/src/components/SignedInScreen.tsx +64 -0
- package/template-nextjs/src/components/Transaction.tsx +128 -0
- package/template-nextjs/src/components/UserBalance.tsx +42 -0
- package/template-nextjs/tsconfig.json +27 -0
- package/{template-react-components → template-react}/README.md +13 -6
- package/{template-react-components → template-react}/index.html +1 -1
- package/template-react/public/eth.svg +25 -0
- package/template-react/public/logo.svg +5 -0
- package/{template-react-components → template-react}/src/Header.tsx +1 -1
- package/template-react/src/config.ts +9 -0
- package/{template-react-components → template-react}/src/main.tsx +2 -2
- package/template-react/src/theme.ts +32 -0
- package/template-react-components/public/vite.svg +0 -1
- package/template-react-components/src/config.ts +0 -3
- /package/{template-react-components → template-nextjs}/public/eth.svg +0 -0
- /package/{template-react-components/src → template-nextjs/src/components}/theme.ts +0 -0
- /package/{template-react-components → template-react}/_gitignore +0 -0
- /package/{template-react-components → template-react}/env.example +0 -0
- /package/{template-react-components → template-react}/eslint.config.js +0 -0
- /package/{template-react-components → template-react}/package.json +0 -0
- /package/{template-react-components → template-react}/src/App.tsx +0 -0
- /package/{template-react-components → template-react}/src/Icons.tsx +0 -0
- /package/{template-react-components → template-react}/src/Loading.tsx +0 -0
- /package/{template-react-components → template-react}/src/SignInScreen.tsx +0 -0
- /package/{template-react-components → template-react}/src/SignedInScreen.tsx +0 -0
- /package/{template-react-components → template-react}/src/Transaction.tsx +0 -0
- /package/{template-react-components → template-react}/src/UserBalance.tsx +0 -0
- /package/{template-react-components → template-react}/src/index.css +0 -0
- /package/{template-react-components → template-react}/src/vite-env.d.ts +0 -0
- /package/{template-react-components → template-react}/tsconfig.app.json +0 -0
- /package/{template-react-components → template-react}/tsconfig.json +0 -0
- /package/{template-react-components → template-react}/tsconfig.node.json +0 -0
- /package/{template-react-components → template-react}/vite.config.ts +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import ClientApp from "@/components/ClientApp";
|
|
3
|
+
import Providers from "@/components/Providers";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Home page for the Next.js app
|
|
7
|
+
*
|
|
8
|
+
* @returns The home page
|
|
9
|
+
*/
|
|
10
|
+
export default function Home() {
|
|
11
|
+
return (
|
|
12
|
+
<Providers>
|
|
13
|
+
<ClientApp />
|
|
14
|
+
</Providers>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useIsInitialized, useIsSignedIn } from "@coinbase/cdp-hooks";
|
|
4
|
+
|
|
5
|
+
import Loading from "@/components/Loading";
|
|
6
|
+
import SignedInScreen from "@/components/SignedInScreen";
|
|
7
|
+
import SignInScreen from "@/components/SignInScreen";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A component that displays the client app.
|
|
11
|
+
*/
|
|
12
|
+
export default function ClientApp() {
|
|
13
|
+
const isInitialized = useIsInitialized();
|
|
14
|
+
const isSignedIn = useIsSignedIn();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="app flex-col-container flex-grow">
|
|
18
|
+
{!isInitialized && <Loading />}
|
|
19
|
+
{isInitialized && (
|
|
20
|
+
<>
|
|
21
|
+
{!isSignedIn && <SignInScreen />}
|
|
22
|
+
{isSignedIn && <SignedInScreen />}
|
|
23
|
+
</>
|
|
24
|
+
)}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
<h1 className="site-title">CDP React StarterKit</h1>
|
|
37
|
+
<div className="user-info flex-row-container">
|
|
38
|
+
{evmAddress && (
|
|
39
|
+
<button
|
|
40
|
+
aria-label="copy wallet address"
|
|
41
|
+
className="flex-row-container copy-address-button"
|
|
42
|
+
onClick={copyAddress}
|
|
43
|
+
>
|
|
44
|
+
{!isCopied && (
|
|
45
|
+
<>
|
|
46
|
+
<IconUser className="user-icon user-icon--user" />
|
|
47
|
+
<IconCopy className="user-icon user-icon--copy" />
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
{isCopied && <IconCheck className="user-icon user-icon--check" />}
|
|
51
|
+
<span className="wallet-address">
|
|
52
|
+
{evmAddress.slice(0, 6)}...{evmAddress.slice(-4)}
|
|
53
|
+
</span>
|
|
54
|
+
</button>
|
|
55
|
+
)}
|
|
56
|
+
<AuthButton />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</header>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, type SVGProps } from "react";
|
|
4
|
+
|
|
5
|
+
const SvgIcon = ({ children, ...props }: SVGProps<SVGSVGElement> & { children: ReactNode }) => {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
role="img"
|
|
11
|
+
aria-hidden={props["aria-label"] ? undefined : true}
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check icon
|
|
21
|
+
*
|
|
22
|
+
* @param props - SVG props
|
|
23
|
+
* @returns SVG element
|
|
24
|
+
*/
|
|
25
|
+
export const IconCheck = (props: Omit<SVGProps<SVGSVGElement>, "viewBox">) => {
|
|
26
|
+
return (
|
|
27
|
+
<SvgIcon width="24" height="24" viewBox="0 0 24 24" {...props}>
|
|
28
|
+
<path
|
|
29
|
+
fillRule="evenodd"
|
|
30
|
+
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"
|
|
31
|
+
clipRule="evenodd"
|
|
32
|
+
/>
|
|
33
|
+
</SvgIcon>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Copy icon
|
|
39
|
+
*
|
|
40
|
+
* @param props - SVG props
|
|
41
|
+
* @returns SVG element
|
|
42
|
+
*/
|
|
43
|
+
export const IconCopy = (props: Omit<SVGProps<SVGSVGElement>, "viewBox">) => {
|
|
44
|
+
return (
|
|
45
|
+
<SvgIcon width="24" height="24" viewBox="0 0 24 24" {...props}>
|
|
46
|
+
<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" />
|
|
47
|
+
<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" />
|
|
48
|
+
</SvgIcon>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* User icon
|
|
54
|
+
*
|
|
55
|
+
* @param props - SVG props
|
|
56
|
+
* @returns SVG element
|
|
57
|
+
*/
|
|
58
|
+
export const IconUser = (props: Omit<SVGProps<SVGSVGElement>, "viewBox">) => {
|
|
59
|
+
return (
|
|
60
|
+
<SvgIcon width="24" height="24" viewBox="0 0 24 24" {...props}>
|
|
61
|
+
<path
|
|
62
|
+
fillRule="evenodd"
|
|
63
|
+
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"
|
|
64
|
+
clipRule="evenodd"
|
|
65
|
+
/>
|
|
66
|
+
</SvgIcon>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { LoadingSpinner } from "@coinbase/cdp-react/components/LoadingSpinner";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* App loading screen
|
|
7
|
+
*/
|
|
8
|
+
export default function Loading() {
|
|
9
|
+
return (
|
|
10
|
+
<main>
|
|
11
|
+
<h1 className="sr-only">Loading</h1>
|
|
12
|
+
<LoadingSpinner />
|
|
13
|
+
</main>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CDPReactProvider } from "@coinbase/cdp-react/components/CDPReactProvider";
|
|
4
|
+
|
|
5
|
+
import { theme } from "@/components/theme";
|
|
6
|
+
|
|
7
|
+
interface ProvidersProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const CDP_CONFIG = {
|
|
12
|
+
projectId: process.env.NEXT_PUBLIC_CDP_PROJECT_ID ?? "",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const APP_CONFIG = {
|
|
16
|
+
name: "CDP Next.js StarterKit",
|
|
17
|
+
logoUrl: "http://localhost:3000/logo.svg",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Providers component that wraps the application in all requisite providers
|
|
22
|
+
*
|
|
23
|
+
* @param props - { object } - The props for the Providers component
|
|
24
|
+
* @param props.children - { React.ReactNode } - The children to wrap
|
|
25
|
+
* @returns The wrapped children
|
|
26
|
+
*/
|
|
27
|
+
export default function Providers({ children }: ProvidersProps) {
|
|
28
|
+
return (
|
|
29
|
+
<CDPReactProvider config={CDP_CONFIG} app={APP_CONFIG} theme={theme}>
|
|
30
|
+
{children}
|
|
31
|
+
</CDPReactProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AuthButton } from "@coinbase/cdp-react/components/AuthButton";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sign in screen
|
|
7
|
+
*/
|
|
8
|
+
export default function SignInScreen() {
|
|
9
|
+
return (
|
|
10
|
+
<main className="card card--login">
|
|
11
|
+
<h1 className="sr-only">Sign in</h1>
|
|
12
|
+
<p className="card-title">Welcome!</p>
|
|
13
|
+
<p>Please sign in to continue.</p>
|
|
14
|
+
<AuthButton />
|
|
15
|
+
</main>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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 "@/components/Header";
|
|
9
|
+
import Transaction from "@/components/Transaction";
|
|
10
|
+
import UserBalance from "@/components/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 balance = await client.getBalance({
|
|
36
|
+
address: evmAddress,
|
|
37
|
+
});
|
|
38
|
+
setBalance(balance);
|
|
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 balance={formattedBalance} />
|
|
54
|
+
</div>
|
|
55
|
+
<div className="card card--transaction">
|
|
56
|
+
{isSignedIn && evmAddress && (
|
|
57
|
+
<Transaction balance={formattedBalance} onSuccess={getBalance} />
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</main>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSendEvmTransaction, useEvmAddress } from "@coinbase/cdp-hooks";
|
|
3
|
+
import { Button } from "@coinbase/cdp-react/components/Button";
|
|
4
|
+
import { LoadingSkeleton } from "@coinbase/cdp-react/components/LoadingSkeleton";
|
|
5
|
+
import { type MouseEvent, useCallback, useMemo, useState } from "react";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
balance?: string;
|
|
9
|
+
onSuccess?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* This component demonstrates how to send an EVM transaction using the CDP hooks.
|
|
14
|
+
*
|
|
15
|
+
* @param {Props} props - The props for the Transaction component.
|
|
16
|
+
* @param {string} [props.balance] - The user's balance.
|
|
17
|
+
* @param {() => void} [props.onSuccess] - A function to call when the transaction is successful.
|
|
18
|
+
* @returns A component that displays a transaction form and a transaction hash.
|
|
19
|
+
*/
|
|
20
|
+
export default function Transaction(props: Props) {
|
|
21
|
+
const { balance, onSuccess } = props;
|
|
22
|
+
const sendEvmTransaction = useSendEvmTransaction();
|
|
23
|
+
const evmAddress = useEvmAddress();
|
|
24
|
+
|
|
25
|
+
const [isPending, setIsPending] = useState(false);
|
|
26
|
+
const [transactionHash, setTransactionHash] = useState<string | null>(null);
|
|
27
|
+
const hasBalance = useMemo(() => {
|
|
28
|
+
return balance && balance !== "0";
|
|
29
|
+
}, [balance]);
|
|
30
|
+
|
|
31
|
+
const handleSendTransaction = useCallback(
|
|
32
|
+
async (e: MouseEvent<HTMLButtonElement>) => {
|
|
33
|
+
if (!evmAddress) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
setIsPending(true);
|
|
39
|
+
|
|
40
|
+
const { transactionHash } = await sendEvmTransaction({
|
|
41
|
+
transaction: {
|
|
42
|
+
to: evmAddress, // Send to yourself for testing
|
|
43
|
+
value: 1000000000000n, // 0.000001 ETH in wei
|
|
44
|
+
gas: 21000n,
|
|
45
|
+
chainId: 84532, // Base Sepolia
|
|
46
|
+
type: "eip1559",
|
|
47
|
+
},
|
|
48
|
+
evmAccount: evmAddress,
|
|
49
|
+
network: "base-sepolia",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
setTransactionHash(transactionHash);
|
|
53
|
+
setIsPending(false);
|
|
54
|
+
onSuccess?.();
|
|
55
|
+
},
|
|
56
|
+
[evmAddress, sendEvmTransaction, onSuccess],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{balance === undefined && (
|
|
62
|
+
<>
|
|
63
|
+
<h2 className="card-title">Send a transaction</h2>
|
|
64
|
+
<LoadingSkeleton className="loading--text" />
|
|
65
|
+
<LoadingSkeleton className="loading--btn" />
|
|
66
|
+
</>
|
|
67
|
+
)}
|
|
68
|
+
{balance !== undefined && (
|
|
69
|
+
<>
|
|
70
|
+
{!transactionHash && (
|
|
71
|
+
<>
|
|
72
|
+
<h2 className="card-title">Send a transaction</h2>
|
|
73
|
+
{hasBalance && (
|
|
74
|
+
<>
|
|
75
|
+
<p>Send 0.000001 ETH to yourself on Base Sepolia</p>
|
|
76
|
+
<Button
|
|
77
|
+
className="tx-button"
|
|
78
|
+
onClick={handleSendTransaction}
|
|
79
|
+
isPending={isPending}
|
|
80
|
+
>
|
|
81
|
+
Send Transaction
|
|
82
|
+
</Button>
|
|
83
|
+
</>
|
|
84
|
+
)}
|
|
85
|
+
{!hasBalance && (
|
|
86
|
+
<>
|
|
87
|
+
<p>You need ETH to send a transaction, but you have none.</p>
|
|
88
|
+
<p>
|
|
89
|
+
Get some from{" "}
|
|
90
|
+
<a
|
|
91
|
+
href="https://portal.cdp.coinbase.com/products/faucet"
|
|
92
|
+
target="_blank"
|
|
93
|
+
rel="noopener noreferrer"
|
|
94
|
+
>
|
|
95
|
+
Base Sepolia Faucet
|
|
96
|
+
</a>
|
|
97
|
+
</p>
|
|
98
|
+
</>
|
|
99
|
+
)}
|
|
100
|
+
</>
|
|
101
|
+
)}
|
|
102
|
+
{transactionHash && (
|
|
103
|
+
<>
|
|
104
|
+
<h2 className="card-title">Transaction sent</h2>
|
|
105
|
+
<p>
|
|
106
|
+
Transaction hash:{" "}
|
|
107
|
+
<a
|
|
108
|
+
href={`https://sepolia.basescan.org/tx/${transactionHash}`}
|
|
109
|
+
target="_blank"
|
|
110
|
+
rel="noopener noreferrer"
|
|
111
|
+
>
|
|
112
|
+
{transactionHash.slice(0, 6)}...{transactionHash.slice(-4)}
|
|
113
|
+
</a>
|
|
114
|
+
</p>
|
|
115
|
+
<Button
|
|
116
|
+
variant="secondary"
|
|
117
|
+
className="tx-button"
|
|
118
|
+
onClick={() => setTransactionHash(null)}
|
|
119
|
+
>
|
|
120
|
+
Send another transaction
|
|
121
|
+
</Button>
|
|
122
|
+
</>
|
|
123
|
+
)}
|
|
124
|
+
</>
|
|
125
|
+
)}
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { LoadingSkeleton } from "@coinbase/cdp-react/components/LoadingSkeleton";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
balance?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A component that displays the user's balance.
|
|
10
|
+
*
|
|
11
|
+
* @param {Props} props - The props for the UserBalance component.
|
|
12
|
+
* @param {string} [props.balance] - The user's balance.
|
|
13
|
+
* @returns A component that displays the user's balance.
|
|
14
|
+
*/
|
|
15
|
+
export default function UserBalance(props: Props) {
|
|
16
|
+
const { balance } = props;
|
|
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
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
26
|
+
"exclude": ["node_modules"]
|
|
27
|
+
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
# CDP React App
|
|
2
2
|
|
|
3
|
-
This project was generated with [`@coinbase/create-cdp-app`](https://github.
|
|
3
|
+
This project was generated with [`@coinbase/create-cdp-app`](https://coinbase.github.io/cdp-web/modules/_coinbase_create-cdp-app.html) using the React template.
|
|
4
4
|
|
|
5
5
|
## Project Structure
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
src/
|
|
9
|
-
├── App.tsx # Main application component with
|
|
10
|
-
├──
|
|
11
|
-
├──
|
|
12
|
-
├──
|
|
13
|
-
|
|
9
|
+
├── App.tsx # Main application component with routing and layout
|
|
10
|
+
├── config.ts # CDP configuration settings
|
|
11
|
+
├── Header.tsx # Navigation header with authentication status
|
|
12
|
+
├── Icons.tsx # Reusable icon components
|
|
13
|
+
├── index.css # Global styles and theme variables
|
|
14
|
+
├── Loading.tsx # Loading state component
|
|
15
|
+
├── main.tsx # Entry point with CDP provider setup
|
|
16
|
+
├── SignedInScreen.tsx # Screen displayed after successful authentication
|
|
17
|
+
├── SignInScreen.tsx # Authentication screen with CDP sign-in flow
|
|
18
|
+
├── theme.ts # Theme configuration and styling constants
|
|
19
|
+
├── Transaction.tsx # Example transaction flow using CDP Hooks
|
|
20
|
+
├── UserBalance.tsx # Component to display user's wallet balance
|
|
14
21
|
```
|
|
15
22
|
|
|
16
23
|
## Getting Started
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>CDP React StarterKit</title>
|
|
8
8
|
</head>
|
|
@@ -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>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
|
2
|
+
<circle cx="64" cy="64" r="64" fill="#008080" />
|
|
3
|
+
<path fill="#FFF"
|
|
4
|
+
d="M78.91 76.225q.7 0 1.25.54l3.39 3.68q-2.82 3.49-6.93 5.35-4.11 1.85-9.87 1.85-5.15 0-9.26-1.76-4.12-1.76-7.03-4.89-2.91-3.14-4.46-7.49t-1.55-9.51q0-5.21 1.66-9.55 1.66-4.33 4.69-7.47 3.02-3.14 7.21-4.88 4.2-1.74 9.28-1.74 5.06 0 8.98 1.66t6.67 4.35l-2.88 4q-.25.39-.65.67-.4.29-1.11.29-.48 0-.99-.27t-1.12-.67-1.41-.88-1.85-.88q-1.06-.4-2.45-.67-1.39-.28-3.22-.28-3.1 0-5.68 1.11-2.57 1.1-4.43 3.2-1.86 2.09-2.88 5.12-1.02 3.02-1.02 6.89 0 3.91 1.1 6.95t2.99 5.12 4.45 3.18q2.56 1.11 5.5 1.11 1.76 0 3.19-.2 1.42-.19 2.62-.6 1.2-.42 2.27-1.08 1.08-.65 2.13-1.61.32-.29.67-.47.36-.17.74-.17" />
|
|
5
|
+
</svg>
|
|
@@ -32,7 +32,7 @@ function Header() {
|
|
|
32
32
|
return (
|
|
33
33
|
<header>
|
|
34
34
|
<div className="header-inner">
|
|
35
|
-
<h1 className="site-title">CDP
|
|
35
|
+
<h1 className="site-title">CDP Next.js StarterKit</h1>
|
|
36
36
|
<div className="user-info flex-row-container">
|
|
37
37
|
{evmAddress && (
|
|
38
38
|
<button
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Config } from "@coinbase/cdp-core";
|
|
2
|
+
import { type AppConfig } from "@coinbase/cdp-react";
|
|
3
|
+
|
|
4
|
+
export const CDP_CONFIG: Config = { projectId: import.meta.env.VITE_CDP_PROJECT_ID };
|
|
5
|
+
|
|
6
|
+
export const APP_CONFIG: AppConfig = {
|
|
7
|
+
name: "CDP React StarterKit",
|
|
8
|
+
logoUrl: "http://localhost:3000/logo.svg",
|
|
9
|
+
};
|
|
@@ -3,13 +3,13 @@ import { StrictMode } from "react";
|
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
4
|
|
|
5
5
|
import App from "./App.tsx";
|
|
6
|
-
import { CDP_CONFIG } from "./config.ts";
|
|
6
|
+
import { APP_CONFIG, CDP_CONFIG } from "./config.ts";
|
|
7
7
|
import { theme } from "./theme.ts";
|
|
8
8
|
import "./index.css";
|
|
9
9
|
|
|
10
10
|
createRoot(document.getElementById("root")!).render(
|
|
11
11
|
<StrictMode>
|
|
12
|
-
<CDPReactProvider config={CDP_CONFIG} theme={theme}>
|
|
12
|
+
<CDPReactProvider config={CDP_CONFIG} app={APP_CONFIG} theme={theme}>
|
|
13
13
|
<App />
|
|
14
14
|
</CDPReactProvider>
|
|
15
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
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
File without changes
|