guides
Guide

How to use Magic with the NEAR Blockchain

Magic Staff · December 21, 2021

#Overview

#Resources

Github Repo

Live Demo

# Quick Start

Bash
01$ git clone https://github.com/magiclabs/magic-near-guide.git
02$ cd magic-near-guide
03// Enter your Magic API key in `.env` such as
04// REACT_APP_MAGIC_PUBLISHABLE_KEY=pk_live_abc123
05$ yarn
06$ yarn start
07// Visit your app on http://localhost:3000

# What is NEAR

NEAR Protocol is a layer-one blockchain with features that include Proof-of-Stake consensus, smart contract support, high scalability, and human-readable addressing. 

Dissimilar to Ethereum and EVM-compatible chains which use the Solidity programming language, smart contracts on NEAR are written in Rust or AssemblyScript (similar to JavaScript). One of the major benefits building on the NEAR blockchain is that developers can build decentralized applications that maintain security while also providing scalability that popular dApps require. The high transaction throughput is enabled through sharding, which divides the computation required from nodes, so each only need to process transactions relevant to their respective shard.

An important differentiator with NEAR compared to other L1s is that it supports human-readable accounts at the base layer, such as magic.near, instead of non-human-readable addresses such as 0x2130c8ab2c23a33... found in other blockchains, making NEAR more user-friendly.

The native token for the blockchain is NEAR, which is used to pay for all transaction fees on the platform.

#Get Testnet Tokens

  1. Create a test wallet on wallet.testnet.near.org. 200 NEAR test tokens will automatically be deposited.
  2. Login to your Magic-NEAR app with email, Google, or phone number to get your public address.
  3. Transfer funds from your wallet on wallet.testnet.near.org to your Magic wallet.

#Tutorial

At the end of this tutorial, users will be able to login to your dapp with just an email, social provider, or phone number, create a NEAR wallet, then receive and transfer funds.

# Dependencies

01$ yarn add magic-sdk @magic-ext/near @magic-ext/oauth near-api-js

# Login With Magic

Magic's NearExtension is required to access NEAR-specific sdk methods. The extension allows for Magic to manage NEAR private keys through our Delegated Key Management. Transactions are only signed by Magic, rather than sent by Magic, which is why the rpcUrl key is empty. 

If wanting to allow a user to login via a social provider, add new OAuthExtension() to the extensions array.

Javascript
01import { Magic } from 'magic-sdk';
02import { NearExtension } from "@magic-ext/near";
03import { OAuthExtension } from '@magic-ext/oauth'; 
04
05export const magic = new Magic(process.env.REACT_APP_MAGIC_PUBLISHABLE_KEY, {
06  extensions: [
07    new NearExtension({
08      rpcUrl: '',
09    }),
10    new OAuthExtension()
11  ]}
12);

The Magic login flow sends a magic link to a user's inbox, which when clicked, authenticates them on your app while also generating a random private key and NEAR public address. This one line kicks off the entire flow on behalf of the developer, await magic.auth.loginWithMagicLink({ email });.

Javascript
01const login = useCallback(async () => {
02 setIsLoggingIn(true); 
03 try {
04   await magic.auth.loginWithMagicLink({ email });
05   history.push("/");
06 } catch {
07   setIsLoggingIn(false);
08 }
09}, [email]);

Users can also login with their phone number.

Javascript
01const loginWithSMS = useCallback(async () => {
02   setIsLoggingIn(true);
03   try {
04     await magic.auth.loginWithSMS({ phoneNumber });
05     history.push("/");
06   } catch (err) {
07     console.log(err);
08     setIsLoggingIn(false);
09   }
10 }, [phoneNumber]);

Lastly, Magic also supports social providers, such as Google. This function takes two parameters, first being the social provider (Google, Facebook, Apple, etc) and second being a callback URL for where the user should get directed to on your application after authenticating with the social provider and Magic.

Javascript
01const handleLoginWithGoogle = async (e) => {
02   e.preventDefault();
03   await magic.oauth.loginWithRedirect({
04     provider: "google",
05     redirectURI: `${window.location.origin}/callback`
06   });
07 };

To handle the callback (in Callback.js), we can simply call magic.oauth.getRedirectResult() which will return the user details returned from the social provider, as well as an user details from magic including publicAddress.

Javascript
01useEffect(() => {
02     magic.oauth.getRedirectResult().then((result) => {
03       console.log(result);
04       history.push("/");
05     });
06 }, []);

You can allow a user to logout with the following code snippet.

Javascript
01const logout = useCallback(() => {
02 magic.user.logout().then(() => {
03   history.push("/login");
04 })
05}, [history]);

# Display User Data

After a user has successfully logged in, you can display their data, such as email and publicAddress.

