How to use email to create wallets on Solana
Magic provides leading wallet-as-a-service so that your users can have the best wallet experience with minimal effort on your part. This guide will walk you through how to use Magic to authenticate your users and have them sign and send transactions on Solana. The code snippets provided are based on a Next.js web app but can be modified to work with virtually any JavaScript framework.
#Project Prerequisites
To follow along with this guide, you’ll need two things:
- A Magic Publishable API Key
- A web client
You can get your Publishable API Key from your Magic Dashboard.
If you already have an existing web client to use, feel free to skip ahead to the section titled Install project dependencies. Otherwise, you can use the make-scoped-magic-app
CLI tool to bootstrap a Next.js app with Magic authentication already baked into the client.
The make-scoped-magic-app
CLI tool is an easy way to bootstrap new projects with Magic. To generate your application, simply run the command below in the shell of your choice. Be sure to replace <YOUR_PUBLISHABLE_API_KEY>
with the Publishable API Key from your Magic Dashboard.
You can also run the command without the flags to be guided interactively through the setup process. If you go through the guided prompts, note that this guide’s code snippets assume that you’ve chosen the following options when prompted:
- Custom Setup
- Solana → Devnet
- Email OTP
01npx make-scoped-magic-app \\
02 --template nextjs-solana-dedicated-wallet \\
03 --network solana-devnet \\
04 --login-methods EmailOTP \\
05 --publishable-api-key <YOUR_PUBLISHABLE_API_KEY>
The resulting project already contains all of the client-side code shown throughout this guide. Go through each section to learn how the code is structured and what it does, but understand there’s no need to write additional code to follow along.
#Install Project Dependencies
To integrate Magic into your project you’ll need the base magic-sdk
package as well as the @magic-ext/solana
extension. The magic-sdk
package handles Magic’s core authentication, user, and wallet management functionality, while the @magic-ext/solana
package adds methods specific to Solana.
You’ll also want to install @solana/web3.js
for Solana-related types and transaction convenience methods.
If you used the make-scoped-magic-app
CLI to bootstrap your project, all three of these packages have been preloaded as dependencies. If you brought your own project to this guide, run the following command to install the required dependencies:
01npm install magic-sdk @magic-ext/solana @solana/web3.js
#Initialize Magic
With the Magic SDK installed, you can initialize Magic with the Magic
constructor. This requires your Publishable API Key (found in your Magic dashboard). We prefer to add this to our .env
file rather than put it directly into our code.
01NEXT_PUBLIC_MAGIC_PUBLISHABLE_KEY=pk_live_1234567890
Where you initialize your Magic instance will depend on your chosen framework and architectural patterns. If you utilized the make-scoped-magic-app
CLI to initialize your project, this setup code has already been completed and can be found in src/components/magic/MagicProvider.tsx
, where Magic is initialized and surfaced to your app using the React Context API.
When initializing your Magic instance, ensure you add a Solana configuration. You do this with the SolanaExtension
object from @magic-ext/solana
. Simply provide your chosen RPC URL and you’re good to go.
01import { Magic } from 'magic-sdk'
02import { SolanaExtension } from '@magic-ext/solana';
03
04const magic = new Magic(process.env.NEXT_PUBLIC_MAGIC_API_KEY as string, {
05 extensions: [
06 new SolanaExtension({
07 rpcUrl: "<https://api.devnet.solana.com>", // or your own preferred RPC URL
08 }),
09 ],
10});
This magic
object will be your client’s access point for all things Magic. Take a look at the client-side API documentation for a list of modules and methods accessible through magic
.
#Initialize a Solana Connection
Every interaction with the Solana network using @solana/web3.js
is facilitated through a Connection
object. This object serves as the gateway to a specific Solana network, often referred to as a “cluster.” In this guide, we'll be utilizing the Devnet cluster.
To employ the Connection
object, create an instance by instantiating a new object and supplying your preferred RPC URL. If you bootstrapped your project with the make-scoped-magic-app
CLI, your client’s Connection
object is initialized in the MagicProvider
and surfaced to the rest of the app using the React Context API.
01import { Connection } from '@solana/web3.js';
02
03const connection = new Connection("<https://api.devnet.solana.com>");
#Create Solana wallets with Email OTP
When Magic authenticates a user for the first time, Magic will automatically generate a new wallet for that user with zero additional work required by you.
Magic provides a number of ways to authenticate users. For simplicity, we’ll stick with one-time passcodes (Email OTP) sent to the user’s email. To set up Email OTP, you’ll need to have a way for users to input their email address, after which you simply call loginWithEmailOTP
from Magic’s Auth module. If the authentication is successful, the return value will be a token representing the user.
If you've generated a Next.js project using the Magic CLI, you will already have a login function created named handleLogin
in src/components/magic/auth/EmailOTP.tsx
.
01const handleLogin = async () => {
02 // handle email format validation and other potential errors
03
04 const didToken = await magic?.auth.loginWithEmailOTP({ email })
05
06 // add custom handling, e.g. store token, send to server, etc
07}
#Send a transaction
To send a transaction, build your transactions as you would normally using helper functions from @solana/web3.js
. When it comes time to sign, however, you’ll use the signTransaction
method accessible through the solana
module on your magic
instance. Once signed, you’ll send the transaction using the sendRawTransaction
method on your connection
object. Below is an example component that transfers SOL from the user’s wallet to another wallet.
01import { LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
02import { useMagic } from '../MagicProvider';
03
04const SendTransaction = () => {
05 const { magic, connection } = useMagic();
06 // add the rest of your state
07
08 const sendTransaction = useCallback(async () => {
09 const userPublicKey = new PublicKey(publicAddress as string);
10 const receiverPublicKey = new PublicKey(toAddress as string);
11
12 const hash = await connection?.getLatestBlockhash();
13 if (!hash) return;
14
15 const transaction = new Transaction({
16 feePayer: userPublicKey,
17 ...hash,
18 });
19
20 const lamportsAmount = Number(amount) * LAMPORTS_PER_SOL;
21
22 const transfer = SystemProgram.transfer({
23 fromPubkey: userPublicKey,
24 toPubkey: receiverPublicKey,
25 lamports: lamportsAmount,
26 });
27
28 transaction.add(transfer);
29
30 // uses Magic to sign a Solana transaction
31 const signedTransaction = await magic?.solana.signTransaction(transaction, {
32 requireAllSignatures: false,
33 verifySignatures: true,
34 });
35
36 // uses the base64 string of signedTransaction and creates a Buffer
37 // sends transaction to Solana network
38 const signature = await connection?.sendRawTransaction(
39 Buffer.from(signedTransaction?.rawTransaction as string, 'base64'),
40 );
41
42 // do something with signature
43 }
44...
45// the rest of the UI component
46}
Notice that the signed transaction returned by magic.solana.signTransaction
has a property rawTransaction
. This value is a base64-encoded string representing the signed transaction. Since the sendRawTransaction
method takes a buffer, we construct a new buffer from the base-64 encoded signedTransaction.rawTransaction
.
To test this out yourself, you’ll need some Devnet SOL. If you’re using the scaffold generated by the make-scoped-magic-app
CLI, there’s a button in the UI to airdrop Devnet SOL to the connected wallet. Otherwise, you can use a faucet like the one provided on Solana’s website.
#Next Steps
You now know how to integrate Magic into your Solana project and include the following features:
- Simple authentication with Email OTP
- Automatic Solana wallet creation for first-time users
- Ability to have users sign and send a transaction on Solana
Feel free to take a look at our final code solution. These are only a few of Magic and Solana’s features and methods. Take a look at the Solana blockchain docs for more information about how to use Magic with your dApps.
#Resources
- The full codebase can be found on GitHub
- Work with the project directly in Codesandbox
- Check out the documentation