Particle Connect for iOS

Integrating Particle Connect into your iOS application allows for seamless onboarding, combining Web2-like and Web3-native experiences. It functions as an SSO for Web3, unifying various onboarding methods within a customizable in-house connection modal.

This includes options for Web3 accounts via social logins, Web3 wallets through WalletConnect and Solana’s wallet-adapter, and direct private key imports. Particle Connect on iOS aims to provide a universally accessible gateway into mobile dApps.

Implementing Particle Connect in your iOS application is straightforward, as detailed below.

Repository

Before starting the configuration and usage process, explore our demo repository to understand the architecture and implementation flow. You can find the official Particle Connect iOS SDK repository.

Using a private key or the mnemonic import/generate function is strongly discouraged. If you use it, you need to secure the data yourself, as Particle doesn’t have a relationship with the imported/generated mnemonic or private key.

Getting Started

Prerequisites

The setup process on iOS can be somewhat lengthy, so to ensure you don’t run into any compatibility issues and can get started as soon as possible, you’ll need to confirm that your project meets several different prerequisites:

  • Xcode 15.0 or higher
  • CocoaPods 1.14.3 or higher
  • iOS 14 or higher

Setting up the Particle dashboard

Additionally, before diving in, you’ll need to retrieve three universally required values from the Particle dashboard: your projectId, clientKey, and appId. These directly link your project (instance of Particle Connect) with the Particle dashboard to enable customization, analytics, tracking, etc.

Follow the quickstart tutorial to set up a project and find the required keys: Create a new project.

Dependencies

The Podfile is a critical element of the Particle Connect integration. It’s where you declare the numerous dependencies required. This file serves as a roadmap for the pods (packages) to be used within your application.

It’s crucial to have a Podfile in place. If you don’t have one, head over to the root of your project directly to create one. Run the following command to get started:

Terminal
pod init

Then, within your Podfile, you’ll need to throw in a few different dependencies, the specifics of which depend on the functions of Particle Connect that you’d like to use.

Connect V2

We recommend trying our Particle Connect Demo to easily customize and design your connect page. After customizing, you can copy the generated code from the Code Snippets section and paste it into the ConnectKitDemo to experience it directly in your app.

Starting from version 2.0.0, you can quickly integrate the ParticleConnectKit SDK, which includes features such as social login, SNS login, third-party wallet login, and customizable login UI.

Podfile
pod `ParticleConnectKit`
pod 'AuthCoreAdapter'
pod 'ParticleConnect'
pod 'ConnectPhantomAdapter'
pod 'ConnectWalletConnectAdapter'
pod 'ConnectCommon'

Connect V1 (Legacy)

  1. Regardless of specific functions, you’ll need ConnectCommon and ParticleConnect, alongside AuthCoreAdapter.
  2. If you’d like to support importing (or generating fresh) wallets on EVM chains, you’ll need ConnectEVMAdapter.
    1. To do the same on Solana, use ConnectSolanaAdapter.
  3. To support connection with Phantom (on Solana), you’ll need to use ConnectPhantomAdapter
  4. For general WalletConnect-based connection, you can use ConnectWalletConnectAdapter:
Podfile
pod 'ConnectCommon'
pod 'ParticleConnect'
pod 'ConnectWalletConnectAdapter'
pod 'ConnectEVMAdapter'
pod 'ConnectSolanaAdapter'
pod 'ConnectPhantomAdapter'
pod 'AuthCoreAdapter'

With this set, you can simply run the following command to ensure changes are saved:

Terminal
pod install --repo-update
open your-project.xcworkspace

Before continuing, paste the following code into your Podfile if it is not already present; this is required for utilizing Particle Connect. Otherwise, you’ll encounter issues.

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
    config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
      end
    end
end

Configuration

Now that you’ve retrieved your projectId, clientKey, and appId from the Particle dashboard and also set up your Podfile, it’s time to move on to the core configuration of your project before diving into examples of utilization. To begin this process, configure a ParticleNetwork-Info.plist file.

Since it’s likely not already present, head to the root of your project and make a blank file, ParticleNetwork-Info.plist

