Introduction

There are two main approaches to implementing the SDK, one where Crossmint takes care of securing the users keys (in a non custodial way, using an MPC signer backed by Fireblocks), and one where you are responsible of managing the user’s keys, exposing your own custom signer. The former is faster to integrate, but has one limitation in that it needs to run in a browser. The latter can be implemented in a Backend service or a simple Typescript file. This guide will describe how to implement each of these cases.

Prerequisites

Create Developer Account

To get started, register as a developer in the Crossmint developer console. Crossmint offers two consoles:

The production/mainnets console at www.crossmint.com/console The development/testnets console at staging.crossmint.com/console

For this guide, we'll use the latter for test integration. Navigate to https://staging.crossmint.com/console, sign in, and proceed as prompted.

Before integrating, contact your Crossmint Customer Success Engineer to enable this feature on your project.

API Keys Require CSE

During the closed Beta, you must ask your Crossmint Customer Success Engineer to provision keys for you.

Create API Keys

In the developer console, navigate to the API keys section and create a new API key. This key should have 2 scopes:

  • wallets.create
  • wallets.read

Installation

From the root directory of your project add the SDK to get started.

yarn add @crossmint/client-sdk

Crossmint managed keys

Integration steps

This guide will start from scratch, with an empty Next.js application. Then you’ll install the required @crossmint/client-sdk-aa dependency. Finally, create a function that implements the package and creates the wallet, showing some information about it.

Setup the Project

1

Create a new Next.js application

Use the create-next-app package to get started:

npx create-next-app@latest

If you see this message enter y and Enter to proceed:

Need to install the following packages:
  create-next-app@latest
Ok to proceed? (y)
2

Name your app `crossmint-smart-wallet-sdk` and accept the default options

What is your project named? crossmint-smart-wallet-sdk
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use `src/` directory? No
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias? No
3

Change into the directory created in previous steps

cd crossmint-smart-wallet-sdk
4

Install `@crossmint/client-sdk-aa`

pnpm i @crossmint/client-sdk-aa
5

Install `ethers`

To help us parsing the decimal units to crypto ones, we need to install the ethers library and use just a simple parse function. So, first install the library:

pnpm i ethers
6

Open the project in a code editor

code .
You may not have the code command installed on your system. If not, simply open the folder in your preffered code editor.

Continue in Code Editor

1

Obtain your `apiKey` from your API keys view in the console

2

Add a new file named `.env.local` to root directory of your project

Set your as environment variables by adding the values from Step 1.

These values are safe to use in the client side of your application and are not considered sensitive.
.env.local
NEXT_PUBLIC_API_KEY="_YOUR_API_KEY_"
3

Open the `/src/app/page.tsx` file in your editor

Replace the entire contents with the following:

/src/app/page.tsx
"use client";


import { CrossmintAASDK, FireblocksNCWSigner, Blockchain, EVMAAWallet } from "@crossmint/client-sdk-aa";
import { useState } from "react";
import { parseUnits, formatEther } from "ethers";

const userIdentifier = { email: 'youremail@gmail.com' };


export default function Home() {
const [wallet, setWallet] = useState<EVMAAWallet | undefined>(undefined);
const [balance, setBalance] = useState<string | undefined>(undefined);
const [address, setAddress] = useState<string | undefined>(undefined);
const [loading, setLoading] = useState<boolean>(false);


const createAAWallet = async () => {
  setLoading(true);
  const wallet = await createAAWalletHelper();
  console.log(wallet);
  setWallet(wallet);
  setLoading(false);
};

const getAddress = async () => {
  if (!wallet) {
    return 'The wallet is not initialized';
  }

  const address = await wallet.getAddress();
  setAddress(address);
}

const getBalance = async () => {
  if (!wallet) {
    return 'The wallet is not initialized';
  }

  const balance = await wallet.getBalance();
  setBalance(formatEther(balance.toString()));
}


if (loading) return (<div>Loading...</div>);


return (
  <div>
    {/* Wallet not set section */}
    {!wallet && (
      <div>
        <div>Wallet not created</div>
        <div>
          <button onClick={createAAWallet}>Create Wallet</button>
        </div>
      </div>
    )}
    {/* Wallet set section */}
    {wallet && (<>
      <div>
        <button onClick={getAddress}>Get Address</button>
        {address && <div>Address: {address}</div>}
      </div>
      <div>
        <button onClick={getBalance}>Get Balance</button>
        {balance && <div>Balance: {balance}</div>}
      </div>
    </>)}
  </div>
  )
}

const createAAWalletHelper = async () => {
  const xm = CrossmintAASDK.init({
    apiKey: process.env.NEXT_PUBLIC_API_KEY || '',
  });

  const fireblocksNCWSigner: FireblocksNCWSigner = {
    type: 'FIREBLOCKS_NCW',
    passphrase: '1234',
  };

  const walletInitParams = {
    signer: fireblocksNCWSigner,
  };

  return xm.getOrCreateWallet(userIdentifier, Blockchain.MUMBAI, walletInitParams);
};

