Web (JavaScript/TypeScript) - Auth

Interacting with Particle Auth within web applications using either JavaScript or TypeScript

🔗

This SDK powers the majority of our web demo, which you can find at https://core-demo.particle.network

Particle Auth for Web Applications

Among all of the SDKs for Particle Auth, the Web SDK, or @particle-network/auth-core-modal (alongside the now deprecated @particle-network/auth) is by far the most widely used and thus offers extensive support and flexibility in facilitating interaction with Particle's Wallet-as-a-Service.

The Particle Auth Core SDK is one of the primary mechanisms of configuring and using Particle. Whether you're starting from scratch or already have an onboarding or authentication flow set up within your application, the Particle Auth Core Web SDK is a simple yet powerful integration.


Getting Started

To get started with the Particle Auth Web SDK, you can add @particle-network/auth-core-modal or @particle-network/auth to your project using either of the two following mechanisms:

npm install @particle-network/auth-core-modal

yarn add @particle-network/auth-core-modal
yarn add @particle-network/auth

npm install @particle-network/auth

# Additional packages which can be installed alongside @particle-network/auth:
# @particle-network/provider - for conversion of Particle Auth into an EIP-1193 provider object.
# @particle-network/solana-wallet - to natively construct and execute Solana transactions.

How do I decide between @particle-network/auth and @particle-network/auth-core-modal?

If you're new to using Particle Auth, you'll likely want to use @particle-network/auth-core-modal - this is our React-based SDK which takes a simple and refined approach to configuration and implementation.

Otherwise, if you're using an older project or can't use the @particle-network/auth-core-modal due to its dependence on React, you may find @particle-network/auth more convenient, albeit deprecated.


Configuration

Before interacting with Particle, you'll need to import and configure your master configuration object; this'll be used to not only customize the embedded wallet modal which is optionally shown after logging in, but also will allow you to define required keys and authenticate your project.

Although before we import and configure this object, you'll need to retrieve the aforementioned keys. To do so, take a look at the following process:

  1. Sign up/log in to the Particle dashboard.

  1. Create a new project or enter an existing project.

  1. Create a new web application, or skip this step if you already have one.

  1. Retrieve the project ID (projectId), the client key (clientKey), and the application ID (appId).


Now that you’ve retrieved your project ID, client key, and application ID from the Particle dashboard, you can move on to initializing and configuring your master configuration object. Regardless of the Particle Auth SDK you use, you'll have the opportunity to define your projectId, clientKey, and appId to authenticate your instance of Particle Auth. In addition to this, customizations can be applied to the resulting wallet modal.

📘

Structural differences between @particle-network/auth-core-modal and @particle-network/auth

@particle-network/auth-core-modal is React-based, therefore its master configuration object, AuthCoreContextProvider, is a React component which should wrap either your core application logic or the component in which your application exists.

Alternatively, @particle-network/auth handles configuration through a standard ParticleNetwork class, simply requiring initialization; the instance of ParticleNetwork you define will be used directly for social login initiation.

import React from 'react'
import ReactDOM from 'react-dom/client'
import { AuthType } from '@particle-network/auth-core';
import { EthereumGoerli } from '@particle-network/chains';
import { AuthCoreContextProvider, PromptSettingType } from '@particle-network/auth-core-modal';
import App from './App'

import('buffer').then(({ Buffer }) => {
  window.Buffer = Buffer;
});

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <AuthCoreContextProvider
      options={{
        projectId: process.env.REACT_APP_PROJECT_ID,
        clientKey: process.env.REACT_APP_CLIENT_KEY,
        appId: process.env.REACT_APP_APP_ID,
        authTypes: [AuthType.email, AuthType.google, AuthType.twitter],
        themeType: 'dark',
        fiatCoin: 'USD',
        language: 'en',
        erc4337: {
          name: 'SIMPLE',
          version: '1.0.0',
        },
        promptSettingConfig: {
          promptPaymentPasswordSettingWhenSign: PromptSettingType.first,
          promptMasterPasswordSettingWhenLogin: PromptSettingType.first,
        },
        wallet: {
          visible: true,
          customStyle: {
            supportChains: [EthereumGoerli],
          }
        },
      }}
    >
    <App />
      </AuthCoreContextProvider>
  </React.StrictMode>
)
import { ParticleNetwork, WalletEntryPosition } from "@particle-network/auth";
import { Ethereum } from "@particle-network/chains"; // Optional

