We've updated our Terms of Service. By continuing to use our services, you agree to the updated Terms.
guides
Guide

How to use email to create wallets on Solana

Magic Staff · January 29, 2024
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:

  1. A Magic Publishable API Key
  2. 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
Bash
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:

NPM
Yarn
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.

Plaintext
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.

Typescript
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.

Typescript
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.

Typescript
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.

Typescript
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:

  1. Simple authentication with Email OTP
  2. Automatic Solana wallet creation for first-time users
  3. 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

Let's make some magic!