We are:

  1. Creating a react App that will use a single component
  2. Creating 3 react functions:
    • createAAWallet: which will use a helper function to create the non-custodial wallet
    • getBalance: to get the balance once the wallet is created
    • getAdress: same as with get balance, but for the address
  3. Third, and this is the most non-custodial step, the createAAWalletHelper will use the @crossmint/client-sdk-aa to interact with it and create a non custodial wallet. The steps are: - Init the CrossmintSDK - Setup the signer (a Fireblocks one) and the email to be associated with the wallet - Execute the getOrCreate from the CrossmintSDK object, where we are saying which chain will be consumed. (Mumbai as it’s on the testnet environment)
4

Run the application from your terminal

pnpm run dev

Open the app in your browser with the url http://localhost:3000/. You should see Buttons for the described functionalities.

Important Notes

  • Under the hood, this code reserves a wallet address for your user, but it does not deploy the smart contract. The wallet can begin receiving NFTs, tokens, and many other operations, but the actual smart contract deployment is deferred to the time of the first user transaction to avoid unnecessary deployment fees.
  • The same wallet address will be assigned across all EVM chains the user interacts with. Make sure to pass the same email parameter to all of them.
  • If you have your own signer, such as a Web3Auth or an externally owned account (e.g. Metamask), you can set those as the owners of the smart contract wallet.

Retrieve the Wallet

Same Device / New Session

By default, the user wallet is stored on device in the browser they signed in on, even if they close your app and reopen it.

To retrieve the wallet from local storage, simply call the getOrCreateWallet() function, but without passing any passphrase. You can modify your code as follows to accomplish this:

We will incorporate the code needed to add a button and being able to retrieve a wallet.

First we need to add the helper method outside the react component.

TypeScript
const retrieveAAWalletHelper = async () => {
  const xm = CrossmintAASDK.init({
    apiKey: process.env.NEXT_PUBLIC_API_KEY || "",
  });

  //Create here the Signer - Fireblocks NCW Signer | Ethers Signer | Web3Auth Signer defined in the next steps

  const walletInitParams = {
    signer,
  };
  const userIdentifier = { email: "user@myapp.com" };
  /*const userIdentifier = { userId: <uuid> };
  const userIdentifier = { phoneNumber: <phoneNumber> };*/

  const chain: Blockchain = Blockchain.POLYGON; // Or any other supported chain

  return xm.getOrCreateWallet(
    userIdentifier,
    Blockchain.MUMBAI,
    walletInitParams
  );
};

The userIdentifier parameter is an object that allows Crossmint to uniquely identify a user within their platform. This object can include one of the following fields: email, userId, and phoneNumber. This flexibility enables the integration of user identification in a way that best fits your application or service. For example:

  • email: An email address associated with the user.
  • userId: A unique identifier assigned to the user.
  • phoneNumber: A phone number associated with the user.

The wallet can be created using a Fireblocks non-custodial wallet, an ethers signer or a Web3Auth Signer.

Ask the user for a passphrase, and then call getOrCreateWallet() to create a wallet for the user.

Fireblocks NCW Signer

TypeScript
import { FireblocksNCWSigner } from "@crossmint/client-sdk";
import { Blockchain } from "@crossmint/client-sdk";

const signer: FireblocksNCWSigner = {
  type: "FIREBLOCKS_NCW",
  passphrase: "1234",
};

Ethers Signer

TypeScript
import { ethers } from 'ethers';
import { Blockchain } from '@crossmint/client-sdk';

const signer: ethers.Wallet = <your-ethers-signer>

Web3Auth Signer

TypeScript
import { Web3AuthSigner } from "@crossmint/client-sdk";
import { Blockchain } from '@crossmint/client-sdk';

const signer: Web3AuthSigner = {
  type: "WEB3_AUTH";
  clientId: <web3auth-client-id>
  verifierId: <web3auth-verifierId>
  jwt: <jwt>
};
TypeScript
Then we need to create the react function that will use that helper to retrieve a wallet: We can put it below the createAAWallet react function.

const retrieveAAWallet = async () => {
  setLoading(true);
  const wallet = await retrieveAAWalletHelper();
  console.log(wallet);
  setWallet(wallet);
  setLoading(false);
};

Finally, we need to incorporate the button to execute the retrieve of a wallet. We can extend the Wallet not set section with a simple create wallet button, to have two buttons, by adding the retrieve a wallet one.

TypeScript
{
  !wallet && (
    <div>
      <div>Wallet not created</div>
      <div>
        <button onClick={createAAWallet}>Create Wallet</button>
      </div>
      <div>
        <button onClick={retrieveAAWallet}>Retrieve Wallet</button>
      </div>
    </div>
  );
}