const particle = new ParticleNetwork({
  projectId: "xx",
  clientKey: "xx",
  appId: "xx",
  chainName: Ethereum.name, // Optional: resolves to 'ethereum' both in this case & by default
  chainId: Ethereum.id, // Optional: resolves to 1 both in this case & by default
  wallet: {   // Optional: object controlling additional configurations
    displayWalletEntry: true,  // Whether or not the wallet popup is shown on-screen after login
    defaultWalletEntryPosition: WalletEntryPosition.BR, // If the former is true, the position in which the popup appears
    uiMode: "dark",  // Light or dark, if left blank, aligns with web auth default
    supportChains: [{ id: 1, name: "Ethereum"}, { id: 5, name: "Ethereum"}], // Restricts the chains available within the web wallet interface
    customStyle: {}, // If applicable, custom wallet style in JSON
  },
  securityAccount: { // Optional: Configuration of security requirements upon login
    // If, and in what frequency, will the user be prompted to set a payment password
    // 0: None, 1: Once (default), 2: Always
    promptSettingWhenSign: 1,
    // If, and in what frequency, will the user be prompted to set a master password
    // 0: None (default), 1: Once, 2: Always
    promptMasterPasswordSettingWhenLogin: 1
  },
});

Interaction with Web3

Now that you've configured an instance of Particle Auth, it's time to leverage Particle to facilitate social logins and interactions with Web3.

To begin, you'll need to choose between the three following interaction mechanisms:

  1. Ethers.js.
  2. Web3.js.
  3. viem.
  4. Particle native.

All three will achieve the same end goal of facilitating interaction with Web3, although they will result in a slightly different initialization process. If you've chosen Particle native, no initialization of this nature past configuring AuthCoreContextProvider or ParticleNetwork is needed.

import { ethers } from "ethers";

// Particle Auth Core
import { useEthereum } from '@particle-network/auth-core-modal';

const { provider } = useEthereum();

const ethersProvider = new ethers.providers.Web3Provider(provider, "any");

// Particle Auth (old)
const particleProvider = new ParticleProvider(particle.auth);

const ethersProvider = new ethers.providers.Web3Provider(particleProvider, "any");
import { Web3 } from "web3";

// Particle Auth Core
const { provider } = useEthereum();

window.web3 = new Web3(provider);

// Particle Auth (old)
const particleProvider = new ParticleProvider(particle.auth);

window.web3 = new Web3(particleProvider);
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains

// Particle Auth Core
import { useEthereum } from '@particle-network/auth-core-modal';

const { provider } = useEthereum();

const viemProvider = createWalletClient({
  chain: mainnet,
  transport: custom(provider)
})

// Particle Auth (old)
const particleProvider = new ParticleProvider(particle.auth);

const viemProvider = createWalletClient({
  chain: mainnet,
  transport: custom(particleProvider)
})

Initiating Social Login

To enable interaction with a given user's account (wallet), they'll first need to log in with Particle Auth (or Particle Connect), akin to the requirement of entering a password and opening MetaMask. Before interaction is possible, a user must either already be logged in (an application needs to be able to recognize this), go through the login process.

Both of the above can be achieved through the following snippet:

// Particle Auth Core
import { useConnect } from '@particle-network/auth-core-modal';

const { connect, connected } = useConnect();
if (connected) {
	const userInfo = await connect();
}

// Particle Auth (old)
if (!particle.auth.isLogin()) { // Boolean based upon login state of session
    // Request user login if needed, returns current user info, such as name, email, etc.
    const userInfo = await particle.auth.login();
}

💻

Give it a try yourself (embedded demo)

Additionally, specific configurations can be set within connect() or particle.auth.login() to customize the login experience beyond UI-based changes through the Particle dashboard.

// Particle Auth Core
const userInfo = connect({
		// socialType, if set, will skip the auth modal shown above and will instead automatically redirect to a chosen social login
    // 'email' | 'phone' | 'google' | 'apple' | 'twitter' | 'facebook' | 'microsoft' | 'linkedin' | 'github' | 'twitch' | 'discord' | 'jwt'
  	socialType: 'google',
  	jwt: 'xxxxxxxxxx', // If applicable, JWT value
   	phone: '+1xxxxxxxx', // Optional, E.164 format
    code: 'xxxxxx', // Optional
  	authorization: {
        uniq: true,  // Optional: Defaults to false
        message: 'base58 string', // Signature message in hex or base58 for Solana
    }
})

// Particle Auth (old)
const userInfo = particle.auth.login({
    // preferredAuthType, if set, will skip the auth modal shown above and will instead automatically redirect to a chosen social login
    // 'email' | 'phone' | 'google' | 'apple' | 'twitter' | 'facebook' | 'microsoft' | 'linkedin' | 'github' | 'twitch' | 'discord' | 'jwt'
    preferredAuthType?: AuthType,
    // If JWT usage is setup and set ('jwt') as preferredAuthType, Particle Auth will auto login
    account?: string, // If applicable, JWT value
    supportAuthTypes?: string, // If you'd like the Particle Auth UI to only offer select social login types, defaults to 'all'. Split with ','
    hideLoading?: boolean, // Hides Particle loading animation when use JWT authorization
    socialLoginPrompt?: string, // Specified social login prompt. 'none' | 'consent' | 'select_account'
    authorization: { // Optional: Login with authorization signature
        message: '0x...', // Signature message in hex or base58 for Solana
        uniq: false, // Optional: Defaults to false
    }
  })

🗒️

web3.eth.getAccounts will also initiate login automatically if user isn't already logged in.


Retrieving Public Address

