├── .codesandbox
└── workspace.json
├── .gitignore
├── .prettierrc
├── README.md
├── package-lock.json
├── package.json
├── public
└── index.html
├── src
├── App.tsx
├── components
│ ├── Button
│ │ └── index.tsx
│ ├── Logs
│ │ ├── Log.tsx
│ │ └── index.tsx
│ ├── NoProvider
│ │ └── index.tsx
│ ├── Sidebar
│ │ └── index.tsx
│ └── index.tsx
├── constants.ts
├── index.tsx
├── react-app-env.d.ts
├── types.ts
└── utils
│ ├── createAddressLookupTable.ts
│ ├── createTransferTransaction.ts
│ ├── createTransferTransactionV0.ts
│ ├── extendAddressLookupTable.ts
│ ├── getProvider.ts
│ ├── hexToRGB.ts
│ ├── index.ts
│ ├── pollSignatureStatus.ts
│ ├── signAllTransactions.ts
│ ├── signAndSendTransaction.ts
│ ├── signAndSendTransactionV0WithLookupTable.ts
│ ├── signMessage.ts
│ └── signTransaction.ts
├── tsconfig.json
└── yarn.lock
/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [
4 | 320,
5 | 675
6 | ],
7 | "Tablet": [
8 | 1024,
9 | 765
10 | ],
11 | "Desktop": [
12 | 1400,
13 | 800
14 | ],
15 | "Desktop HD": [
16 | 1920,
17 | 1080
18 | ]
19 | }
20 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules
3 |
4 | # MacOS
5 | .DS_Store
6 |
7 | # VSCode
8 | .vscode/
9 | workspace*
10 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "semi": true,
4 | "singleQuote": true,
5 | "trailingComma": "es5"
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phantom Wallet Sandbox
2 |
3 | > A CodeSandbox for learning how to interact with Phantom Wallet
4 |
5 | [Play with the sandbox in full view](https://r3byv.csb.app/)
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@phantom-labs/sandbox",
3 | "version": "1.0.0",
4 | "description": "The official CodeSandbox for learning how to interact with Phantom Wallet.",
5 | "license": "MIT",
6 | "keywords": [
7 | "phantom",
8 | "phantom wallet",
9 | "phantom-wallet",
10 | "codesandbox",
11 | "solana",
12 | "ethereum",
13 | "crypto",
14 | "blockchain",
15 | "love"
16 | ],
17 | "main": "src/index.tsx",
18 | "dependencies": {
19 | "@solana/web3.js": "1.63.1"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^17.0.21",
23 | "@types/react-dom": "^17.0.9",
24 | "@types/styled-components": "^5.1.25",
25 | "react-scripts": "^4.0.3",
26 | "typescript": "^4.4.3"
27 | },
28 | "peerDependencies": {
29 | "react": "^18.2.0",
30 | "react-dom": "^17.0.2",
31 | "styled-components": "^5.3.5"
32 | },
33 | "scripts": {
34 | "start": "react-scripts start",
35 | "build": "react-scripts build",
36 | "test": "react-scripts test --env=jsdom",
37 | "tsc": "tsc"
38 | },
39 | "browserslist": [
40 | ">0.2%",
41 | "not dead",
42 | "not ie <= 11",
43 | "not op_mini all"
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Phantom Wallet – CodeSandbox
13 |
14 |
25 |
26 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @DEV: If the sandbox is throwing dependency errors, chances are you need to clear your browser history.
3 | * This will trigger a re-install of the dependencies in the sandbox – which should fix things right up.
4 | * Alternatively, you can fork this sandbox to refresh the dependencies manually.
5 | */
6 | import React, { useState, useEffect, useCallback, useMemo } from 'react';
7 | import styled from 'styled-components';
8 | import { Connection, PublicKey } from '@solana/web3.js';
9 |
10 | import {
11 | createAddressLookupTable,
12 | createTransferTransaction,
13 | createTransferTransactionV0,
14 | extendAddressLookupTable,
15 | getProvider,
16 | pollSignatureStatus,
17 | signAllTransactions,
18 | signAndSendTransaction,
19 | signAndSendTransactionV0WithLookupTable,
20 | signMessage,
21 | signTransaction,
22 | } from './utils';
23 |
24 | import { TLog } from './types';
25 |
26 | import { Logs, Sidebar, NoProvider } from './components';
27 |
28 | // =============================================================================
29 | // Styled Components
30 | // =============================================================================
31 |
32 | const StyledApp = styled.div`
33 | display: flex;
34 | flex-direction: row;
35 | height: 100vh;
36 | @media (max-width: 768px) {
37 | flex-direction: column;
38 | }
39 | `;
40 |
41 | // =============================================================================
42 | // Constants
43 | // =============================================================================
44 |
45 | // NB: This URL will only work for Phantom sandbox apps! Please do not use this for your project.
46 | const NETWORK = 'https://phantom-phantom-f0ad.mainnet.rpcpool.com/';
47 | const provider = getProvider();
48 | const connection = new Connection(NETWORK);
49 | const message = 'To avoid digital dognappers, sign below to authenticate with CryptoCorgis.';
50 |
51 | // =============================================================================
52 | // Typedefs
53 | // =============================================================================
54 |
55 | export type ConnectedMethods =
56 | | {
57 | name: string;
58 | onClick: () => Promise;
59 | }
60 | | {
61 | name: string;
62 | onClick: () => Promise;
63 | };
64 |
65 | interface Props {
66 | publicKey: PublicKey | null;
67 | connectedMethods: ConnectedMethods[];
68 | handleConnect: () => Promise;
69 | logs: TLog[];
70 | clearLogs: () => void;
71 | }
72 |
73 | // =============================================================================
74 | // Hooks
75 | // =============================================================================
76 |
77 | /**
78 | * @DEVELOPERS
79 | * The fun stuff!
80 | */
81 | const useProps = (): Props => {
82 | const [logs, setLogs] = useState([]);
83 |
84 | const createLog = useCallback(
85 | (log: TLog) => {
86 | return setLogs((logs) => [...logs, log]);
87 | },
88 | [setLogs]
89 | );
90 |
91 | const clearLogs = useCallback(() => {
92 | setLogs([]);
93 | }, [setLogs]);
94 |
95 | useEffect(() => {
96 | if (!provider) return;
97 |
98 | // attempt to eagerly connect
99 | provider.connect({ onlyIfTrusted: true }).catch(() => {
100 | // fail silently
101 | });
102 |
103 | provider.on('connect', (publicKey: PublicKey) => {
104 | createLog({
105 | status: 'success',
106 | method: 'connect',
107 | message: `Connected to account ${publicKey.toBase58()}`,
108 | });
109 | });
110 |
111 | provider.on('disconnect', () => {
112 | createLog({
113 | status: 'warning',
114 | method: 'disconnect',
115 | message: '👋',
116 | });
117 | });
118 |
119 | provider.on('accountChanged', (publicKey: PublicKey | null) => {
120 | if (publicKey) {
121 | createLog({
122 | status: 'info',
123 | method: 'accountChanged',
124 | message: `Switched to account ${publicKey.toBase58()}`,
125 | });
126 | } else {
127 | /**
128 | * In this case dApps could...
129 | *
130 | * 1. Not do anything
131 | * 2. Only re-connect to the new account if it is trusted
132 | *
133 | * ```
134 | * provider.connect({ onlyIfTrusted: true }).catch((err) => {
135 | * // fail silently
136 | * });
137 | * ```
138 | *
139 | * 3. Always attempt to reconnect
140 | */
141 |
142 | createLog({
143 | status: 'info',
144 | method: 'accountChanged',
145 | message: 'Attempting to switch accounts.',
146 | });
147 |
148 | provider.connect().catch((error) => {
149 | createLog({
150 | status: 'error',
151 | method: 'accountChanged',
152 | message: `Failed to re-connect: ${error.message}`,
153 | });
154 | });
155 | }
156 | });
157 |
158 | return () => {
159 | provider.disconnect();
160 | };
161 | }, [createLog]);
162 |
163 | /** SignAndSendTransaction */
164 | const handleSignAndSendTransaction = useCallback(async () => {
165 | if (!provider) return;
166 |
167 | try {
168 | const transaction = await createTransferTransaction(provider.publicKey, connection);
169 | createLog({
170 | status: 'info',
171 | method: 'signAndSendTransaction',
172 | message: `Requesting signature for: ${JSON.stringify(transaction)}`,
173 | });
174 | const signature = await signAndSendTransaction(provider, transaction);
175 | createLog({
176 | status: 'info',
177 | method: 'signAndSendTransaction',
178 | message: `Signed and submitted transaction ${signature}.`,
179 | });
180 | pollSignatureStatus(signature, connection, createLog);
181 | } catch (error) {
182 | createLog({
183 | status: 'error',
184 | method: 'signAndSendTransaction',
185 | message: error.message,
186 | });
187 | }
188 | }, [createLog]);
189 |
190 | /** SignAndSendTransactionV0 */
191 | const handleSignAndSendTransactionV0 = useCallback(async () => {
192 | if (!provider) return;
193 |
194 | try {
195 | const transactionV0 = await createTransferTransactionV0(provider.publicKey, connection);
196 | createLog({
197 | status: 'info',
198 | method: 'signAndSendTransactionV0',
199 | message: `Requesting signature for: ${JSON.stringify(transactionV0)}`,
200 | });
201 | const signature = await signAndSendTransaction(provider, transactionV0);
202 | createLog({
203 | status: 'info',
204 | method: 'signAndSendTransactionV0',
205 | message: `Signed and submitted transactionV0 ${signature}.`,
206 | });
207 | pollSignatureStatus(signature, connection, createLog);
208 | } catch (error) {
209 | createLog({
210 | status: 'error',
211 | method: 'signAndSendTransactionV0',
212 | message: error.message,
213 | });
214 | }
215 | }, [createLog]);
216 |
217 | /** SignAndSendTransactionV0WithLookupTable */
218 | const handleSignAndSendTransactionV0WithLookupTable = useCallback(async () => {
219 | if (!provider) return;
220 | try {
221 | const [lookupSignature, lookupTableAddress] = await createAddressLookupTable(
222 | provider,
223 | provider.publicKey,
224 | connection,
225 | await connection.getLatestBlockhash().then((res) => res.blockhash)
226 | );
227 | createLog({
228 | status: 'info',
229 | method: 'signAndSendTransactionV0WithLookupTable',
230 | message: `Signed and submitted transactionV0 to make an Address Lookup Table ${lookupTableAddress} with signature: ${lookupSignature}. Please wait for 5-7 seconds after signing the next transaction to be able to see the next transaction popup. This time is needed as newly appended addresses require one slot to warmup before being available to transactions for lookups.`,
231 | });
232 | const extensionSignature = await extendAddressLookupTable(
233 | provider,
234 | provider.publicKey,
235 | connection,
236 | await connection.getLatestBlockhash().then((res) => res.blockhash),
237 | lookupTableAddress
238 | );
239 | createLog({
240 | status: 'info',
241 | method: 'signAndSendTransactionV0WithLookupTable',
242 | message: `Signed and submitted transactionV0 to extend Address Lookup Table ${extensionSignature}.`,
243 | });
244 |
245 | const signature = await signAndSendTransactionV0WithLookupTable(
246 | provider,
247 | provider.publicKey,
248 | connection,
249 | await connection.getLatestBlockhash().then((res) => res.blockhash),
250 | lookupTableAddress
251 | );
252 | createLog({
253 | status: 'info',
254 | method: 'signAndSendTransactionV0WithLookupTable',
255 | message: `Signed and submitted transactionV0 with Address Lookup Table ${signature}.`,
256 | });
257 | pollSignatureStatus(signature, connection, createLog);
258 | } catch (error) {
259 | createLog({
260 | status: 'error',
261 | method: 'signAndSendTransactionV0WithLookupTable',
262 | message: error.message,
263 | });
264 | }
265 | }, [createLog]);
266 |
267 | /** SignTransaction */
268 | const handleSignTransaction = useCallback(async () => {
269 | if (!provider) return;
270 |
271 | try {
272 | const transaction = await createTransferTransaction(provider.publicKey, connection);
273 | createLog({
274 | status: 'info',
275 | method: 'signTransaction',
276 | message: `Requesting signature for: ${JSON.stringify(transaction)}`,
277 | });
278 | const signedTransaction = await signTransaction(provider, transaction);
279 | createLog({
280 | status: 'success',
281 | method: 'signTransaction',
282 | message: `Transaction signed: ${JSON.stringify(signedTransaction)}`,
283 | });
284 | } catch (error) {
285 | createLog({
286 | status: 'error',
287 | method: 'signTransaction',
288 | message: error.message,
289 | });
290 | }
291 | }, [createLog]);
292 |
293 | /** SignAllTransactions */
294 | const handleSignAllTransactions = useCallback(async () => {
295 | if (!provider) return;
296 |
297 | try {
298 | const transactions = [
299 | await createTransferTransaction(provider.publicKey, connection),
300 | await createTransferTransaction(provider.publicKey, connection),
301 | ];
302 | createLog({
303 | status: 'info',
304 | method: 'signAllTransactions',
305 | message: `Requesting signature for: ${JSON.stringify(transactions)}`,
306 | });
307 | const signedTransactions = await signAllTransactions(provider, transactions[0], transactions[1]);
308 | createLog({
309 | status: 'success',
310 | method: 'signAllTransactions',
311 | message: `Transactions signed: ${JSON.stringify(signedTransactions)}`,
312 | });
313 | } catch (error) {
314 | createLog({
315 | status: 'error',
316 | method: 'signAllTransactions',
317 | message: error.message,
318 | });
319 | }
320 | }, [createLog]);
321 |
322 | /** SignMessage */
323 | const handleSignMessage = useCallback(async () => {
324 | if (!provider) return;
325 |
326 | try {
327 | const signedMessage = await signMessage(provider, message);
328 | createLog({
329 | status: 'success',
330 | method: 'signMessage',
331 | message: `Message signed: ${JSON.stringify(signedMessage)}`,
332 | });
333 | return signedMessage;
334 | } catch (error) {
335 | createLog({
336 | status: 'error',
337 | method: 'signMessage',
338 | message: error.message,
339 | });
340 | }
341 | }, [createLog]);
342 |
343 | /** Connect */
344 | const handleConnect = useCallback(async () => {
345 | if (!provider) return;
346 |
347 | try {
348 | await provider.connect();
349 | } catch (error) {
350 | createLog({
351 | status: 'error',
352 | method: 'connect',
353 | message: error.message,
354 | });
355 | }
356 | }, [createLog]);
357 |
358 | /** Disconnect */
359 | const handleDisconnect = useCallback(async () => {
360 | if (!provider) return;
361 |
362 | try {
363 | await provider.disconnect();
364 | } catch (error) {
365 | createLog({
366 | status: 'error',
367 | method: 'disconnect',
368 | message: error.message,
369 | });
370 | }
371 | }, [createLog]);
372 |
373 | const connectedMethods = useMemo(() => {
374 | return [
375 | {
376 | name: 'Sign and Send Transaction (Legacy)',
377 | onClick: handleSignAndSendTransaction,
378 | },
379 | {
380 | name: 'Sign and Send Transaction (v0)',
381 | onClick: handleSignAndSendTransactionV0,
382 | },
383 | {
384 | name: 'Sign and Send Transaction (v0 + Lookup table)',
385 | onClick: handleSignAndSendTransactionV0WithLookupTable,
386 | },
387 | {
388 | name: 'Sign Transaction',
389 | onClick: handleSignTransaction,
390 | },
391 | {
392 | name: 'Sign All Transactions',
393 | onClick: handleSignAllTransactions,
394 | },
395 | {
396 | name: 'Sign Message',
397 | onClick: handleSignMessage,
398 | },
399 | {
400 | name: 'Disconnect',
401 | onClick: handleDisconnect,
402 | },
403 | ];
404 | }, [
405 | handleSignAndSendTransaction,
406 | handleSignAndSendTransactionV0,
407 | handleSignAndSendTransactionV0WithLookupTable,
408 | handleSignTransaction,
409 | handleSignAllTransactions,
410 | handleSignMessage,
411 | handleDisconnect,
412 | ]);
413 |
414 | return {
415 | publicKey: provider?.publicKey || null,
416 | connectedMethods,
417 | handleConnect,
418 | logs,
419 | clearLogs,
420 | };
421 | };
422 |
423 | // =============================================================================
424 | // Stateless Component
425 | // =============================================================================
426 |
427 | const StatelessApp = React.memo((props: Props) => {
428 | const { publicKey, connectedMethods, handleConnect, logs, clearLogs } = props;
429 |
430 | return (
431 |
432 |
433 |
434 |
435 | );
436 | });
437 |
438 | // =============================================================================
439 | // Main Component
440 | // =============================================================================
441 |
442 | const App = () => {
443 | const props = useProps();
444 |
445 | if (!provider) {
446 | return ;
447 | }
448 |
449 | return ;
450 | };
451 |
452 | export default App;
453 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { WHITE, DARK_GRAY, LIGHT_GRAY } from '../../constants';
4 |
5 | import { hexToRGB } from '../../utils';
6 |
7 | const Button = styled.button`
8 | cursor: pointer;
9 | width: 100%;
10 | color: ${WHITE};
11 | background-color: ${DARK_GRAY};
12 | padding: 15px 10px;
13 | font-weight: 600;
14 | outline: 0;
15 | border: 0;
16 | border-radius: 6px;
17 | user-select: none;
18 | &:hover {
19 | background-color: ${hexToRGB(LIGHT_GRAY, 0.9)};
20 | }
21 | &:focus-visible&:not(:hover) {
22 | background-color: ${hexToRGB(LIGHT_GRAY, 0.8)};
23 | }
24 | &:active {
25 | background-color: ${LIGHT_GRAY};
26 | }
27 | `;
28 |
29 | export default Button;
30 |
--------------------------------------------------------------------------------
/src/components/Logs/Log.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { Status, TLog } from '../../types';
5 |
6 | import { RED, YELLOW, GREEN, BLUE, PURPLE } from '../../constants';
7 |
8 | // =============================================================================
9 | // Styled Components
10 | // =============================================================================
11 |
12 | const Column = styled.div`
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | line-height: 1.5;
17 | `;
18 |
19 | const Row = styled.div`
20 | display: flex;
21 | flex-direction: row;
22 | align-items: center;
23 | `;
24 |
25 | const StyledSpan = styled.span<{ status: Status }>`
26 | color: ${(props) => {
27 | switch (props.status) {
28 | case 'success':
29 | return GREEN;
30 | case 'warning':
31 | return YELLOW;
32 | case 'error':
33 | return RED;
34 | case 'info':
35 | return BLUE;
36 | }
37 | }};
38 | margin-right: 5px;
39 | `;
40 |
41 | const Method = styled.p`
42 | color: ${PURPLE};
43 | margin-right: 10px;
44 | `;
45 |
46 | const Message = styled.p`
47 | overflow-wrap: break-word;
48 | `;
49 |
50 | // =============================================================================
51 | // Main Component
52 | // =============================================================================
53 |
54 | const Log = React.memo((props: TLog) => (
55 |
56 |
57 |
58 | {'>'} {props.status}
59 |
60 | {props.method && [{props.method}]}
61 |
62 | {props.message}
63 | {props.messageTwo && {props.messageTwo}}
64 |
65 | ));
66 |
67 | export default Log;
68 |
--------------------------------------------------------------------------------
/src/components/Logs/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { PublicKey } from '@solana/web3.js';
4 |
5 | import { TLog } from '../../types';
6 |
7 | import { BLACK, GRAY } from '../../constants';
8 |
9 | import Button from '../Button';
10 | import Log from './Log';
11 |
12 | // =============================================================================
13 | // Styled Components
14 | // =============================================================================
15 |
16 | const StyledSection = styled.section`
17 | position: relative;
18 | flex: 2;
19 | padding: 20px;
20 | background-color: ${BLACK};
21 | overflow: auto;
22 | font-family: monospace;
23 | `;
24 |
25 | const ClearLogsButton = styled(Button)`
26 | position: absolute;
27 | top: 20px;
28 | right: 20px;
29 | width: 100px;
30 | `;
31 |
32 | const PlaceholderMessage = styled.p`
33 | color: ${GRAY};
34 | `;
35 |
36 | const Row = styled.div`
37 | display: flex;
38 | flex-direction: row;
39 | align-items: center;
40 | span {
41 | margin-right: 10px;
42 | }
43 | `;
44 |
45 | // =============================================================================
46 | // Typedefs
47 | // =============================================================================
48 |
49 | interface Props {
50 | publicKey: PublicKey | null;
51 | logs: TLog[];
52 | clearLogs: () => void;
53 | }
54 |
55 | // =============================================================================
56 | // Main Component
57 | // =============================================================================
58 |
59 | const Logs = React.memo((props: Props) => {
60 | const { publicKey, logs, clearLogs } = props;
61 |
62 | return (
63 |
64 | {logs.length > 0 ? (
65 | <>
66 | {logs.map((log, i) => (
67 |
68 | ))}
69 | Clear Logs
70 | >
71 | ) : (
72 |
73 | {'>'}
74 |
75 | {publicKey ? (
76 | // connected
77 | <>
78 | Click a button and watch magic happen...{' '}
79 |
80 | ✨
81 |
82 | >
83 | ) : (
84 | // not connected
85 | <>
86 | Welcome to the Phantom sandbox. Connect to your Phantom wallet and play around...{' '}
87 |
88 | 👻
89 |
90 | >
91 | )}
92 |
93 |
94 | )}
95 |
96 | );
97 | });
98 |
99 | export default Logs;
100 |
--------------------------------------------------------------------------------
/src/components/NoProvider/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { REACT_GRAY } from '../../constants';
5 |
6 | // =============================================================================
7 | // Styled Components
8 | // =============================================================================
9 |
10 | const StyledMain = styled.main`
11 | padding: 20px;
12 | height: 100vh;
13 | background-color: ${REACT_GRAY};
14 | `;
15 |
16 | // =============================================================================
17 | // Main Component
18 | // =============================================================================
19 |
20 | // TODO: @PHANTOM-TEAM: Let's improve this UI
21 | const NoProvider = () => {
22 | return (
23 |
24 | Could not find a provider
25 |
26 | );
27 | };
28 |
29 | export default NoProvider;
30 |
--------------------------------------------------------------------------------
/src/components/Sidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PublicKey } from '@solana/web3.js';
3 | import styled from 'styled-components';
4 |
5 | import { GRAY, REACT_GRAY, PURPLE, WHITE, DARK_GRAY } from '../../constants';
6 |
7 | import { hexToRGB } from '../../utils';
8 |
9 | import Button from '../Button';
10 | import { ConnectedMethods } from '../../App';
11 |
12 | // =============================================================================
13 | // Styled Components
14 | // =============================================================================
15 |
16 | const Main = styled.main`
17 | position: relative;
18 | flex: 1;
19 | display: flex;
20 | flex-direction: column;
21 | justify-content: space-between;
22 | padding: 20px;
23 | align-items: center;
24 | background-color: ${REACT_GRAY};
25 | > * {
26 | margin-bottom: 10px;
27 | }
28 | @media (max-width: 768px) {
29 | width: 100%;
30 | height: auto;
31 | }
32 | `;
33 |
34 | const Body = styled.div`
35 | display: flex;
36 | flex-direction: column;
37 | align-items: center;
38 | button {
39 | margin-bottom: 15px;
40 | }
41 | `;
42 |
43 | const Link = styled.a.attrs({
44 | href: 'https://phantom.app/',
45 | target: '_blank',
46 | rel: 'noopener noreferrer',
47 | })`
48 | display: flex;
49 | flex-direction: column;
50 | align-items: flex-end;
51 | text-decoration: none;
52 | margin-bottom: 30px;
53 | padding: 5px;
54 | &:focus-visible {
55 | outline: 2px solid ${hexToRGB(GRAY, 0.5)};
56 | border-radius: 6px;
57 | }
58 | `;
59 |
60 | const Subtitle = styled.h5`
61 | color: ${GRAY};
62 | font-weight: 400;
63 | `;
64 |
65 | const Pre = styled.pre`
66 | margin-bottom: 5px;
67 | `;
68 |
69 | const Badge = styled.div`
70 | margin: 0;
71 | padding: 10px;
72 | width: 100%;
73 | color: ${PURPLE};
74 | background-color: ${hexToRGB(PURPLE, 0.2)};
75 | font-size: 14px;
76 | border-radius: 6px;
77 | @media (max-width: 400px) {
78 | width: 280px;
79 | white-space: nowrap;
80 | overflow: hidden;
81 | text-overflow: ellipsis;
82 | }
83 | @media (max-width: 320px) {
84 | width: 220px;
85 | white-space: nowrap;
86 | overflow: hidden;
87 | text-overflow: ellipsis;
88 | }
89 | ::selection {
90 | color: ${WHITE};
91 | background-color: ${hexToRGB(PURPLE, 0.5)};
92 | }
93 | ::-moz-selection {
94 | color: ${WHITE};
95 | background-color: ${hexToRGB(PURPLE, 0.5)};
96 | }
97 | `;
98 |
99 | const Divider = styled.div`
100 | border: 1px solid ${DARK_GRAY};
101 | height: 1px;
102 | margin: 20px 0;
103 | `;
104 |
105 | const Tag = styled.p`
106 | text-align: center;
107 | color: ${GRAY};
108 | a {
109 | color: ${PURPLE};
110 | text-decoration: none;
111 | ::selection {
112 | color: ${WHITE};
113 | background-color: ${hexToRGB(PURPLE, 0.5)};
114 | }
115 | ::-moz-selection {
116 | color: ${WHITE};
117 | background-color: ${hexToRGB(PURPLE, 0.5)};
118 | }
119 | }
120 | @media (max-width: 320px) {
121 | font-size: 14px;
122 | }
123 | ::selection {
124 | color: ${WHITE};
125 | background-color: ${hexToRGB(PURPLE, 0.5)};
126 | }
127 | ::-moz-selection {
128 | color: ${WHITE};
129 | background-color: ${hexToRGB(PURPLE, 0.5)};
130 | }
131 | `;
132 |
133 | // =============================================================================
134 | // Typedefs
135 | // =============================================================================
136 |
137 | interface Props {
138 | publicKey?: PublicKey;
139 | connectedMethods: ConnectedMethods[];
140 | connect: () => Promise;
141 | }
142 |
143 | // =============================================================================
144 | // Main Component
145 | // =============================================================================
146 |
147 | const Sidebar = React.memo((props: Props) => {
148 | const { publicKey, connectedMethods, connect } = props;
149 |
150 | return (
151 |
152 |
153 |
154 |
155 | CodeSandbox
156 |
157 | {publicKey ? (
158 | // connected
159 | <>
160 |
161 |
Connected as
162 |
{publicKey.toBase58()}
163 |
164 |
165 | {connectedMethods.map((method, i) => (
166 |
169 | ))}
170 | >
171 | ) : (
172 | // not connected
173 |
174 | )}
175 |
176 | {/* 😊 💕 */}
177 |
178 | Made with{' '}
179 |
180 | ❤️
181 | {' '}
182 | by the Phantom team
183 |
184 |
185 | );
186 | });
187 |
188 | export default Sidebar;
189 |
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as Button } from './Button';
2 | export { default as Logs } from './Logs';
3 | export { default as NoProvider } from './NoProvider';
4 | export { default as Sidebar } from './Sidebar';
5 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | // =============================================================================
2 | // Colors
3 | // =============================================================================
4 |
5 | export const RED = '#EB3742';
6 | export const YELLOW = '#FFDC62';
7 | export const GREEN = '#21E56F';
8 | export const BLUE = '#59cff7';
9 | export const PURPLE = '#8A81F8';
10 | export const WHITE = '#FFFFFF';
11 | export const GRAY = '#777777';
12 | export const REACT_GRAY = '#222222';
13 | export const DARK_GRAY = '#333333';
14 | export const LIGHT_GRAY = '#444444';
15 | export const BLACK = '#000000';
16 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { PublicKey, Transaction, VersionedTransaction, SendOptions } from '@solana/web3.js';
2 |
3 | type DisplayEncoding = 'utf8' | 'hex';
4 |
5 | type PhantomEvent = 'connect' | 'disconnect' | 'accountChanged';
6 |
7 | type PhantomRequestMethod =
8 | | 'connect'
9 | | 'disconnect'
10 | | 'signAndSendTransaction'
11 | | 'signAndSendTransactionV0'
12 | | 'signAndSendTransactionV0WithLookupTable'
13 | | 'signTransaction'
14 | | 'signAllTransactions'
15 | | 'signMessage';
16 |
17 | interface ConnectOpts {
18 | onlyIfTrusted: boolean;
19 | }
20 |
21 | export interface PhantomProvider {
22 | publicKey: PublicKey | null;
23 | isConnected: boolean | null;
24 | signAndSendTransaction: (
25 | transaction: Transaction | VersionedTransaction,
26 | opts?: SendOptions
27 | ) => Promise<{ signature: string; publicKey: PublicKey }>;
28 | signTransaction: (transaction: Transaction | VersionedTransaction) => Promise;
29 | signAllTransactions: (
30 | transactions: (Transaction | VersionedTransaction)[]
31 | ) => Promise<(Transaction | VersionedTransaction)[]>;
32 | signMessage: (message: Uint8Array | string, display?: DisplayEncoding) => Promise;
33 | connect: (opts?: Partial) => Promise<{ publicKey: PublicKey }>;
34 | disconnect: () => Promise;
35 | on: (event: PhantomEvent, handler: (args: any) => void) => void;
36 | request: (method: PhantomRequestMethod, params: any) => Promise;
37 | }
38 |
39 | export type Status = 'success' | 'warning' | 'error' | 'info';
40 |
41 | export interface TLog {
42 | status: Status;
43 | method?: PhantomRequestMethod | Extract;
44 | message: string;
45 | messageTwo?: string;
46 | }
47 |
--------------------------------------------------------------------------------
/src/utils/createAddressLookupTable.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AddressLookupTableProgram,
3 | Connection,
4 | PublicKey,
5 | TransactionMessage,
6 | VersionedTransaction,
7 | } from '@solana/web3.js';
8 |
9 | import { PhantomProvider } from '../types';
10 | import { signAndSendTransaction } from '.';
11 |
12 | /**
13 | * 1. Creates an Address Lookup Table Instruction
14 | * 2. Signs and sends it in a transactionV0
15 | *
16 | * @param {String} publicKey a public key
17 | * @param {Connection} connection an RPC connection
18 | * @param {String} publicKey recent blockhash
19 | * @returns {[VersionedTransaction, String]} array of transaction
20 | * signature and lookup table address
21 | */
22 | const createAddressLookupTable = async (
23 | provider: PhantomProvider,
24 | publicKey: PublicKey,
25 | connection: Connection,
26 | blockhash: string
27 | ): Promise<[string, PublicKey]> => {
28 |
29 | // get current `slot`
30 | let slot = await connection.getSlot();
31 |
32 | // create an Address Lookup Table
33 | const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({
34 | authority: publicKey,
35 | payer: publicKey,
36 | recentSlot: slot,
37 | });
38 |
39 | console.log('lookup table address:', lookupTableAddress.toBase58());
40 |
41 | // To create the Address Lookup Table on chain:
42 | // send the `lookupTableInst` instruction in a transaction
43 | const lookupMessage = new TransactionMessage({
44 | payerKey: publicKey,
45 | recentBlockhash: blockhash,
46 | instructions: [lookupTableInst],
47 | }).compileToV0Message();
48 |
49 | const lookupTransaction = new VersionedTransaction(lookupMessage);
50 | const lookupSignature = await signAndSendTransaction(provider, lookupTransaction);
51 | console.log('Sent transaction for lookup table:', lookupSignature);
52 |
53 | return [lookupSignature, lookupTableAddress]
54 | };
55 |
56 | export default createAddressLookupTable;
57 |
--------------------------------------------------------------------------------
/src/utils/createTransferTransaction.ts:
--------------------------------------------------------------------------------
1 | import { Transaction, SystemProgram, Connection, PublicKey } from '@solana/web3.js';
2 |
3 | /**
4 | * Creates an arbitrary transfer transaction
5 | * @param {String} publicKey a public key
6 | * @param {Connection} connection an RPC connection
7 | * @returns {Transaction} a transaction
8 | */
9 | const createTransferTransaction = async (publicKey: PublicKey, connection: Connection): Promise => {
10 | const transaction = new Transaction().add(
11 | SystemProgram.transfer({
12 | fromPubkey: publicKey,
13 | toPubkey: publicKey,
14 | lamports: 100,
15 | })
16 | );
17 | transaction.feePayer = publicKey;
18 |
19 | const anyTransaction: any = transaction;
20 | anyTransaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
21 |
22 | return transaction;
23 | };
24 |
25 | export default createTransferTransaction;
26 |
--------------------------------------------------------------------------------
/src/utils/createTransferTransactionV0.ts:
--------------------------------------------------------------------------------
1 | import { TransactionMessage, VersionedTransaction, SystemProgram, Connection, PublicKey } from '@solana/web3.js';
2 |
3 | /**
4 | * Creates an arbitrary transfer transactionV0 (Versioned Transaction)
5 | * @param {String} publicKey a public key
6 | * @param {Connection} connection an RPC connection
7 | * @returns {VersionedTransaction} a transactionV0
8 | */
9 | const createTransferTransactionV0 = async (
10 | publicKey: PublicKey,
11 | connection: Connection
12 | ): Promise => {
13 | // connect to the cluster and get the minimum rent for rent exempt status
14 | // perform this step to get an "arbitrary" amount to transfer
15 | let minRent = await connection.getMinimumBalanceForRentExemption(0);
16 |
17 | // get latest `blockhash`
18 | let blockhash = await connection.getLatestBlockhash().then((res) => res.blockhash);
19 |
20 | // create an array with your desired `instructions`
21 | // in this case, just a transfer instruction
22 | const instructions = [
23 | SystemProgram.transfer({
24 | fromPubkey: publicKey,
25 | toPubkey: publicKey,
26 | lamports: minRent,
27 | }),
28 | ];
29 |
30 | // create v0 compatible message
31 | const messageV0 = new TransactionMessage({
32 | payerKey: publicKey,
33 | recentBlockhash: blockhash,
34 | instructions,
35 | }).compileToV0Message();
36 |
37 | // make a versioned transaction
38 | const transactionV0 = new VersionedTransaction(messageV0);
39 |
40 | return transactionV0;
41 | };
42 |
43 | export default createTransferTransactionV0;
44 |
--------------------------------------------------------------------------------
/src/utils/extendAddressLookupTable.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AddressLookupTableProgram,
3 | Connection,
4 | PublicKey,
5 | SystemProgram,
6 | TransactionMessage,
7 | VersionedTransaction,
8 | } from '@solana/web3.js';
9 |
10 | import { PhantomProvider } from '../types';
11 | import { signAndSendTransaction } from '.';
12 |
13 | /**
14 | * 1. Extends (add addresses) the table
15 | * 2. Signs and sends the extension instruction
16 | *
17 | * @param {String} publicKey a public key
18 | * @param {Connection} connection an RPC connection
19 | * @param {String} publicKey recent blockhash
20 | * @param {String} publicKey address of the lookup table
21 | * @returns {String} signature of confirmed transaction
22 | */
23 | const extendAddressLookupTable = async (
24 | provider: PhantomProvider,
25 | publicKey: PublicKey,
26 | connection: Connection,
27 | blockhash: string,
28 | lookupTableAddress: PublicKey
29 | ): Promise => {
30 |
31 | // add addresses to the `lookupTableAddress` table via an `extend` instruction
32 | const extendInstruction = AddressLookupTableProgram.extendLookupTable({
33 | payer: publicKey,
34 | authority: publicKey,
35 | lookupTable: lookupTableAddress,
36 | addresses: [
37 | publicKey,
38 | SystemProgram.programId,
39 | // more `publicKey` addresses can be listed here
40 | ],
41 | });
42 |
43 | // Send this `extendInstruction` in a transaction to the cluster
44 | // to insert the listing of `addresses` into your lookup table with address `lookupTableAddress`
45 | const extensionMessageV0 = new TransactionMessage({
46 | payerKey: publicKey,
47 | recentBlockhash: blockhash,
48 | instructions: [extendInstruction],
49 | }).compileToV0Message();
50 |
51 | const extensionTransactionV0 = new VersionedTransaction(extensionMessageV0);
52 | const extensionSignature = await signAndSendTransaction(provider, extensionTransactionV0);
53 |
54 | // Confirm transaction: we will have to wait for the transaction to fetch the
55 | // lookup table account before proceeding: takes around 3-5 seconds to fetch.
56 | const status = (await connection.confirmTransaction(extensionSignature)).value;
57 | if (status.err) {
58 | throw new Error(`Transaction ${extensionSignature} failed (${JSON.stringify(status)})`);
59 | }
60 | console.log('Sent transaction for lookup table extension:', extensionSignature);
61 |
62 | return extensionSignature
63 | };
64 |
65 | export default extendAddressLookupTable;
66 |
--------------------------------------------------------------------------------
/src/utils/getProvider.ts:
--------------------------------------------------------------------------------
1 | import { PhantomProvider } from '../types';
2 |
3 | /**
4 | * Retrieves the Phantom Provider from the window object
5 | * @returns {PhantomProvider | undefined} a Phantom provider if one exists in the window
6 | */
7 | const getProvider = (): PhantomProvider | undefined => {
8 | if ('phantom' in window) {
9 | const anyWindow: any = window;
10 | const provider = anyWindow.phantom?.solana;
11 |
12 | if (provider?.isPhantom) {
13 | return provider;
14 | }
15 | }
16 |
17 | window.open('https://phantom.app/', '_blank');
18 | };
19 |
20 | export default getProvider;
21 |
--------------------------------------------------------------------------------
/src/utils/hexToRGB.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns a color from a hex string and alpha numeric
3 | * @param {String} hex a hex string
4 | * @param {Number} alpha an alpha numeric
5 | * @returns {String} a formatted rgba
6 | */
7 | const hexToRGB = (hex: string, alpha: number) => {
8 | const r = parseInt(hex.slice(1, 3), 16);
9 | const g = parseInt(hex.slice(3, 5), 16);
10 | const b = parseInt(hex.slice(5, 7), 16);
11 |
12 | return `rgba(${r},${g},${b},${alpha})`;
13 | };
14 |
15 | export default hexToRGB;
16 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { default as createAddressLookupTable } from './createAddressLookupTable';
2 | export { default as createTransferTransaction } from './createTransferTransaction';
3 | export { default as createTransferTransactionV0 } from './createTransferTransactionV0';
4 | export { default as extendAddressLookupTable } from './extendAddressLookupTable';
5 | export { default as getProvider } from './getProvider';
6 | export { default as hexToRGB } from './hexToRGB';
7 | export { default as pollSignatureStatus } from './pollSignatureStatus';
8 | export { default as signAllTransactions } from './signAllTransactions';
9 | export { default as signAndSendTransaction } from './signAndSendTransaction';
10 | export { default as signAndSendTransactionV0WithLookupTable } from './signAndSendTransactionV0WithLookupTable';
11 | export { default as signMessage } from './signMessage';
12 | export { default as signTransaction } from './signTransaction';
13 |
--------------------------------------------------------------------------------
/src/utils/pollSignatureStatus.ts:
--------------------------------------------------------------------------------
1 | import { Connection } from '@solana/web3.js';
2 |
3 | import { TLog } from '../types';
4 |
5 | const POLLING_INTERVAL = 1000; // one second
6 | const MAX_POLLS = 30;
7 |
8 | /**
9 | * Polls for transaction signature statuses
10 | * @param {String} signature a transaction signature
11 | * @param {Connection} connection an RPC connection
12 | * @param {Function} createLog a function to create log
13 | * @returns
14 | */
15 | const pollSignatureStatus = async (
16 | signature: string,
17 | connection: Connection,
18 | createLog: (log: TLog) => void
19 | ): Promise => {
20 | let count = 0;
21 |
22 | const interval = setInterval(async () => {
23 | // Failed to confirm transaction in time
24 | if (count === MAX_POLLS) {
25 | clearInterval(interval);
26 | createLog({
27 | status: 'error',
28 | method: 'signAndSendTransaction',
29 | message: `Transaction: ${signature}`,
30 | messageTwo: `Failed to confirm transaction within ${MAX_POLLS} seconds. The transaction may or may not have succeeded.`,
31 | });
32 | return;
33 | }
34 |
35 | const { value } = await connection.getSignatureStatus(signature);
36 | const confirmationStatus = value?.confirmationStatus;
37 |
38 | if (confirmationStatus) {
39 | const hasReachedSufficientCommitment = confirmationStatus === 'confirmed' || confirmationStatus === 'finalized';
40 |
41 | createLog({
42 | status: hasReachedSufficientCommitment ? 'success' : 'info',
43 | method: 'signAndSendTransaction',
44 | message: `Transaction: ${signature}`,
45 | messageTwo: `Status: ${confirmationStatus.charAt(0).toUpperCase() + confirmationStatus.slice(1)}`,
46 | });
47 |
48 | if (hasReachedSufficientCommitment) {
49 | clearInterval(interval);
50 | return;
51 | }
52 | } else {
53 | createLog({
54 | status: 'info',
55 | method: 'signAndSendTransaction',
56 | message: `Transaction: ${signature}`,
57 | messageTwo: 'Status: Waiting on confirmation...',
58 | });
59 | }
60 |
61 | count++;
62 | }, POLLING_INTERVAL);
63 | };
64 |
65 | export default pollSignatureStatus;
66 |
--------------------------------------------------------------------------------
/src/utils/signAllTransactions.ts:
--------------------------------------------------------------------------------
1 | import { Transaction, VersionedTransaction } from '@solana/web3.js';
2 |
3 | import { PhantomProvider } from '../types';
4 |
5 | /**
6 | * Signs an array of transactions
7 | * @param {PhantomProvider} provider a Phantom provider
8 | * @param {Transaction | VersionedTransaction} transaction1 a transaction to sign
9 | * @param {Transaction | VersionedTransaction} transaction2 a transaction to sign
10 | * @returns {(Transaction | VersionedTransaction)[]} an array of signed transactions
11 | */
12 | const signAllTransactions = async (
13 | provider: PhantomProvider,
14 | transaction1: Transaction | VersionedTransaction,
15 | transaction2: Transaction | VersionedTransaction
16 | ): Promise<(Transaction | VersionedTransaction)[]> => {
17 | try {
18 | const transactions = await provider.signAllTransactions([transaction1, transaction2]);
19 | return transactions;
20 | } catch (error) {
21 | console.warn(error);
22 | throw new Error(error.message);
23 | }
24 | };
25 |
26 | export default signAllTransactions;
27 |
--------------------------------------------------------------------------------
/src/utils/signAndSendTransaction.ts:
--------------------------------------------------------------------------------
1 | import { Transaction, VersionedTransaction } from '@solana/web3.js';
2 |
3 | import { PhantomProvider } from '../types';
4 |
5 | /**
6 | * Signs and sends transaction
7 | * @param {PhantomProvider} provider a Phantom Provider
8 | * @param {Transaction} transaction a transaction to sign
9 | * @returns {Transaction} a signed transaction
10 | */
11 | const signAndSendTransaction = async (
12 | provider: PhantomProvider,
13 | transaction: Transaction | VersionedTransaction
14 | ): Promise => {
15 | try {
16 | const { signature } = await provider.signAndSendTransaction(transaction, {skipPreflight: false});
17 | return signature;
18 | } catch (error) {
19 | console.warn(error);
20 | throw new Error(error.message);
21 | }
22 | };
23 |
24 | export default signAndSendTransaction;
25 |
--------------------------------------------------------------------------------
/src/utils/signAndSendTransactionV0WithLookupTable.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Connection,
3 | PublicKey,
4 | SystemProgram,
5 | TransactionMessage,
6 | VersionedTransaction,
7 | } from '@solana/web3.js';
8 |
9 | import { PhantomProvider } from '../types';
10 | import {
11 | createAddressLookupTable,
12 | extendAddressLookupTable,
13 | signAndSendTransaction,
14 | } from '.';
15 | import { Logs } from '../components';
16 |
17 | /**
18 | * Creates an arbitrary transfer transactionV0 (Versioned Transaction),
19 | * uses the Address Lookup Table to fetch the accounts,
20 | * signs this transaction and sends it.
21 | * @param {String} publicKey a public key
22 | * @param {Connection} connection an RPC connection
23 | * @param {String} publicKey recent blockhash
24 | * @param {String} publicKey address of the lookup table
25 | * @returns {VersionedTransaction} a transactionV0
26 | */
27 | const signAndSendTransactionV0WithLookupTable = async (
28 | provider: PhantomProvider,
29 | publicKey: PublicKey,
30 | connection: Connection,
31 | blockhash: string,
32 | lookupTableAddress: PublicKey
33 | ): Promise => {
34 |
35 | // connect to the cluster and get the minimum rent for rent exempt status
36 | // perform this step to get an "arbitrary" amount to transfer
37 | let minRent = await connection.getMinimumBalanceForRentExemption(0);
38 |
39 | // similar to requesting another account (or PDA) from the cluster,
40 | // you can fetch a complete Address Lookup Table with
41 | // the getAddressLookupTable method
42 |
43 | // get the table from the cluster
44 | const lookupTableAccount = await connection.getAddressLookupTable(lookupTableAddress).then((res) => res.value);
45 | // `lookupTableAccount` will now be a `AddressLookupTableAccount` object
46 | console.log('Table address from cluster:', lookupTableAccount.key.toBase58());
47 |
48 | // Our lookupTableAccount variable will now be a AddressLookupTableAccount
49 | // object which we can parse to read the listing of all
50 | // the addresses stored on chain in the lookup table
51 |
52 | // Loop through and parse all the address stored in the table
53 | for (let i = 0; i < lookupTableAccount.state.addresses.length; i++) {
54 | const address = lookupTableAccount.state.addresses[i];
55 | console.log(i, address.toBase58());
56 | }
57 |
58 | // create an array with your desired `instructions`
59 | // in this case, just a transfer instruction
60 | const instructions = [
61 | SystemProgram.transfer({
62 | fromPubkey: publicKey,
63 | toPubkey: publicKey,
64 | lamports: minRent,
65 | }),
66 | ];
67 |
68 | // create v0 compatible message
69 | const messageV0 = new TransactionMessage({
70 | payerKey: publicKey,
71 | recentBlockhash: blockhash,
72 | instructions,
73 | }).compileToV0Message([lookupTableAccount]);
74 |
75 | // make a versioned transaction
76 | const transactionV0 = new VersionedTransaction(messageV0);
77 | const signature = await signAndSendTransaction(provider, transactionV0);
78 | return signature;
79 | };
80 |
81 | export default signAndSendTransactionV0WithLookupTable;
82 |
--------------------------------------------------------------------------------
/src/utils/signMessage.ts:
--------------------------------------------------------------------------------
1 | import { PhantomProvider } from '../types';
2 |
3 | /**
4 | * Signs a message
5 | * @param {PhantomProvider} provider a Phantom Provider
6 | * @param {String} message a message to sign
7 | * @returns {Any} TODO(get type)
8 | */
9 | const signMessage = async (provider: PhantomProvider, message: string): Promise => {
10 | try {
11 | const encodedMessage = new TextEncoder().encode(message);
12 | const signedMessage = await provider.signMessage(encodedMessage);
13 | return signedMessage;
14 | } catch (error) {
15 | console.warn(error);
16 | throw new Error(error.message);
17 | }
18 | };
19 |
20 | export default signMessage;
21 |
--------------------------------------------------------------------------------
/src/utils/signTransaction.ts:
--------------------------------------------------------------------------------
1 | import { Transaction, VersionedTransaction } from '@solana/web3.js';
2 |
3 | import { PhantomProvider } from '../types';
4 |
5 | /**
6 | * Signs a transaction
7 | * @param {PhantomProvider} provider a Phantom Provider
8 | * @param {Transaction | VersionedTransaction} transaction a transaction to sign
9 | * @returns {Transaction | VersionedTransaction} a signed transaction
10 | */
11 | const signTransaction = async (
12 | provider: PhantomProvider,
13 | transaction: Transaction | VersionedTransaction
14 | ): Promise => {
15 | try {
16 | const signedTransaction = await provider.signTransaction(transaction);
17 | return signedTransaction;
18 | } catch (error) {
19 | console.warn(error);
20 | throw new Error(error.message);
21 | }
22 | };
23 |
24 | export default signTransaction;
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "./src/**/*"
4 | ],
5 | "exclude": [
6 | "node_modules"
7 | ],
8 | "compilerOptions": {
9 | "strict": false,
10 | "esModuleInterop": true,
11 | "lib": [
12 | "dom",
13 | "es2015"
14 | ],
15 | "jsx": "react-jsx",
16 | "target": "es6",
17 | "allowJs": true,
18 | "skipLibCheck": true,
19 | "allowSyntheticDefaultImports": true,
20 | "forceConsistentCasingInFileNames": true,
21 | "noFallthroughCasesInSwitch": false,
22 | "module": "esnext",
23 | "moduleResolution": "node",
24 | "resolveJsonModule": true,
25 | "isolatedModules": true,
26 | "noEmit": true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------