Cleaning Up

When the developer feels it is convenient to clean the data, there is the purge method. This method clears the data, requiring the recovery method to be called the next time the user wants to use the wallet. This ensures no key shard is left on the device once it’s no longer in use. It is recommended that you purge storage upon a user logout:

So, following previous pattern, we will create a helper method that will be called by a react function when the user clicks a button:

So, first, we need to create the helper function

TypeScript
const purgeAAWalletHelper = async () => {
  const xm = CrossmintAASDK.init({
    apiKey: process.env.NEXT_PUBLIC_API_KEY || "",
  });

  await xm.purgeAllWalletData();
  console.info("Purged user wallet data");
};

Then, we need a react function that will call that one:

TypeScript
const purgeAAWalletData = async () => {
  setLoading(true);
  await purgeAAWalletHelper();
  setWallet(undefined);
  setBalance(undefined);
  setAddress(undefined);
  setLoading(false);
};

And finally an html button that will call to the react function. As consequence, the wallet set section should be modified as follows:

TypeScript
{
  wallet && (
    <>
      <div>
        <button onClick={getAddress}>Get Address</button>
        {address && <div>Address: {address}</div>}
      </div>
      <div>
        <button onClick={getBalance}>Get Balance</button>
        {balance && <div>Balance: {balance}</div>}
      </div>
      <div>
        <button onClick={purgeAAWalletData}>Purge wallet data</button>
      </div>
    </>
  );
}

Using the Wallet

Now that the user wallet is created, it’s time to do something with it!

Signing a Message

Using the wallet object created in the example(s) above, you can easily sign plaintext messages. Message signing allows a user to prove ownership of their private key (and by extension, their account) without revealing the key itself.

In the example below, you can see how to sign a simple message, ‘Hello, world!’, using the wallet object:

Scopes

This action needs a new scope in the api_key:

  • wallets:messages.sign

Lets add a new section for signing a message

This time, as we need just to execute a method in the wallet object returned by the SDK, we need to create the method and call it from a button. Also we will need to create a new variable (state) to allocate the result. So, first lets create the new state variable where we will save the signedMessage once set

TypeScript
const [signedMessage, setSignedMessage] = useState<string | undefined>(
  undefined
);

Then, put the react function under purgeAAWalletData (or wherever feels convenient)

TypeScript
const signMessage = async () => {
  const message = "Hello, world!";
  // Sign the message
  const signature = await wallet!.signMessage(message);

  setSignedMessage(signature);
};

Finally, add the html button. We will put it with the ones when the wallet is already created, and the whole block should look like this:

TypeScript
return (
  <div>
    {/* Wallet not set section */}
    {!wallet && (
      <div>
        <div>Wallet not created</div>
        <div>
          <button onClick={createAAWallet}>Create Wallet</button>
        </div>
        <div>
          <button onClick={retrieveAAWallet}>Retrieve Wallet</button>
        </div>
      </div>
    )}
    {/* Wallet set section */}
    {wallet && (
      <>
        <div>
          <button onClick={getAddress}>Get Address</button>
          {address && <div>Address: {address}</div>}
        </div>
        <div>
          <button onClick={getBalance}>Get Balance</button>
          {balance && <div>Balance: {balance}</div>}
        </div>
        <div>
          <button onClick={purgeAAWalletData}>Purge wallet data</button>
        </div>
        <div>
          <button onClick={signMessage}>Sign Message</button>
          {signedMessage && <div>Signed Message: {signedMessage}</div>}
        </div>
      </>
    )}
  </div>
);

Verify message

You can verify the signature of a message using the verifyMessage method of your wallet. It will return a boolean value indicating if the signature corresponds to the message sent as a parameter.

You can create a new section, under the signMessage, and reuse the signedMessage state value that will contain the signature.

TypeScript
const verifyMessage = async () => {
  const message = "Hello, world!";
  // Verify the message
  const isVerified = await wallet!.verifyMessage(message, signedMessage);
  console.log(isVerified ? 'Message verified' , 'Message not verified')
};

Signing Typed Data

If you need to sign typed data you will pass a JSON object representing the data to sign. It should follow the EIP-712 format.

Lets add a new section for Signing Typed Data Same as with signMessage, we need the wallet variable which lives inside the react component. we will create only a react function with the functionality inside of it, and call it from an html button to then show the result. Also, we will create a state variable to save the value. So first, create the state variable, put it below the previous ones created

TypeScript
const [signedTypeData, setSignedTypeData] = useState<string | undefined>(
  undefined
);

Second, create the react function