Within this file, you must enter the authentication credentials retrieved from the Particle dashboard. You can do this through the structure below:

ParticleNetwork-Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>PROJECT_UUID</key>
	<string>YOUR_PROJECT_UUID</string>
	<key>PROJECT_CLIENT_KEY</key>
	<string>YOUR_PROJECT_CLIENT_KEY</string>
	<key>PROJECT_APP_UUID</key>
	<string>YOUR_PROJECT_APP_UUID</string>
</dict>
</plist>

Additionally, head over to your UIApplicationDelegate. You’ll need to import the various libraries previously declared within your Podfile at the top of the file. An example of this can be seen below:

import AuthCoreAdapter
import ConnectCommon
import ConnectPhantomAdapter
import ConnectWalletConnectAdapter
import ParticleNetworkBase
import ParticleNetworkChains
import ParticleConnect

Initialization

Now that these are set, you’ll need to initialize Particle Connect itself (within UIApplicationDelegate). Particle Connect will not function until initialization is complete (and valid). Essentially, initialization involves declaring specific imported adapters, the chain to be used, and application metadata.

Specifically, ParticleConnect.initialize takes the following parameters:

FieldTypeDescription
envDevEnvironmentThe specific environment to be used. This can be either .debug, .staging, or .production; it should be set to .production when released.
chainInfoChainInfoThe primary chain to be used within your application. It should include an overall blockchain and a specific network. In this example, it’s .ethereum(alternatively, it could be .ethereumSepolia, and so on).
dAppDataDAppMetadataMetadata outlining the description of your application; this should be an instance of DAppMetadata with the following parameters: name, the name of your project. icon, a URL linking to the icon of your project; this should be 512x512, ideally. url, the URL of your project’s website. description, the description of your project, redirectUniversalLink, optional, redirect universal link for your project, default is DAppMetadata.standard.
adapters[ConnectAdapter]You’ll need to pass a list of adapters to be offered within the connection modal. This can be defined as a simple array containing various imported adapters (such as AuthCoreAdapter, MetaMaskConnectAdapter, etc.)

An example of initialization can be found below.

var adapters: [ConnectAdapter] = [
       AuthCoreAdaper(),
       WalletConnectAdapter(),
       MetaMaskConnectAdapter(),
       PhantomConnectAdapter(),
       ImtokenConnectAdapter(),
       BitgetConnectAdapter(),
       RainbowConnectAdapter(),
       TrustConnectAdapter(),
		   ZerionConnectAdapter(),
       MathConnectAdapter(),
       Inch1ConnectAdapter(),
       ZengoConnectAdapter(),
       AlphaConnectAdapter(),
       OKXConnectAdapter()
       ]

ParticleConnect.initialize(env: .debug, chainInfo: .ethereumSepolia, dAppData: .standard, adapters: adapters)

Scheme Configuration

The final step in setting up your iOS project is configuring a scheme URL and LSAApplicationQueriesSchemes within your application. Beginning with the scheme URL, add the following to your dApp’s application(_:open:options:) method:

return ParticleConnect.handleUrl(url)

From there, you’ll need to configure the scheme URL itself; select your application target within the “Info” section, add a URL type, and include the scheme. This scheme should be pn added to your appId.

For example, if your appId is 63bfa427-cf5f-4742-9ff1-e8f5a1b9828f, then the scheme URL would be pn63bfa427-cf5f-4742-9ff1-e8f5a1b9828f —simply adding pn to the beginning of the appId.

With the scheme URL configured, you’ll also need to head into info.plist and add LSApplicationQueriesSchemes according to your specific utilization of Web3 wallets (not Particle Auth, but independent wallet adapters). An example of this can be seen below (each of these is optional and dependent upon your given implementation)

Info.plist
<key>LSApplicationQueriesSchemes</key>
<array>
<string>metamask</string>
<string>phantom</string>
<string>bitkeep</string>
<string>imtokenv2</string>
<string>rainbow</string>
<string>trust</string>
<string>zerion</string>
<string>mathwallet</string>
<string>oneinch</string>
<string>awallet</string>
<string>okex</string>
<string>bnc</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access in order to open photos of barcodes</string>
<key>NSCameraUsageDescription</key>
<string>We use the camera to scan barcodes</string>
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID for secure access to the app.</string>