Retrieving the public address associated with a given user's account (assuming you're not pulling it from the login via userInfo) can be achieved through the following:

import Web3 from 'web3';

// Particle Auth Core
import { useEthereum } from '@particle-network/auth-core-modal';
const { provider } = useEthereum();

// Particle Auth (old)
import { ParticleProvider } from "@particle-network/provider";
const provider = new ParticleProvider(particle.auth);


const web3 = new Web3(provider);
// Assuming Web3 is not already initialized

const accounts = await web3.eth.getAccounts();
import { ethers } from "ethers";

// Particle Auth Core
import { useEthereum } from '@particle-network/auth-core-modal';
const { provider } = useEthereum();

// Particle Auth (old)
import { ParticleProvider } from "@particle-network/provider";
const provider = new ParticleProvider(particle.auth);


const ethersProvider = new ethers.providers.Web3Provider(provider, "any");
// Assuming Ethers is not already initialized

const accounts = await ethersProvider.listAccounts();
import { useEthereum } from '@particle-network/auth-core-modal'; // Particle Auth Cor
import { ParticleProvider } from "@particle-network/provider"; // Particle Auth (old)

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { address, provider } = useEthereum();

// For Particle Auth, after configuring ParticleNetwork and logging in
const account = await particle.evm.getAddress()

// Or, alternatively, send a JSON-RPC request to the provider
const provider = new ParticleProvider(particle.auth); // Only if using Particle Auth

const accounts = await provider.request({ method: 'eth_accounts'});
import { useSolana } from '@particle-network/auth-core-modal'; // Particle Auth Core
import { SolanaWallet } from "@particle-network/solana-wallet"; // Particle Auth (old)

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { address } = useSolana();

// For Particle Auth, after configuring ParticleNetwork and logging in
const account = await particle.solana.getAddress()

// Or, alternatively, leverage SolanaWallet for interaction (old, meant for the previous Particle Auth SDK)
const solanaWallet = new SolanaWallet(pn.auth);
const publicKey = solanaWallet.publicKey;
const account = publicKey?.toBase58();

Sending Transactions

Sending transactions using Particle as the Signer/underlying wallet is also quite simple. If you're using Ethers.js or Web3.js, you're already set —as long as a user has logged in with Particle, transactions can be sent as you would with any other wallet.

When a given transaction is sent and a signature is needed for authentication, a standard confirmation popup (also customizable through the Particle dashboard) will appear directly within the application. Transactions can be sent through the following:

window.web3.eth.sendTransaction(txnParams, (error, txnHash) => {
    if (error) throw error;
    console.log(txnHash);
});
const signer = ethersProvider.getSigner();

signer.sendTransaction(txnParams)
  .then(tx => {
    console.log(tx);
  })
  .catch(error => {
    throw error;
  });
// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
import { useEthereum } from '@particle-network/auth-core-modal';
const { sendTransaction } = useEthereum();

const txnHash = await sendTransaction(txnParams);

// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const txnHash = await particle.evm.sendTransaction(txnParams);
import { useSolana } from '@particle-network/auth-core-modal'; // Particle Auth Core
import { SolanaWallet } from "@particle-network/solana-wallet"; // Particle Auth alternative

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signAndSendTransaction } = useSolana();

const result = await signAndSendTransaction(transaction);


// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const result = await particle.solana.signAndSendTransaction(transaction)

// Or, alternatively, leverage SolanaWallet for interaction (old, meant to be used with the previous Particle Auth SDK)
const solanaWallet = new SolanaWallet(pn.auth);
solanaWallet.signAndSendTransaction(transaction)

📘

To the end-user, sending transactions, regardless of the chosen method, looks like this (depending on customization outlined in the Particle dashboard).


Data Signing

In cases where you'd like to sign either a raw string (personal) or typed data (eth_signTypedData), the process is quite straightforward. It can be done either as standard through libraries such as Ethers or Web3.js, or by using Particle natively. Both of these scenarios are shown within the various examples below.

Similar to sending transactions, signatures will be formatted and displayed to your user in a popup request for confirmation and for applicable data in UTF-8.

Personal Signatures

const msg = "GM, Particle!";
const accounts = await window.web3.eth.getAccounts();

window.web3.eth.personal
  .sign(msg, accounts[0])
  .then((result) => {
    console.log("personal_sign", result);
  })
  .catch((error) => {
    console.error("personal_sign", error);
  });
const msg = "GM, Particle!";
const signer = ethersProvider.getSigner();
const accounts = await signer.getAddress();

signer.signMessage(msg)
  .then((result) => {
    console.log("personal_sign", result);
  })
  .catch((error) => {
    console.error("personal_sign", error);
  });

const msg = "GM, Particle!";

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signMessage } = useEthereum();

const result = await signMessage(msg);


// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const result = await particle.evm.personalSign(`0x${Buffer.from(msg).toString('hex')}`);
import { useSolana } from "@particle-network/auth-core-modal"; // Particle Auth Core
import { SolanaWallet } from "@particle-network/solana-wallet"; // Particle Auth alternative (old)

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signMessage } = useSolana();

// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const result = await particle.solana.signMessage('base58 string');

const result = await signMessage('base58 string');

// Or, alternatively, leverage SolanaWallet for interaction (old, meant to be used with the previous Particle Auth SDK)
const solanaWallet = new SolanaWallet(pn.auth);
solanaWallet.signMessage('base58 string');

Sign Typed Data V1

const accounts = await window.web3.eth.getAccounts();
const from = accounts[0];

// Placeholder data
const msg = [
    {
      type: "string",
      name: "fullName",
      value: "John Doe",
    },
    {
      type: "uint32",
      name: "userId",
      value: "1234",
    },
  ];

const params = [msg, from];
const method = "eth_signTypedData_v1";

// .request can also be called directly through a ParticleProvider instance
window.web3.currentProvider
  .request({
    method,
    params,
  })
  .then((result) => {
    console.log("signTypedData result", result);
  })
  .catch((err) => {
    console.log("signTypedData error", err);
  });
const signer = provider.getSigner();

// Placeholder data
const msg = [
  {
    name: 'fullName',
    type: 'string',
    value: 'John Doe',
  },
  {
    name: 'userId',
    type: 'uint32',
    value: '1234',
  },
];

(async () => {
  try {
    const accounts = await provider.listAccounts();
    const from = accounts[0];

    const types = msg.map(entry => entry.type);
    const values = msg.map(entry => entry.value);

    const hash = utils.keccak256(utils.defaultAbiCoder.encode(types, values));

    const signingKey = new utils.SigningKey(await signer.privateKey);
    const signature = signingKey.signDigest(hash);

    console.log('Signed hash:', signature);
  } catch (err) {
    console.log('Error:', err);
  }
})();
// Placeholder data
const msg = [
    {
      type: "string",
      name: "fullName",
      value: "John Doe",
    },
    {
      type: "uint32",
      name: "userId",
      value: "1234",
    },
  ];

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signTypedData } = useEthereum();

const result = await signTypedData({ data: JSON.parse(msg), version: 'V1' });


// For Particle Auth, after configuring ParticleNetwork and logging in
const result = await particle.evm.signTypedData({ data: JSON.parse(msg), version: 'V1' });

Sign Typed Data V3

const accounts = await window.web3.eth.getAccounts();
const from = accounts[0];

// Placeholder data
const payload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "verifyingContract", type: "address" },
      ],
      Person: [
        { name: "name", type: "string" },
        { name: "wallet", type: "address" },
      ],
      Mail: [
        { name: "from", type: "Person" },
        { name: "to", type: "Person" },
        { name: "contents", type: "string" },
      ],
    },
    primaryType: "Mail",
    domain: {
      name: "Ether Mail",
      version: "1",
      verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
    },
    message: {
      from: {
        name: "Cow",
        wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
      },
      to: {
        name: "Bob",
        wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
      },
      contents: "Hello, Bob!",
    },
  };

const params = [from, payload];
const method = "eth_signTypedData_v3";

window.web3.currentProvider
  .request({
    method,
    params,
  })
  .then((result) => {
    console.log("signTypedData_v3 result", result);
  })
  .catch((err) => {
    console.log("signTypedData_v3 error", err);
  });
const signer = provider.getSigner();

const domain = {
  name: "Ether Mail",
  version: "1",
  verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
};

const types = {
  EIP712Domain: [
    { name: "name", type: "string" },
    { name: "version", type: "string" },
    { name: "verifyingContract", type: "address" },
  ],
  Person: [
    { name: "name", type: "string" },
    { name: "wallet", type: "address" },
  ],
  Mail: [
    { name: "from", type: "Person" },
    { name: "to", type: "Person" },
    { name: "contents", type: "string" },
  ],
};

const message = {
  from: {
    name: "Cow",
    wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
  },
  to: {
    name: "Bob",
    wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
  },
  contents: "Hello, Bob!",
};

(async () => {
  try {
    const accounts = await provider.listAccounts();
    const from = accounts[0];

    const signature = await signer._signTypedData(domain, types, message);
    console.log("signTypedData_v3 result:", signature);
  } catch (err) {
    console.log("signTypedData_v3 error:", err);
  }
})();
// Placeholder data
const payload = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "verifyingContract", type: "address" },
      ],
      Person: [
        { name: "name", type: "string" },
        { name: "wallet", type: "address" },
      ],
      Mail: [
        { name: "from", type: "Person" },
        { name: "to", type: "Person" },
        { name: "contents", type: "string" },
      ],
    },
    primaryType: "Mail",
    domain: {
      name: "Ether Mail",
      version: "1",
      verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
    },
    message: {
      from: {
        name: "Cow",
        wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
      },
      to: {
        name: "Bob",
        wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
      },
      contents: "Hello, Bob!",
    },
  };

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signTypedData } = useEthereum();

const result = await signTypedData({ data: JSON.parse(payload), version: 'V3' });


// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const result = await particle.evm.signTypedData({ data: JSON.parse(payload), version: 'V3' });

Sign Typed Data V4

const accounts = await window.web3.eth.getAccounts();
const from = accounts[0];

// Placeholder data
const payload = {
    domain: {
      name: "Ether Mail",
      verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
      version: "1",
    },
    message: {
      contents: "Hello, Bob!",
      from: {
        name: "Cow",
        wallets: [
          "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
          "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF",
        ],
      },
      to: [
        {
          name: "Bob",
          wallets: [
            "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
            "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
            "0xB0B0b0b0b0b0B000000000000000000000000000",
          ],
        },
      ],
    },
    primaryType: "Mail",
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "verifyingContract", type: "address" },
      ],
      Group: [
        { name: "name", type: "string" },
        { name: "members", type: "Person[]" },
      ],
      Mail: [
        { name: "from", type: "Person" },
        { name: "to", type: "Person[]" },
        { name: "contents", type: "string" },
      ],
      Person: [
        { name: "name", type: "string" },
        { name: "wallets", type: "address[]" },
      ],
    },
  };
const params = [from, payload];
const method = "eth_signTypedData_v4";

window.web3.currentProvider
  .request({
    method,
    params,
  })
  .then((result) => {
    console.log("signTypedData_v4 result", result);
  })
  .catch((err) => {
    console.log("signTypedData_v4 error", err);
  });
const signer = provider.getSigner();

// Placeholder data
const domain = {
  name: "Ether Mail",
  version: "1",
  verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
};

const types = {
  EIP712Domain: [
    { name: "name", type: "string" },
    { name: "version", type: "string" },
    { name: "verifyingContract", type: "address" }
  ],
  Group: [
    { name: "name", type: "string" },
    { name: "members", type: "Person[]" }
  ],
  Mail: [
    { name: "from", type: "Person" },
    { name: "to", type: "Person[]" },
    { name: "contents", type: "string" }
  ],
  Person: [
    { name: "name", type: "string" },
    { name: "wallets", type: "address[]" }
  ]
};

const message = {
  contents: "Hello, Bob!",
  from: {
    name: "Cow",
    wallets: [
      "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
      "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"
    ]
  },
  to: [
    {
      name: "Bob",
      wallets: [
        "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
        "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
        "0xB0B0b0b0b0b0B000000000000000000000000000"
      ]
    }
  ]
};

(async () => {
  try {
    const accounts = await provider.listAccounts();
    const from = accounts[0];
  
    const signature = await signer._signTypedData(domain, types, message);
    console.log("signTypedData_v4 result:", signature);
  } catch (err) {
    console.log("signTypedData_v4 error:", err);
  }
})();
// Placeholder data
const payload = {
    domain: {
      name: "Ether Mail",
      verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
      version: "1",
    },
    message: {
      contents: "Hello, Bob!",
      from: {
        name: "Cow",
        wallets: [
          "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
          "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF",
        ],
      },
      to: [
        {
          name: "Bob",
          wallets: [
            "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
            "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
            "0xB0B0b0b0b0b0B000000000000000000000000000",
          ],
        },
      ],
    },
    primaryType: "Mail",
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "verifyingContract", type: "address" },
      ],
      Group: [
        { name: "name", type: "string" },
        { name: "members", type: "Person[]" },
      ],
      Mail: [
        { name: "from", type: "Person" },
        { name: "to", type: "Person[]" },
        { name: "contents", type: "string" },
      ],
      Person: [
        { name: "name", type: "string" },
        { name: "wallets", type: "address[]" },
      ],
    },
  };

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signTypedData } = useEthereum();

const result = await signTypedData({ data: JSON.parse(payload), version: 'V4' });

// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const result = await particle.evm.signTypedData({ data: JSON.parse(payload), version: 'V4' });

Unique Signature (Personal & Typed)

const message = '0x.......'

// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signMessage } = useEthereum();

const result = await signMessage(message, uniq: true);

// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const result = await particle.evm.personalSignUniq(message);


// Or, alternatively, send the JSON-RPC request to the provider directly
const { provider } = useEthereum(); // Particle Auth Core
const provider = new ParticleProvider(particle.auth); // Particle Auth (old)

const result = await provider.request({method: 'personal_sign_uniq', params: [message, address]});
// For Particle Auth Core, after configuring AuthCoreContextProvider and logging in
const { signTypedData } = useEthereum();

const result = await signTypedData({ data: JSON.parse(payload), version: 'V4', uniq: true });

// For Particle Auth, after configuring ParticleNetwork and logging in (old)
const result = await particle.evm.signTypedDataUniq(message);


// Or, alternatively, send the JSON-RPC request to the provider directly
const { provider } = useEthereum(); // Particle Auth Core
const provider = new ParticleProvider(particle.auth); // Particle Auth (old)

const result = await provider.request({method: 'eth_signTypedData_v4_uniq', params: [address, message]});

Note about window.ethereum

🚧

If you use window.ethereum to interact with Web3 (conduct RPC calls), a few things need to be noted:

