diff --git a/.gitignore b/.gitignore
index b512c09..e63ff76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,20 @@
-node_modules
\ No newline at end of file
+# Debugging
+npm-debug.*
+yarn-debug.*
+yarn-error.*
+.eslintcache
+
+# Dependencies
+node_modules
+
+# MacOS
+.DS_Store
+
+# Production
+build
+build.zip
+dist
+
+# VSCode
+.vscode/
+workspace*
diff --git a/.prettierrc b/.prettierrc
index 7197da8..2a46d49 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,4 +1,6 @@
{
- "singleQuote": false,
+ "printWidth": 120,
+ "semi": true,
+ "singleQuote": true,
"trailingComma": "es5"
}
diff --git a/README.md b/README.md
index 7fdc766..87a049c 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
-# sandbox
-Created with CodeSandbox
-https://r3byv.csb.app/
+# Phantom Wallet Sandbox
+
+> A CodeSandbox for learning how to interact with Phantom Wallet
+
+(https://r3byv.csb.app/)[Play with the sandbox in full view]
diff --git a/package.json b/package.json
index 4081f94..bc9c938 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,14 @@
{
- "name": "react-typescript",
+ "name": "@phantom-labs/sandbox",
"version": "1.0.0",
- "description": "React and TypeScript example starter project",
+ "description": "The official CodeSandbox for learning how to interact with Phantom Wallet.",
+ "license": "MIT",
"keywords": [
- "typescript",
- "react",
- "starter"
+ "phantom",
+ "phantom wallet",
+ "phantom-wallet",
+ "solana",
+ "codesandbox"
],
"main": "src/index.tsx",
"dependencies": {
@@ -22,8 +25,7 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
- "test": "react-scripts test --env=jsdom",
- "eject": "react-scripts eject"
+ "test": "react-scripts test --env=jsdom"
},
"browserslist": [
">0.2%",
diff --git a/public/index.html b/public/index.html
index 42ae2d2..ba10eab 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,43 +1,17 @@
-
-
-
- React App
+ Phantom Wallet – CodeSandbox
-
-
-
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index fcf54cd..8fd00f8 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,97 +1,70 @@
-import { useState, useEffect, useCallback } from "react";
-import {
- Connection,
- PublicKey,
- Transaction,
- SystemProgram,
- SendOptions,
-} from "@solana/web3.js";
-import "./styles.css";
-
-type DisplayEncoding = "utf8" | "hex";
-type PhantomEvent = "disconnect" | "connect" | "accountChanged";
-type PhantomRequestMethod =
- | "connect"
- | "disconnect"
- | "signTransaction"
- | "signAllTransactions"
- | "signMessage";
-
-interface ConnectOpts {
- onlyIfTrusted: boolean;
-}
+import { useState, useEffect, useCallback, useMemo } from 'react';
+import { Connection, PublicKey } from '@solana/web3.js';
-interface PhantomProvider {
- publicKey: PublicKey | null;
- isConnected: boolean | null;
- signAndSendTransaction: (
- transaction: Transaction,
- opts?: SendOptions
- ) => Promise<{ signature: string; publicKey: PublicKey }>;
- signTransaction: (transaction: Transaction) => Promise;
- signAllTransactions: (transactions: Transaction[]) => Promise;
- signMessage: (
- message: Uint8Array | string,
- display?: DisplayEncoding
- ) => Promise;
- connect: (opts?: Partial) => Promise<{ publicKey: PublicKey }>;
- disconnect: () => Promise;
- on: (event: PhantomEvent, handler: (args: any) => void) => void;
- request: (method: PhantomRequestMethod, params: any) => Promise;
-}
+import './styles.css';
-const getProvider = (): PhantomProvider | undefined => {
- if ("solana" in window) {
- const anyWindow: any = window;
- const provider = anyWindow.solana;
- if (provider.isPhantom) {
- return provider;
- }
- }
- window.open("https://phantom.app/", "_blank");
-};
+import {
+ getProvider,
+ signAllTransactions,
+ signAndSendTransaction,
+ signMessage,
+ signTransaction,
+ createTransferTransaction,
+ pollSignatureStatus,
+} from './utils';
+
+// =============================================================================
+// Constants
+// =============================================================================
// alternatively, use clusterApiUrl("mainnet-beta");
-const NETWORK = "https://solana-api.projectserum.com";
+export const NETWORK = 'https://solana-api.projectserum.com';
+const provider = getProvider();
+const connection = new Connection(NETWORK);
+const message = 'To avoid digital dognappers, sign below to authenticate with CryptoCorgis.';
+
+// =============================================================================
+// Main Component
+// =============================================================================
-export default function App() {
+const App = () => {
const [, setConnected] = useState(false);
const [publicKey, setPublicKey] = useState(null);
const [logs, setLogs] = useState([]);
+
const addLog = useCallback(
- (log: string) => setLogs((logs) => [...logs, "> " + log]),
- []
+ (log: string) => {
+ return setLogs((logs) => [...logs, '> ' + log]);
+ },
+ [logs]
);
- const provider = getProvider();
- const connection = new Connection(NETWORK);
-
useEffect(() => {
if (!provider) return;
// try to eagerly connect
- provider.connect({ onlyIfTrusted: true }).catch((err) => {
+ provider.connect({ onlyIfTrusted: true }).catch((error) => {
// fail silently
});
- provider.on("connect", (publicKey: PublicKey) => {
+ provider.on('connect', (publicKey: PublicKey) => {
setPublicKey(publicKey);
setConnected(true);
- addLog("[connect] " + publicKey?.toBase58());
+ addLog(`[connect] ${publicKey?.toBase58()}`);
});
- provider.on("disconnect", () => {
+ provider.on('disconnect', () => {
setPublicKey(null);
setConnected(false);
- addLog("[disconnect] 👋");
+ addLog('[disconnect] 👋');
});
- provider.on("accountChanged", (publicKey: PublicKey | null) => {
+ provider.on('accountChanged', (publicKey: PublicKey | null) => {
setPublicKey(publicKey);
if (publicKey) {
- addLog("[accountChanged] Switched account to " + publicKey?.toBase58());
+ addLog(`[accountChanged] Switched account to ${publicKey?.toBase58()}`);
} else {
- addLog("[accountChanged] Switched unknown account");
+ addLog('[accountChanged] Switched unknown account');
// In this case, dapps could not to anything, or,
// Only re-connecting to the new account if it is trusted
// provider.connect({ onlyIfTrusted: true }).catch((err) => {
@@ -100,9 +73,9 @@ export default function App() {
// Or, always trying to reconnect
provider
.connect()
- .then(() => addLog("[accountChanged] Reconnected successfully"))
- .catch((err) => {
- addLog("[accountChanged] Failed to re-connect: " + err.message);
+ .then(() => addLog('[accountChanged] Reconnected successfully'))
+ .catch((error) => {
+ addLog(`[accountChanged] Failed to re-connect: ${error.message}`);
});
}
});
@@ -110,114 +83,117 @@ export default function App() {
return () => {
provider.disconnect();
};
- }, [provider, addLog]);
+ }, [provider]);
if (!provider) {
return Could not find a provider
;
}
- const createTransferTransaction = async () => {
- if (!provider.publicKey) return;
- let transaction = new Transaction().add(
- SystemProgram.transfer({
- fromPubkey: provider.publicKey,
- toPubkey: provider.publicKey,
- lamports: 100,
- })
- );
- transaction.feePayer = provider.publicKey;
- addLog("Getting latest blockhash");
- const anyTransaction: any = transaction;
- anyTransaction.recentBlockhash = (
- await connection.getLatestBlockhash()
- ).blockhash;
- return transaction;
- };
-
- // A simple helper function used to space out our signature polling
- const pause = (ms: number) => new Promise((res) => setTimeout(res, ms));
+ /** SignAndSendTransaction */
+ const handleSignAndSendTransaction = useCallback(async () => {
+ try {
+ const transaction = await createTransferTransaction(provider, connection);
+ addLog(`Requesting signature for: ${JSON.stringify(transaction)}`);
+ const signature = await signAndSendTransaction(provider, transaction);
+ addLog(`Signed and submitted transaction ${signature}, awaiting confirmation...`);
+ pollSignatureStatus(signature, connection, addLog);
+ } catch (error) {
+ console.warn(error);
+ addLog(`[error] signAndSendTransaction: ${JSON.stringify(error)}`);
+ }
+ }, [provider, connection, addLog]);
- const pollSignatureStatus = async (signature: string) => {
- const maxPolls = 10;
- for (let pollCount = 0; pollCount < maxPolls; pollCount++) {
- const { value } = await connection.getSignatureStatus(signature);
- if (value?.confirmationStatus) {
- addLog(`Transaction ${signature} ${value.confirmationStatus}`);
- if (
- value.confirmationStatus === "confirmed" ||
- value.confirmationStatus === "finalized"
- )
- return;
- }
- await pause(1000);
+ /** SignTransaction */
+ const handleSignTransaction = useCallback(async () => {
+ try {
+ const transaction = await createTransferTransaction(provider, connection);
+ addLog(`Requesting signature for: ${JSON.stringify(transaction)}`);
+ const signedTransaction = await signTransaction(provider, transaction);
+ addLog(`Transaction signed: ${JSON.stringify(signedTransaction)}`);
+ } catch (error) {
+ console.warn(error);
+ addLog(`[error] signTransaction: ${JSON.stringify(error)}`);
}
- addLog(`Failed to confirm transaction ${signature}`);
- };
+ }, [provider, connection, addLog]);
- const signAndSendTransaction = async () => {
+ /** SignAllTransactions */
+ const handleSignAllTransactions = useCallback(async () => {
try {
- const transaction = await createTransferTransaction();
- if (!transaction) return;
- addLog("Requesting signature for: " + JSON.stringify(transaction));
- const { signature } = await provider.signAndSendTransaction(transaction);
- addLog(
- "Signed and submitted transaction " +
- signature +
- ", awaiting confirmation..."
- );
- pollSignatureStatus(signature);
- } catch (err) {
- console.warn(err);
- addLog("[error] signAndSendTransaction: " + JSON.stringify(err));
+ const transactions = [
+ await createTransferTransaction(provider, connection),
+ await createTransferTransaction(provider, connection),
+ ];
+ addLog(`Requesting signature for: ${JSON.stringify(transactions)}`);
+ const signedTransactions = await signAllTransactions(provider, transactions[0], transactions[1]);
+ addLog(`Transactions signed: ${JSON.stringify(signedTransactions)}`);
+ } catch (error) {
+ addLog(`[error] signAllTransactions: ${JSON.stringify(error)}`);
}
- };
+ }, [provider, connection, addLog]);
- const signTransaction = async () => {
+ /** SignMessage */
+ const handleSignMessage = useCallback(async () => {
try {
- const transaction = await createTransferTransaction();
- if (!transaction) return;
- addLog("Requesting signature for: " + JSON.stringify(transaction));
- const signedTransaction = await provider.signTransaction(transaction);
- addLog("Transaction signed: " + JSON.stringify(signedTransaction));
- } catch (err) {
- console.warn(err);
- addLog("[error] signTransaction: " + JSON.stringify(err));
+ const signedMessage = await signMessage(provider, message);
+ addLog(`Message signed: ${JSON.stringify(signedMessage)}`);
+ return signedMessage;
+ } catch (error) {
+ console.warn(error);
+ addLog(`[error] signMessage: ${JSON.stringify(error)}`);
}
- };
+ }, [provider, message, addLog]);
- const signAllTransactions = async () => {
+ /** Connect */
+ const handleConnect = useCallback(async () => {
try {
- const [transaction1, transaction2] = await Promise.all([
- createTransferTransaction(),
- createTransferTransaction(),
- ]);
- if (transaction1 && transaction2) {
- addLog(
- "Requesting signature for: " +
- JSON.stringify([transaction1, transaction2])
- );
- const transactions = await provider.signAllTransactions([
- transaction1,
- transaction2,
- ]);
- addLog("Transactions signed: " + JSON.stringify(transactions));
- }
- } catch (err) {
- console.warn(err);
- addLog("[error] signAllTransactions: " + JSON.stringify(err));
+ await provider.connect();
+ } catch (error) {
+ console.warn(error);
+ addLog(`[error] connect: ${JSON.stringify(error)}`);
}
- };
+ }, [provider]);
- const signMessage = async (message: string) => {
+ /** Disconnect */
+ const handleDisconnect = useCallback(async () => {
try {
- const data = new TextEncoder().encode(message);
- const res = await provider.signMessage(data);
- addLog("Message signed: " + JSON.stringify(res));
- } catch (err) {
- console.warn(err);
- addLog("[error] signMessage: " + JSON.stringify(err));
+ await provider.disconnect();
+ } catch (error) {
+ console.warn(error);
+ addLog(`[error] disconnect: ${JSON.stringify(error)}`);
}
- };
+ }, [provider]);
+
+ const methods = useMemo(
+ () => [
+ {
+ name: 'Sign and Send Transaction',
+ onClick: handleSignAndSendTransaction,
+ },
+ {
+ name: 'Sign Transaction',
+ onClick: handleSignTransaction,
+ },
+ {
+ name: 'Sign All Transactions',
+ onClick: handleSignAllTransactions,
+ },
+ {
+ name: 'Sign Message',
+ onClick: handleSignMessage,
+ },
+ {
+ name: 'Disconnect',
+ onClick: handleDisconnect,
+ },
+ ],
+ [
+ handleSignAndSendTransaction,
+ handleSignTransaction,
+ handleSignAllTransactions,
+ handleSignMessage,
+ handleDisconnect,
+ ]
+ );
return (
@@ -231,57 +207,25 @@ export default function App() {
{publicKey.toBase58()}
-
-
-
-
-
+ {methods.map((method, i) => (
+
+ ))}
>
) : (
- <>
-
- >
+
)}