Javascript
01const [userMetadata, setUserMetadata] = useState();
02
03useEffect(() => {
04 // If user is logged in, retrieve the authenticated user's profile.
05 magic.user.isLoggedIn().then(magicIsLoggedIn => {
06   if (magicIsLoggedIn) {
07     magic.user.getMetadata().then(user => {
08       setUserMetadata(user);
09     });
10   } else {
11     // If no user is logged in, redirect to `/login`
12     history.push("/login");
13   }
14 });
15}, []);
16 
17return (
18 <>
19   <div>Email</div>
20   <div>{userMetadata.email}</div>
21   <div>NEAR Address</div>
22   <div>{userMetadata.publicAddress}</div>
23 </>
24)

#Fetch User NEAR Balance

You can use the near-api-js package to connect to a NEAR blockchain to get a user's balance (this connection will later be used to broadcast a signed transaction).

Javascript
01import * as nearAPI from "near-api-js";
02
03const [balance, setBalance] = useState(0);
04const networkId = "testnet"; // testnet, betanet, or mainnet
05 
06useEffect(() => {
07 const { connect, keyStores } = nearAPI;
08 
09 const config = {
10   networkId,
11   keyStore: new keyStores.BrowserLocalStorageKeyStore(),
12   nodeUrl: `https://rpc.${networkId}.near.org`,
13   walletUrl: `https://wallet.${networkId}.near.org`,
14   helperUrl: `https://helper.${networkId}.near.org`,
15   explorerUrl: `https://explorer.${networkId}.near.org`,
16 };
17
18 // connect to NEAR
19 near = await connect(config);
20})
21 
22const fetchBalance = async (address) => {
23 const account = await near.account(address);
24 account.getAccountBalance().then(bal => setBalance(nearAPI.utils.format.formatNearAmount(bal.total)));
25}
26 
27return (
28 <>
29   <div>Balance</div>
30   <div>{balance} NEAR</div>
31 </>
32)

#Send Transaction

In transferring assets on the NEAR blockchain, Magic's role is signing the transaction object since Magic is the key management provider. You then use the near instance created earlier, which is a connection to the blockchain, to broadcast the signed transaction to the network. A NEAR transaction consists of six fields, each broken down below.

1. sender: the sender's public address. This can be either the raw 64-character public address, or .near syntax such as bob.near (or .testnet syntax for NEARs testnet)

2. publicKey: the sending address' public key, which is an object with two key value pairs, keyType and data.

3. receiver: the receiving public address. This can be either the raw 64-character public address, or .near syntax such as bob.near (or .testnet syntax for NEARs testnet)

4. nonce: this number represents the number of transactions that an account has sent, including the transaction being constructed, and is meant to prevent replay attacks. The first transaction sent from a user's wallet will have nonce: 1, the second will have nonce: 2, etc.

5. actions: describes what should be done at the receiver's end. Transfer represents sendings funds from one wallet to another, DeployContract represents a contract deployment transaction. In total there are eight different transaction types.

6. recentBlockHash: each transaction is required to be sent with the hash of a block from the last 24 hours to prove the transaction was recently created.

This is what a sendTransaction function with Magic can look like.

Javascript
01const sendTransaction = async () => {
02 // Grab user's public key from Magic
03 const publicKeyString = await magic.near.getPublicKey();
04 const publicKey = nearAPI.utils.PublicKey.fromString(publicKeyString);
05 
06 // Calculate the sending account's nonce
07 const provider = new nearAPI.providers.JsonRpcProvider(
08   `https://rpc.${networkId}.near.org`
09 );
10 const accessKey = await provider.query(
11   `access_key/${userMetadata.publicAddress}/${publicKey.toString()}`,
12   ""
13 );
14 const nonce = ++accessKey.nonce;
15 
16 // Calculate `actions`
17 const actions = [nearAPI.transactions.transfer(nearAPI.utils.format.parseNearAmount(sendAmount))];
18 
19 // Get recent block hash
20 const status = await near.connection.provider.status();
21 const blockHash = status.sync_info.latest_block_hash;
22 const serializedBlockHash = nearAPI.utils.serialize.base_decode(blockHash);   
23 
24 // Construct transaction object
25 const transaction = nearAPI.transactions.createTransaction(
26   userMetadata.publicAddress,
27   publicKey,
28   destinationAddress,
29   nonce,
30   actions,
31   serializedBlockHash
32 );
33 
34 const rawTransaction = transaction.encode();
35  // Sign raw transaction with Magic
36 const result = await magic.near.signTransaction({rawTransaction, networkID: networkId});
37 const signedTransaction = nearAPI.transactions.SignedTransaction.decode(Buffer.from(result.encodedSignedTransaction));
38 
39 // Send the signed transaction with `near` 
40const receipt = await near.connection.provider.sendTransaction(signedTransaction);
41 console.log(receipt);
42}

#Conclusion

You now have an application that allows a user to generate a NEAR wallet with just their email and transfer funds!

Let's make some magic!