Examples of Utilization

Utilization of WalletConnect V2

You can set specific chains within WalletConnect (chainInfo structures) with setWalletConnectV2SupportChainInfos. An example of this can be found below.

ParticleConnect.setWalletConnectV2SupportChainInfos([.ethereum, .polygon])

Get all Wallet Adapter

To retrieve all wallet adapters either in total, by chainType (evm or solana), or by address, you’ll need to call one of the following methods:

  • ParticleConnect.getAllAdapters, returns all adapters (for every address and chainType).
  • ParticleConnect.getAdapters, retrieves adapters by chainType (evm or solana).
  • ParticleConnect.getAdapterByAddress, returns all adapters associated with a specific (user) public address.
let adapter = ParticleConnect.getAllAdapters()
            .filter { $0.walletType == .authCore }.first

let adapters = ParticleConnect.getAdapters(chainType: .evm)

let adapters = ParticleConnect.getAdapterByAddress(publicAddress: address)

Get Adapter Accounts

adapter.getAccounts returns a list of accounts associated/connected with a specific adapter. adapter is an instance of a given wallet adapter, such as AuthCoreAdapter. Each adapter has a getAccounts method by default. Alternatively, you’ll need to call adapter.getSmartAccounts to retrieve smart accounts associated with a given adapter. E.g.:

let accounts = adapter.getAccounts()

// Alternatively
let smartAccounts = try await adapter.getSmartAccounts()

Switch Chain

Often, the primary chain initially set through initialize won’t be the only chain used within your application; thus, to account for this and change the primary chain post-initialization, you can call ParticleNetwork.setChainInfo, passing in a chainInfo object, as shown in the example below.

WalletType.authCore comes from Particle Auth Core and supports EVM and Solana.

If you need a switch chain for one of the two accounts and need to switch from Solana to EVM or EVM to Solana, you can call (adapter as! ParticleAdapter).switchChain.

ParticleNetwork.setChainInfo(.ethereum)

// Switch chains, such as between Solana and Ethereum.
// Ensure the adapter.walletType is authCore or particle.
let result = try await (adapter as! ParticleAdapter).switchChain(.ethereum).value

Connect Wallet V2

ParticleConnectUI.connect is a one-click login function that integrates a customizable login UI. The parameter config can be derived from ConnectKitConfig.

ConnectKitConfig.init allows these parameters:

FieldTypeDescription
connectOptions[ConnectOption]connectOptions supports email, phone, social and wallet, the sort order is used for the Connect Kit login UI.
socialProviders[EnableSocialProvider]socialProviders supports google, apple and other social options, the sort order is used for the Connect Kit login UI.
walletProviders[EnableWalletProvider]walletProviders supports metamask, trust and other wallet options, the sort order is used for the Connect Kit login UI.
additionalLayoutOptionsAdditionalLayoutOptionsLayout options.
designOptionsDesignOptionsDesign options.
let config = ConnectKitConfig(connectOptions: connectOptions, socialProviders: socialProviders, walletProviders: walletProviders, additionalLayoutOptions: additionalLayoutOptions, designOptions: designOptions)
let account = try await ParticleConnectUI.connect(config: config).value

Connect Wallet V1 (Legacy)

Specific adapters can initiate the connection (through an initialization in a variable such as adapter) with the connect method. This method will display a modal facilitating connection depending on the specific adapter.

For EVMConnectAdapter and SolanaConnectAdapter, the ‘connect’ method will initiate a connection and generate a fresh account (public and private key pair). Otherwise, the specific connection flow will be dependent upon the adapter in which connect is called. E.g.:

FieldTypeDescription
configConnectConifg?The ConnectConfig will only be effective when adapter.walletType is .authCore. You can construct a valid object using the subclass ParticleAuthCoreConfig. When adapter.walletType is another value, you can pass ConnectConfig.none.

ParticleAuthCoreConfig construct method init support the following parameters:

FieldTypeDescription
loginTypeLoginTypeThe specific social login to be used. This can be either .email, .phone, .google, .apple, .jwt, .facebook, .twitter, .discord, .github, .twitch, .microsoft or linkedin.
supportAuthType[SupportAuthType]The methods of authentication visible on the authentication popup UI. By default, this will be exclusive to the chosen social login method, although by passing in additional types, you can expand the UI to include the ability to login with those as an alternative to type.
accountString?(Optional) when type is set to either .email, .phone, or .jwt, you can use the account parameter to pass in an expected email, phone number, or JWT. This is optional for the former two, but required for .jwt. The phone number must be in E.164 format.
codeString?(Optional) when type is set to either .email pr .phone, you can use the code parameter to pass the verification code.
socialLoginPromptSocialLoginPrompt?(Optional) either .none, .consent, or .select_account
loginPageConfigLoginPageConfig?(Optional) to customize the UI page, contains project name, icon and description.
You can use your existing user base or authentication method with Particle Connect through JWT. This way, your users can still log in with their current accounts. Check the JWT guide to learn how to configure JWT.

// Retrieve and use MetaMask adapter
let adapter = ParticleConnect.getAllAdapters().first { $0.walletType == .metaMask }!
let account = try await adapter.connect(ConnectConfig.none).value

// Retrieve and use Particle (social login) adapter
let adapter = ParticleConnect.getAllAdapters().first { $0.walletType == .authCore }!
let account = try await adapter.connect(ParticleAuthCoreConfig(loginType: .google, socialLoginPrompt: .selectAccount)).value

Only after connecting with Particle Connect is successful can you retrieve userInfo by calling the (adapter as! ParticleAdapter).getUserInfo method; other wallet types can’t call this method.

let userInfo = (adapter as! ParticleAdapter).getUserInfo

Disconnect Wallet

Alternatively, if a user has already connected their wallet, you can use disconnect as the primary method to disconnect a user within an active session. This will be relatively universal across adapters. E.g.:

FieldTypeDescription
publicAddressStringThe connected public address, if adapter.WalletType is .authCore, you can pass an empty string, for other walletTypes, you need pass a connected public address.
let result = try await adapter.disconnect(address).value

Is Connected

Another quite important method is isConnected, returning a Boolean based on whether or not a given session (user) is currently connected to a wallet (through the specific adapter instance). adapter.isConnected takes a given public address and returns true or false depending on its connection status. E.g.:

FieldTypeDescription
publicAddressStringThe connected public address, if adapter.WalletType is .authCore, you can pass an empty string, for other walletTypes, you need to pass a connected public address.
let result = adapter.isConnected(publicAddress: address)

If the account originates from .authCore, it’s recommended to directly use auth.isConnected to check the user’s login status.

import ParticleAuthCore

let auth = Auth()
let result = try await auth.isConnected()

Import Wallet

If you’re using EVMConnectAdapter or SolanaConnectAdapter, you can import wallets through either a seed phrase or private key.

These methods will associate an account instance derived from these keys, allowing utilization within your application. These can be achieved through either importWalletFromPrivateKey for importing a private key or importWalletFromMnemonic for importing a mnemonic (seed phrase).

Additionally, you can export one of these wallets with adapter.exportWalletPrivateKey, passing in the address (of the EVMConnectAdapter or SolanaConnectAdapter imported/generated wallet) that you’d like to export.

This is not the import/export of wallets within Particle Auth; Particle Network’s MPC-TSS accounts do not support importing or exporting private keys.

This is specific to a custom account connection with Particle Connect.

let account = try await adapter.importWalletFromPrivateKey(privateKey).value

let account = try await adapter.importWalletFromMnemonic(mnemonic).value

// Exportation
let privateKey = try await adapter.exportWalletPrivateKey(publicAddress: address).value

Sign and Send Transaction

To initiate a signature and complete an on-chain settlement, use adapter.signAndSendTransaction—the primary method for sending transactions through Particle Connect for EVM and Solana networks. When you call this method, a popup will appear in the specified adapter, prompting the user to provide a signature. Once the user confirms, the transaction is immediately sent to the network.