TypeScript
const signTypedData = async () => {
  // Define your object with properties itemName and rarity
  if (!wallet) {
    return "The wallet is not initialized";
  }

  const yourObject = {
    itemName: "Your Item Name",
    rarity: "Rare",
  };

  // Define the schema according to EIP-712
  const domain = {
    name: "YourDomain",
    version: "1",
    chainId: 1, // Chain ID of Ethereum mainnet, change accordingly
    verifyingContract: "0xYourVerifyingContractAddress" as `0x${string}`, // Replace with the contract address
  };

  const types = {
    YourObject: [
      { name: "itemName", type: "string" },
      { name: "rarity", type: "string" },
    ],
  };

  // Create a typed data object
  const typedData = {
    types: types,
    primaryType: "YourObject",
    domain: domain,
    message: yourObject,
  };
  wallet.sendUserOperation;
  const signature = await wallet.signTypedData(typedData);
  console.log("Typed Data:", typedData);
  console.log("Signed Data:", signature);
  setSignedTypeData(signature);
};

Finally, add the html button to execute that function and the div which will show the result once set. Then whole buttons block ( with the wallet set ) should look like this:

TypeScript
{
  wallet && (
    <>
      <div>
        <button onClick={getAddress}>Get Address</button>
        {address && <div>Address: {address}</div>}
      </div>
      <div>
        <button onClick={getBalance}>Get Balance</button>
        {balance && <div>Balance: {balance}</div>}
      </div>
      <div>
        <button onClick={purgeAAWalletData}>Purge wallet data</button>
      </div>
      <div>
        <button onClick={signMessage}>Sign Message</button>
        {signedMessage && <div>Signed Message: {signedMessage}</div>}
      </div>
      <div>
        <button onClick={signTypedData}>Sign Typed Data</button>
        {signedTypeData && <div>Sign Typed Data: {signedTypeData}</div>}
      </div>
    </>
  );
}

Send Transaction

We will try to execute a transaction from our created wallet and check that it’s executed as it should. First, let’s create a new state variable where the TxId will be allocated. We will use it later to check that the Tx executed successfully.

TypeScript
const [TxId, setTxId] = useState<string | undefined>(undefined);

Then, let’s create the react function to send the Tx. As it will be executed from our wallet, we need its value to send a Tx and that’s why we will create a unique react function to be called.

And, then we need to import an ethers helper function:

TypeScript
import { parseUnits } from "ethers";

To be used in the sendTransaction function 🎉:

TypeScript
const sendTransaction = async () => {
  if (!wallet) {
    return "The wallet is not initialized";
  }

  // Define the transaction parameters
  const transaction = {
    to: "0x6f30064f170921209b3f9e5f968bb3f59d598fe4", // Recipient address
    gasLimit: 21000, // Gas limit for the transaction (optional)
    gasPrice: parseUnits("20", "gwei"), // Gas price for the transaction (optional)
    data: "0x6057361d0000000000000000000000000000000000000000000000000000000000000022", // Arbitrary data, such as input parameters for smart contract functions or additional transaction details. (Optional)
  };

  // Send the transaction
  const txnResult = await wallet.sendTransaction(transaction);
  setTxId(txnResult.hash);
};

How to read that code easily? We are sending a transaction to this test smart contract: https://mumbai.polygonscan.com/address/0x6f30064f170921209b3f9e5f968bb3f59d598fe4#readContract

With the following encoded data: : 0x6057361d0000000000000000000000000000000000000000000000000000000000000022

Try changing the last two chars to other values, and see that the value is updated in the retrieve smart contract function. In this link: https://mumbai.polygonscan.com/address/0x6f30064f170921209b3f9e5f968bb3f59d598fe4#readContract#F1 you can check it’s being replaced successfully (remember that the data is encoded in hex and the output is in decimal format)

So, now lets add the html button. The whole block when the wallet is set, should look like this:

TypeScript
{
  wallet && (
    <>
      <div>
        <button onClick={getAddress}>Get Address</button>
        {address && <div>Address: {address}</div>}
      </div>
      <div>
        <button onClick={getBalance}>Get Balance</button>
        {balance && <div>Balance: {balance}</div>}
      </div>
      <div>
        <button onClick={purgeAAWalletData}>Purge wallet data</button>
      </div>
      <div>
        <button onClick={signMessage}>Sign Message</button>
        {signedMessage && <div>Signed Message: {signedMessage}</div>}
      </div>
      <div>
        <button onClick={signTypedData}>Sign Typed Data</button>
        {signedTypeData && <div>Sign Typed Data: {signedTypeData}</div>}
      </div>
      <div>
        <button onClick={sendTransaction}>Send Transaction</button>
        {txId && <div>TxId: {txId}</div>}
      </div>
    </>
  );
}

Now, it’s all about clicking on the button, which will end up showing a TxId which can be put in the https://mumbai.polygonscan.com/ searcher, and can retrieve some more information about it!