123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- import React, { useState, useCallback, useEffect, useMemo } from 'react';
- import styled from 'styled-components';
- import { useWallet, WalletProvider, ConnectionProvider } from '@solana/wallet-adapter-react';
- import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
- import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
- import type { Adapter } from '@solana/wallet-adapter-base';
- import { type SolanaSignInInput } from '@solana/wallet-standard-features';
- import { verifySignIn } from '@solana/wallet-standard-util';
- import {
- createSignInData,
- createSignInErrorData,
- } from './utils';
- import { TLog } from './types';
- import { Logs, Sidebar, AutoConnectProvider } from './components';
- // =============================================================================
- // Styled Components
- // =============================================================================
- const StyledApp = styled.div`
- display: flex;
- flex-direction: row;
- height: 100vh;
- @media (max-width: 768px) {
- flex-direction: column;
- }
- `;
- // =============================================================================
- // Constants
- // =============================================================================
- const message = 'To avoid digital dognappers, sign below to authenticate with CryptoCorgis.';
- // =============================================================================
- // Typedefs
- // =============================================================================
- export type ConnectedMethods =
- | {
- name: string;
- onClick: () => Promise<string>;
- }
- | {
- name: string;
- onClick: () => Promise<void>;
- };
- const StatelessApp = () => {
- const { wallet, publicKey, connect, disconnect, signMessage, signIn } = useWallet();
- const [logs, setLogs] = useState<TLog[]>([]);
- const createLog = useCallback(
- (log: TLog) => {
- return setLogs((logs) => [...logs, log]);
- },
- [setLogs]
- );
- const clearLogs = useCallback(() => {
- setLogs([]);
- }, [setLogs]);
- useEffect(() => {
- if (!publicKey || !wallet) return;
- createLog({
- status: 'success',
- method: 'connect',
- message: `Connected to account ${publicKey.toBase58()}`,
- });
- }, [createLog, publicKey, wallet]);
- /** SignMessage */
- const handleSignMessage = useCallback(async () => {
- if (!publicKey || !wallet) return;
- try {
- const encodedMessage = new TextEncoder().encode(message);
- const signature = await signMessage(encodedMessage);
- createLog({
- status: 'success',
- method: 'signMessage',
- message: `Message signed with signature: ${JSON.stringify(signature)}`,
- });
- } catch (error) {
- createLog({
- status: 'error',
- method: 'signMessage',
- message: error.message,
- });
- }
- }, [createLog, publicKey, signMessage, wallet]);
- /** SignIn */
- const handleSignIn = useCallback(async () => {
- if (!publicKey || !wallet) return;
- const signInData = await createSignInData();
- try {
- const {account, signedMessage, signature} = await signIn(signInData);
- createLog({
- status: 'success',
- method: 'signIn',
- message: `Message signed: ${JSON.stringify(signedMessage)} by ${account.address} with signature ${JSON.stringify(signature)}`,
- });
- } catch (error) {
- createLog({
- status: 'error',
- method: 'signIn',
- message: error.message,
- });
- }
- }, [createLog, publicKey, signIn, wallet]);
- /** SignInError */
- const handleSignInError = useCallback(async () => {
- if (!publicKey || !wallet) return;
- const signInData = await createSignInErrorData();
- try {
- const {account, signedMessage, signature} = await signIn(signInData);
- createLog({
- status: 'success',
- method: 'signMessage',
- message: `Message signed: ${JSON.stringify(signedMessage)} by ${account.address} with signature ${JSON.stringify(signature)}`,
- });
- } catch (error) {
- createLog({
- status: 'error',
- method: 'signIn',
- message: error.message,
- });
- }
- }, [createLog, publicKey, signIn, wallet]);
- /** Connect */
- const handleConnect = useCallback(async () => {
- if (!publicKey || !wallet) return;
- try {
- await connect();
- } catch (error) {
- createLog({
- status: 'error',
- method: 'connect',
- message: error.message,
- });
- }
- }, [connect, createLog, publicKey, wallet]);
- /** Disconnect */
- const handleDisconnect = useCallback(async () => {
- if (!publicKey || !wallet) return;
- try {
- await disconnect();
- createLog({
- status: 'warning',
- method: 'disconnect',
- message: '👋',
- });
- } catch (error) {
- createLog({
- status: 'error',
- method: 'disconnect',
- message: error.message,
- });
- }
- }, [createLog, disconnect, publicKey, wallet]);
- const connectedMethods = useMemo(() => {
- return [
- {
- name: 'Sign Message',
- onClick: handleSignMessage,
- },
- {
- name: 'Sign In',
- onClick: handleSignIn,
- },
- {
- name: 'Sign In Error',
- onClick: handleSignInError,
- },
- {
- name: 'Disconnect',
- onClick: handleDisconnect,
- },
- ];
- }, [
- handleSignMessage,
- handleSignIn,
- handleSignInError,
- handleDisconnect,
- ]);
- return (
- <StyledApp>
- <Sidebar publicKey={publicKey} connectedMethods={connectedMethods} connect={handleConnect} />
- <Logs publicKey={publicKey} logs={logs} clearLogs={clearLogs} />
- </StyledApp>
- );
- };
- // =============================================================================
- // Main Component
- // =============================================================================
- const App = () => {
- const network = WalletAdapterNetwork.Mainnet;
- const endpoint = `https://api.mainnet-beta.solana.com`;
- const wallets = useMemo(
- () => [], // confirmed also with `() => []` for wallet-standard only
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [network]
- );
- const autoSignIn = useCallback(async (adapter: Adapter) => {
- if (!('signIn' in adapter)) return true;
- // Fetch the signInInput from the backend
- /*
- const createResponse = await fetch("/backend/createSignInData");
- const input: SolanaSignInInput = await createResponse.json();
- */
- const input: SolanaSignInInput = await createSignInData();
- // Send the signInInput to the wallet and trigger a sign-in request
- const output = await adapter.signIn(input);
- // Verify the sign-in output against the generated input server-side
- /*
- let strPayload = JSON.stringify({ input, output: {
- account: {
- address: output.account.address,
- publicKey: Array.from(output.account.publicKey),
- },
- signature: Array.from(output["signature"]),
- signedMessage: Array.from(output["signedMessage"]),
- } });
- const verifyResponse = await fetch("/backend/verifySIWS", {
- method: "POST",
- body: strPayload,
- });
- const success = await verifyResponse.json();
- */
- // For demonstration purposes only, this should happen server-side
- if (!verifySignIn(input, output)) {
- console.error('Sign In verification failed!')
- throw new Error('Sign In verification failed!');
- }
- return false;
- }, []);
- return (
- <AutoConnectProvider>
- <ConnectionProvider endpoint={endpoint}>
- <WalletProvider wallets={wallets} autoConnect={autoSignIn}>
- <WalletModalProvider>
- <StatelessApp />
- </WalletModalProvider>
- </WalletProvider>
- </ConnectionProvider>
- </AutoConnectProvider>
- );
- };
- export default App;
|