Object contamination when using multiple wallets is often an issue. To avoid this, you can either:

  1. Only use Particle
    1. When defining window.ethereum, set the global variable to an instance of provider (from useEthereum with Particle Auth Core) or if you're using the older SDKs, ParticleProvider (from @particle-network/provider), therefore forcing exclusive usage of Particle.
  2. Use other wallets simultaneously
    1. If using other plugin wallets, a new provider object will need to be created when switching wallets, ensuring the avoidance of cross-contamination. For example, when using MetaMask, provider could be Object.create(window.ethereum), while ideally Particle is isolated, with it's usage being dictated in provider with Object.create(particleProvider).

Here's an example of the above:

let provider = Object.create(particleProvider);
const ParticleWallet = document.getElementById("ParticleWallet");
const MetaMaskWallet = document.getElementById("MetaMaskWallet");
ParticleWallet.onclick = async () => {
  provider = Object.create(particleProvider);
  ethersProvider = new ethers.providers.Web3Provider(provider, "any");
};
MetaMaskWallet.onclick = async () => {
  if (window.ethereum) {
    provider = Object.create(window.ethereum);
  }
  ethersProvider = new ethers.providers.Web3Provider(provider, "any");
};
const accounts = await provider.request({
  method: "eth_requestAccounts",
});

Particle Native (@particle-network/auth-core-modal)

As you've likely seen throughout this docume, Particle Auth Core acts as a modern implementation of Particle Auth, featuring increased flexibility and a more natural, embedded utilization flow for end-users compared to the older SDK. The way in which the Auth Core SDK is used deviates significantly from the previous Particle Auth SDK. Both achieve the same end-goal, but through different processes and intricacies.

Below is a collection of examples relating to the different hooks available with Particle Auth Core, facilitating both social login and native application-level interaction (such as message signing, sending transactions, etc.) directly through Particle Auth Core.

Boilerplate

If you'd like to begin exploring Particle Auth Core, we have a React boilerplate available for usage through any of the following commands:

npm init @particle-network/auth-core-modal@latest

pnpm create @particle-network/auth-core-modal@latest

yarn create @particle-network/auth-core-modal

useConnect

useConnect acts as the primary hook for facilitating login (connection) with Particle Auth Core, equivalent to particle.auth.login, particle.auth.isLogin, and particle.auth.logout within the older Particle Auth SDK.

import { useConnect } from '@particle-network/auth-core-modal';

const { connect, disconnect, connected, connectionStatus, requestConnectCaptcha, setSocialConnectCallback } = useConnect();

const userInfo = await connect();

const isLoggedIn = connected;

await disconnect();

useEthereum

useEthereum provides direct interaction with a given EVM chain, acting as an alternative to using a traditional library such as ethers or Web3.js.

const { provider,   // EIP1193 provider
        address,    // EVM public address
        chainId,    // Current chain
        chainInfo,
        switchChain,
        signMessage,
        signTypedData,
        sendTransaction,
        enable } = useEthereum();

const txHash = await sendTransaction({
    to: '0xe8fc0baE43aA264064199dd494d0f6630E692e73',
    value: '1000000',
});

const signature = await signMessage(message);

const signature = await signTypedData(typedData);

await switchChain('0x1'); // Also takes a standard number

useSolana

Alike the prior, useSolana is meant to replace particle.solana (from the previous Particle Auth SDK) and facilitate end-to-end interaction with Solana natively through the SDK. This is one of the primary ways to use Solana with Particle Auth Core if you aren't using an external connection modal such as wallet-adapter.

import { useSolana } from '@particle-network/auth-core-modal';

const { address,  // Solana public address
        chainId,  // Current chain (Mainnet, Testnet, Devnet)
        chainInfo,
        switchChain,
        signMessage,
        signTransaction,
        signAllTransactions,
        signAndSendTransaction,
        enable } = useSolana();

const txHash = await signAndSendTransaction(txData);

const signature = await signMessage(message);

useAuthCore

useAuthCore is used for alternative functions such as the retrieval of userInfo (post-login), forcing the different account menus open, opening the on-ramp page, and so on.

import { useAuthCore } from '@particle-network/auth-core-modal';

const {
    userInfo,                       // Standard user info, null is returned if not connected
    needRestoreWallet,              // Whether or not a master password is needed from the current user
    openAccountAndSecurity,         // Opens account and security modal
    openSetMasterPassword,          // Opens set master password modal
    openChangeMasterPassword,       // Opens change master password modal
    openRestoreByMasterPassword,    // Opens input master password modal
    openSetPaymentPassword,         // Opens set payment password modal
    openChangePaymentPassword,      // Opens change payment password modal
    openSetSecurityAccount,         // Opens set security account modal
    openLinkLoginAccount,           // Opens link login account modal
    openWallet,                     // Opens wallet in iframe
    buildWalletUrl,                 // Retrieves wallet url, used for opening the wallet modal in a custom iframe
    openBuy,                        // Opens the onramp aggregation page
} = useAuthCore();

useCustomize

