├── .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 | Phantom 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 | --------------------------------------------------------------------------------