The adapter.signAndSendTransaction method accepts the following parameters:

FieldTypeDescription
publicAddressStringThe connected public address, if adapter.WalletType is .authCore, you can pass an empty string, for other wallet`Types, you need to pass a connected public address.
transactionStringAn EVM transaction is expressed as a hexadecimal string, a Solana transaction is expressed as a base58 string.
let txHash = try await adapter.signAndSendTransaction(publicAddress: address, transaction: transaction).value

You can create an EVM transaction with class TxData or FeeMarketEIP1559TxData, and also you can use another SDK, ParticleWalletAPI, to create a transaction, you can reference the examples from Wallet SDK reference

Sign Transaction

adapter.signTransaction is a Solana-specific method for signing a transaction without pushing it to the network. Specifically, this method requires the following parameters:

FieldTypeDescription
publicAddressStringThe connected public address, if adapter.WalletType is .authCore, you can pass an empty string, for other walletTypes, you need to pass a connected public address.
transactionStringA base58 string representing a transaction object.

This will return a popup requesting confirmation to either SolanaConnectAdapter or PhantomConnectAdapter depending on which you’ve defined as adapter (or a custom adapter you’ve set up). E.g.:

let signed = try await adapter.signTransaction(publicAddress: address, transaction: transaction).value

// Alternatively, the plural form of this method, takes a list of transactions
let signeds = try await adapter.signAllTransactions(publicAddress: address, transactions: transactions).value

Sign Message (EIP191)

To sign a basic UTF-8 message (string), you can use adapter.signMessage to prompt the user for a signature alongside the message in question. adapter.signMessage takes the following parameters:

FieldTypeDescription
publicAddressStringThe connected public address, if adapter.WalletType is .authCore, you can pass an empty string, for other walletTypes, you need to pass a connected public address.
messageStringThe message you’d like the user to sign; for EVM, this should be a hex-encoded string (prefixed by ‘0x’), while for Solana, you can pass a standard string (no encoding needed).
let signed = try await adapter.signMessage(publicAddress: address, message: message).value

Sign Typed Data V4 (EIP712)

As an alternative to signMessage, for EVM chains, you can request that a user signs typed (structured) data rather than purely a raw string. You can use adapter.signTypedData (equivalent to eth_signTypedData V4) to do this. This takes the following parameters:

FieldTypeDescription
publicAddressStringThe connected public address, if adapter.WalletType is .authCore, you can pass an empty string, for other walletTypes, you need to pass a connected public address.
dataStringThe typed data to be signed; see the Web (JavaScript/TypeScript) page for additional guidance.
let signed = try await adapter.signTypedData(publicAddress: publicAddress, data: message).value

(Optional) Custom WalletConnect Projcet ID

ParticleConnect.setWalletConnectV2ProjectId("WalletConnect V2 Project ID")

Custom WalletConnect Adapter

There may be cases where the existing selection of adapters doesn’t include a wallet you’d like to be featured within Particle Connect.

Creating a custom adapter (using WalletConnectAdapter as the parent class) is quite simple in this situation. Doing so will require a structure similar to the example below, in which a custom Coin98 adapter is made by overriding walletType with a custom AdapterInfo instance.

public class Coin98WalletConnectAdapter: WalletConnectAdapter {
  public override var walletType: WalletType {
      return WalletType.custom(info: AdapterInfo.init(name: "Coin98",
                   url: "https://coin98.com/",
                   icon: "https://registry.walletconnect.com/v2/logo/md/dee547be-936a-4c92-9e3f-7a2350a62e00",
                   redirectUrlHost: "coin98",
                   supportChains: [.evm],
                   deepLink: "coin98://", // Requires either universal link or scheme
                   scheme: "coin98://"))
  }
}

Dive Deeper

ParticleConnect includes ParticleAuthCore. If you log in with a Particle account, you can access additional functions such as getUserInfo, openAccountAndSecurity, hasMasterPassword and hasPaymentPassword and changeMasterPassword, you can reference the docs from Auth SDK reference.