useCustomize provides a number of methods used for customizing the wallet modal, such as the theme type (light or dark), a custom style, language, etc.


import { useCustomize } from '@particle-network/auth-core-modal';

const { 
    setThemeType,       // Sets theme type, 'light' or 'dark'
    setCustomStyle,     // Sets custom modal styles
    setWalletOptions,   // Sets wallet modal options
    setLanguage,        // Sets language being used
    setFiatCoin,        // Sets fiat coin being used
    setERC4337          // Sets whether the modal has ERC-4337 enabled
} = useCustomize();

Particle Native (@particle-network/auth)

Alternatively, as you may have noticed throughout this page, ParticleNetwork from the previous Particle Auth SDK has a range of functions, including (but not limited to) directly facilitating Web3 interaction.

Below is a list of examples pertaining to this range of functions, diving into some of the most common ways @particle-network/auth is used.

📘

ParticleNetwork is meant to act as a 'master object' for configuration, interaction, and other key functions within Particle WaaS.

Other SDKs (such as Particle Auth Core and Particle Connect) use hooks to achieve equivalent functionality. Instead, this version of Particle Auth centralizes everything through an instance of ParticleNetwork.

Login

await particle.auth.login(params) // Params is optional; see introduction for more detailed explanation
import { ParticleNetwork } from "@particle-network/auth";

const particle = new ParticleNetwork({...});

let userInfo;
if (!particle.auth.isLogin()) {
    userInfo = await particle.auth.login(params);
} else {
    userInfo = particle.auth.getUserInfo();
}

Is User Logged In

particle.auth.isLogin() // Returns boolean

// Or, alternatively, check also if token is valid; will refresh user security account info
await particle.auth.isLoginAsync()
import { ParticleNetwork } from "@particle-network/auth";

const particle = new ParticleNetwork({...});

const result = particle.auth.isLogin()

Get User Info

📪

getUserInfo API endpoint available

An API endpoint for getUserInfo is also available.

particle.auth.getUserInfo(); // Pulls from user active/logged in within instance; returns null if none
import { ParticleNetwork } from "@particle-network/auth";

const particle = new ParticleNetwork({...});

const info = particle.auth.getUserInfo();

Status Listeners

Particle native usage also supports opening listeners for specific events: connect, disconnect, and chainChanged. connect returns userInfo (same as getUserInfo),disconnect returns nothing, andchainChanged returns the chain that was connected to.

particle.auth.on('connect', (userInfo) => {
    console.log("particle userInfo", userInfo);
});
particle.auth.on('disconnect', () => {
    console.log("particle disconnect");
});
particle.auth.on('chainChanged', (chain) => {
    console.log("particle chainChanged", chain);
});

Set Auth Theme

The theme of the authentication menu, wallet modal, and confirmation popup can also be altered retroactively through setAuthTheme.

particle.setAuthTheme({
  uiMode: "dark",
  displayCloseButton: true,
  displayWallet: true, // Display wallet entrance
  modalBorderRadius: 10, // Auth & wallet modal border radius. default is 10
});
import { ParticleNetwork } from "@particle-network/auth";

const particle = new ParticleNetwork({...});
particle.setAuthTheme({
  uiMode: "dark",
  displayCloseButton: true,
  displayWallet: true,
  modalBorderRadius: 10,
});

Set Language

// Supports languages: en, zh-CN, zh-TW, zh-HK, ja, ko
particle.setLanguage('en');

Set Fiat Denomination

// Supports fiat coin values: 'USD' | 'CNY' | 'JPY' | 'HKD' | 'INR' | 'KRW'
particle.setFiatCoin('USD');

Set ERC-4337 Mode (in-UI)

As a result of Particle's native ERC-4337 account abstraction compatibility, if you're using this integration (and specifically the associated smart account implementation(s)), "ERC-4337 Mode" can be set within the Particle Wallet UI, displaying the smart account of the user rather than the default EOA.

// Enable ERC-4337 in-UI, openWallet will open Account Abstraction Wallet
particle.setERC4337(true);

Switch Chain Info

// Retroactive configuration of chainName and chainId from ParticleNetwork
particle.switchChain({
    name: Polygon.name, // Optional: resolves to 'polygon'
    id: Polygon.id, // Optional: resolves to 137
})
import { ParticleNetwork } from "@particle-network/auth";
import { Polygon } from "@particle-network/chains";

const particle = new ParticleNetwork({...});
particle.switchChain({
    name: Polygon.name,
    id: Polygon.id,
})

Security Account

A user's security account is the primary mechanism for introducing additional security layers onto their MPC-based account. After configuring the security account within ParticleNetwork (if applicable), further information can be retrieved afterward, such as Booleans based upon whether a user has a master password, payment password, or other associated security mechanisms enabled.

Open Security Account UI

particle.auth.openAccountAndSecurity()
import { ParticleNetwork } from "@particle-netwok/auth";

