App.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. import React, { useState, useCallback, useEffect, useMemo } from 'react';
  2. import styled from 'styled-components';
  3. import { useWallet, WalletProvider, ConnectionProvider } from '@solana/wallet-adapter-react';
  4. import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
  5. import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
  6. import type { Adapter } from '@solana/wallet-adapter-base';
  7. import { type SolanaSignInInput } from '@solana/wallet-standard-features';
  8. import { verifySignIn } from '@solana/wallet-standard-util';
  9. import {
  10. createSignInData,
  11. createSignInErrorData,
  12. } from './utils';
  13. import { TLog } from './types';
  14. import { Logs, Sidebar, AutoConnectProvider } from './components';
  15. // =============================================================================
  16. // Styled Components
  17. // =============================================================================
  18. const StyledApp = styled.div`
  19. display: flex;
  20. flex-direction: row;
  21. height: 100vh;
  22. @media (max-width: 768px) {
  23. flex-direction: column;
  24. }
  25. `;
  26. // =============================================================================
  27. // Constants
  28. // =============================================================================
  29. const message = 'To avoid digital dognappers, sign below to authenticate with CryptoCorgis.';
  30. // =============================================================================
  31. // Typedefs
  32. // =============================================================================
  33. export type ConnectedMethods =
  34. | {
  35. name: string;
  36. onClick: () => Promise<string>;
  37. }
  38. | {
  39. name: string;
  40. onClick: () => Promise<void>;
  41. };
  42. const StatelessApp = () => {
  43. const { wallet, publicKey, connect, disconnect, signMessage, signIn } = useWallet();
  44. const [logs, setLogs] = useState<TLog[]>([]);
  45. const createLog = useCallback(
  46. (log: TLog) => {
  47. return setLogs((logs) => [...logs, log]);
  48. },
  49. [setLogs]
  50. );
  51. const clearLogs = useCallback(() => {
  52. setLogs([]);
  53. }, [setLogs]);
  54. useEffect(() => {
  55. if (!publicKey || !wallet) return;
  56. createLog({
  57. status: 'success',
  58. method: 'connect',
  59. message: `Connected to account ${publicKey.toBase58()}`,
  60. });
  61. }, [createLog, publicKey, wallet]);
  62. /** SignMessage */
  63. const handleSignMessage = useCallback(async () => {
  64. if (!publicKey || !wallet) return;
  65. try {
  66. const encodedMessage = new TextEncoder().encode(message);
  67. const signature = await signMessage(encodedMessage);
  68. createLog({
  69. status: 'success',
  70. method: 'signMessage',
  71. message: `Message signed with signature: ${JSON.stringify(signature)}`,
  72. });
  73. } catch (error) {
  74. createLog({
  75. status: 'error',
  76. method: 'signMessage',
  77. message: error.message,
  78. });
  79. }
  80. }, [createLog, publicKey, signMessage, wallet]);
  81. /** SignIn */
  82. const handleSignIn = useCallback(async () => {
  83. if (!publicKey || !wallet) return;
  84. const signInData = await createSignInData();
  85. try {
  86. const {account, signedMessage, signature} = await signIn(signInData);
  87. createLog({
  88. status: 'success',
  89. method: 'signIn',
  90. message: `Message signed: ${JSON.stringify(signedMessage)} by ${account.address} with signature ${JSON.stringify(signature)}`,
  91. });
  92. } catch (error) {
  93. createLog({
  94. status: 'error',
  95. method: 'signIn',
  96. message: error.message,
  97. });
  98. }
  99. }, [createLog, publicKey, signIn, wallet]);
  100. /** SignInError */
  101. const handleSignInError = useCallback(async () => {
  102. if (!publicKey || !wallet) return;
  103. const signInData = await createSignInErrorData();
  104. try {
  105. const {account, signedMessage, signature} = await signIn(signInData);
  106. createLog({
  107. status: 'success',
  108. method: 'signMessage',
  109. message: `Message signed: ${JSON.stringify(signedMessage)} by ${account.address} with signature ${JSON.stringify(signature)}`,
  110. });
  111. } catch (error) {
  112. createLog({
  113. status: 'error',
  114. method: 'signIn',
  115. message: error.message,
  116. });
  117. }
  118. }, [createLog, publicKey, signIn, wallet]);
  119. /** Connect */
  120. const handleConnect = useCallback(async () => {
  121. if (!publicKey || !wallet) return;
  122. try {
  123. await connect();
  124. } catch (error) {
  125. createLog({
  126. status: 'error',
  127. method: 'connect',
  128. message: error.message,
  129. });
  130. }
  131. }, [connect, createLog, publicKey, wallet]);
  132. /** Disconnect */
  133. const handleDisconnect = useCallback(async () => {
  134. if (!publicKey || !wallet) return;
  135. try {
  136. await disconnect();
  137. createLog({
  138. status: 'warning',
  139. method: 'disconnect',
  140. message: '👋',
  141. });
  142. } catch (error) {
  143. createLog({
  144. status: 'error',
  145. method: 'disconnect',
  146. message: error.message,
  147. });
  148. }
  149. }, [createLog, disconnect, publicKey, wallet]);
  150. const connectedMethods = useMemo(() => {
  151. return [
  152. {
  153. name: 'Sign Message',
  154. onClick: handleSignMessage,
  155. },
  156. {
  157. name: 'Sign In',
  158. onClick: handleSignIn,
  159. },
  160. {
  161. name: 'Sign In Error',
  162. onClick: handleSignInError,
  163. },
  164. {
  165. name: 'Disconnect',
  166. onClick: handleDisconnect,
  167. },
  168. ];
  169. }, [
  170. handleSignMessage,
  171. handleSignIn,
  172. handleSignInError,
  173. handleDisconnect,
  174. ]);
  175. return (
  176. <StyledApp>
  177. <Sidebar publicKey={publicKey} connectedMethods={connectedMethods} connect={handleConnect} />
  178. <Logs publicKey={publicKey} logs={logs} clearLogs={clearLogs} />
  179. </StyledApp>
  180. );
  181. };
  182. // =============================================================================
  183. // Main Component
  184. // =============================================================================
  185. const App = () => {
  186. const network = WalletAdapterNetwork.Mainnet;
  187. const endpoint = `https://api.mainnet-beta.solana.com`;
  188. const wallets = useMemo(
  189. () => [], // confirmed also with `() => []` for wallet-standard only
  190. // eslint-disable-next-line react-hooks/exhaustive-deps
  191. [network]
  192. );
  193. const autoSignIn = useCallback(async (adapter: Adapter) => {
  194. if (!('signIn' in adapter)) return true;
  195. // Fetch the signInInput from the backend
  196. /*
  197. const createResponse = await fetch("/backend/createSignInData");
  198. const input: SolanaSignInInput = await createResponse.json();
  199. */
  200. const input: SolanaSignInInput = await createSignInData();
  201. // Send the signInInput to the wallet and trigger a sign-in request
  202. const output = await adapter.signIn(input);
  203. // Verify the sign-in output against the generated input server-side
  204. /*
  205. let strPayload = JSON.stringify({ input, output: {
  206. account: {
  207. address: output.account.address,
  208. publicKey: Array.from(output.account.publicKey),
  209. },
  210. signature: Array.from(output["signature"]),
  211. signedMessage: Array.from(output["signedMessage"]),
  212. } });
  213. const verifyResponse = await fetch("/backend/verifySIWS", {
  214. method: "POST",
  215. body: strPayload,
  216. });
  217. const success = await verifyResponse.json();
  218. */
  219. // For demonstration purposes only, this should happen server-side
  220. if (!verifySignIn(input, output)) {
  221. console.error('Sign In verification failed!')
  222. throw new Error('Sign In verification failed!');
  223. }
  224. return false;
  225. }, []);
  226. return (
  227. <AutoConnectProvider>
  228. <ConnectionProvider endpoint={endpoint}>
  229. <WalletProvider wallets={wallets} autoConnect={autoSignIn}>
  230. <WalletModalProvider>
  231. <StatelessApp />
  232. </WalletModalProvider>
  233. </WalletProvider>
  234. </ConnectionProvider>
  235. </AutoConnectProvider>
  236. );
  237. };
  238. export default App;