const particle = new ParticleNetwork({...});
particle.auth.openAccountAndSecurity().catch((error) => {
    if (error.code === 4011) {
        // Ignore window close
    } else if (error.code === 10005) {
        // Invalid token
    } else if (error.code === 8005) {
        // User not logged in
    }
});

Retrieve Security Account

const securityAccount = await particle.auth.getSecurityAccount();

Security Feature Status

particle.auth.hasMasterPassword();
particle.auth.hasPaymentPassword();
particle.auth.hasSecurityAccount();

Open Particle Web Wallet

If you've chosen not to include the wallet popup in-UI or are just looking for an intentional mechanism for opening the wallet UI, you can do so through the example below:

particle.openWallet(target?: string, features?: string)
import { ParticleNetwork } from "@particle-network/auth";

const particle = new ParticleNetwork({...});

// Need check login state when open wallet.
// To set target and features for custom window style, it's the same as window.open().
particle.openWallet(target?: string, features?: string)


// Open wallet in iframe
const url = pn.buildWalletUrl({
    // Optional: Left top menu style, close or fullscreen
    // "fullscreen": Wallet will be fullscreen when users click.
    // "close": Developer will need to handle click event
    topMenuType: "close"   
});

const iframe = document.createElement("iframe");
iframe.src = url;
// If topMenuType is "close"
window.addEventListener("message", (event) => {
    if (event.data === "PARTICLE_WALLET_CLOSE_IFRAME") {
        //close click event
    }
})

Open On-ramp (Buy Crypto) Page

The Particle Wallet features a built-in on-ramp aggregator, facilitating the usage of various on-ramp providers to buy tokens with a debit card or directly through a bank account. This popup can be programmatically initiated through the following:

particle.openBuy(options?: OpenBuyOptions, target?: string, features?: string)
import { ParticleNetwork } from "@particle-network/auth";

const particle = new ParticleNetwork({...});
                                      
// To set target and features for custom window style, it's the same as window.open().
particle.openBuy(options?: OpenBuyOptions, target?: string, features?: string)

options (type OpenBuyOptions) includes a diverse set of parameters, outlined as follows:

NameDescriptionTypeRequired
network[Solana, Ethereum, Binance Smart Chain, Polygon, Tron, Optimism, etc.]stringFalse (True if Particle not connected)
fiatCoinFiat currency denominationstringFalse
cryptoCoinCryptocurrency denominationstringFalse
fiatAmtThe amount of fiat to be automatically filled in as the purchase volumenumberFalse
boolLock fiat currency in the buy menuboolFalse
fixCryptoCoinLock cryptocurrency in the buy menuboolFalse
fixFiatAmtLock fiat amount in the buy menuboolFalse
walletAddressThe wallet address to receive the cryptocurrencystringFalse (True if Particle not connected)


Integration Examples

Integrating and leveraging Particle Auth isn't only bound to interfaces provided by Particle Network (such as Particle Connect), rather it's built to fit into nearly every implementation scenario. Whether you're using RainbowKit, Web3Modal, Solana's wallet-adapter, or would instead like to have a standalone social login button within your application, Particle Auth can be used. Below you'll find an example of using Particle Auth within RainbowKit, one of the core connection kits officially supported by Particle.

RainbowKit

If you currently use RainbowKit as your primary mechanism for onboarding/connecting wallets, you can quite easily add Particle Auth as an option, either represented as "Particle" or directly as a social login, such as "Google."

To replicate the above, you can follow a process adjacent to the following:

Install RainbowKit Extension

yarn add @particle-network/rainbowkit-ext

npm install @particle-network/rainbowkit-ext

Initialize ParticleNetwork

new ParticleNetwork({
    projectId: process.env.REACT_APP_PROJECT_ID as string,
    clientKey: process.env.REACT_APP_CLIENT_KEY as string,
    appId: process.env.REACT_APP_APP_ID as string,
})
import { ParticleNetwork } from '@particle-network/auth';
import { particleWallet } from '@particle-network/rainbowkit-ext';

const particle = new ParticleNetwork({
    projectId: process.env.REACT_APP_PROJECT_ID as string,
    clientKey: process.env.REACT_APP_CLIENT_KEY as string,
    appId: process.env.REACT_APP_APP_ID as string,
    chainName: 'Ethereum',
    chainId: 1,
    wallet: { displayWalletEntry: true },
  }), []);

Add Instances of particleWallet

const popularWallets = useMemo(() => ({
    groupName: 'Popular',
    wallets: [
      particleWallet({ chains, authType: 'google' }),
      particleWallet({ chains, authType: 'facebook' }),
      particleWallet({ chains, authType: 'apple' }),
      particleWallet({ chains }),
      injectedWallet(commonOptions),
      rainbowWallet(commonOptions),
      coinbaseWallet({ appName: 'RainbowKit demo', ...commonOptions }),
      metaMaskWallet(commonOptions),
      walletConnectWallet(commonOptions),
    ],
  }), [particle]);

📘

Full RainbowKit integration tutorial available

For an extended explanation and tutorial of the above integration of Particle Auth within RainbowKit, we created a video and GitHub template repository .