The Para Swift SDK enables you to integrate secure wallet features including creation, passkey-based authentication, and transaction signing into your iOS applications. This guide covers all necessary steps from installation to implementing authentication flows.

Prerequisites

To use Para, you need an API key. This key authenticates your requests to Para services and is essential for integration.

Don’t have an API key yet? Request access to the Developer Portal to create API keys, manage billing, teams, and more.

Installation

1

Add the Para Swift SDK Package

  1. In your Xcode project, go to File > Add Packages or (in your target) Frameworks, Libraries, and Embedded Content and click +.
  2. Enter https://github.com/getpara/swift-sdk
  3. Select Up to Next Major Version ensuring 2.0.0 < 3.0.0
  4. Add the package to your app target and click Add Package.
2

Configure Associated Domains for Passkeys

To enable passkeys on iOS, you need to configure Associated Domains:

  1. In Xcode, go to Signing & Capabilities for your app target
  2. Click + Capability and add Associated Domains
  3. Add the following entries:
    webcredentials:app.usecapsule.com
    webcredentials:app.beta.usecapsule.com
    
  4. Provide your Team ID + Bundle ID to Para, e.g. A1B2C3D4E5.com.example.yourapp

Without properly registering your domain with Para, passkey authentication flows will fail. Contact Para support if you encounter issues with passkey registration.

Beta Testing Credentials In the BETA Environment, you can use any email ending in @test.getpara.com (like dev@test.getpara.com) or US phone numbers (+1) in the format (area code)-555-xxxx (like (425)-555-1234). Any OTP code will work for verification with these test credentials. These credentials are for beta testing only. You can delete test users anytime in the beta developer console to free up user slots.

Initialization

First, you need to initialize the Para manager in your app. Below is an example of initializing the SDK in a SwiftUI application:

import SwiftUI
import ParaSwift

@main
struct ExampleApp: App {
    @StateObject private var paraManager: ParaManager
    @StateObject private var appRootManager = AppRootManager()

    init() {
        let environmentString = Bundle.main.object(forInfoDictionaryKey: "PARA_ENVIRONMENT") as? String ?? "beta"
        let environment: ParaEnvironment = environmentString == "beta" ? .beta : .prod
        let apiKey = Bundle.main.object(forInfoDictionaryKey: "PARA_API_KEY") as! String

        _paraManager = StateObject(wrappedValue: ParaManager(environment: environment, apiKey: apiKey))
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(paraManager)
        }
    }
}

Authentication

Para uses a streamlined authentication flow centered around the AuthState object. The SDK automatically determines whether a user is new or existing and guides you through the appropriate flow.

Email authentication verifies users via a one-time password (OTP) sent to their email address and then sets up a biometric-enabled passkey for future logins.

1

Initiate Authentication Flow

Start the authentication process with an email:

let authState = try await paraManager.initiateAuthFlow(auth: .email(userEmail))

This returns an AuthState object indicating the next step based on whether the user exists.

2

Handle the Authentication State

Process the returned AuthState based on its stage:

switch authState.stage {
case .verify:
    // New user - needs email verification
    // Show verification code input UI
    break
    
case .login:
    // Existing user - proceed with login
    try await paraManager.handleLogin(
        authState: authState,
        authorizationController: authorizationController,
        webAuthenticationSession: webAuthenticationSession
    )
    // User is now logged in
    break
    
case .signup:
    // Shouldn't happen directly from initiateAuthFlow
    break
}
3

For New Users: Handle Verification

If the user is new (.verify stage), handle the verification code:

// User enters verification code from their email
let verificationCode = "123456" // Replace with actual code from UI input

// Submit the verification code
let verifiedState = try await paraManager.handleVerificationCode(
    verificationCode: verificationCode
)

// verifiedState.stage should now be .signup
4

Complete Signup

After verification, complete the signup process:

// Check available signup methods
let passkeyAvailable = paraManager.isSignupMethodAvailable(
    method: .passkey, 
    authState: verifiedState
)

// Choose signup method (passkey preferred)
let method: ParaManager.SignupMethod = passkeyAvailable ? .passkey : .password

// Complete signup with chosen method
try await paraManager.handleSignup(
    authState: verifiedState,
    method: method,
    authorizationController: authorizationController,
    webAuthenticationSession: webAuthenticationSession
)

// User is now signed up and logged in with a wallet created

Subsequent Logins

After initial setup, users can log in using their email or phone with the same authentication flow:

// Login with email
let authState = try await paraManager.initiateAuthFlow(auth: .email(userEmail))

// Since the user exists, authState.stage will be .login
if authState.stage == .login {
    try await paraManager.handleLogin(
        authState: authState,
        authorizationController: authorizationController,
        webAuthenticationSession: webAuthenticationSession
    )
    // User is now logged in
}

Alternatively, you can use direct passkey login if you know the user has set up passkeys:

// Direct passkey login (will show all available passkeys if email/phone not provided)
try await paraManager.loginWithPasskey(
    authorizationController: authorizationController,
    email: userEmail, // Optional - helps filter passkeys
    phone: nil
)

This will prompt the user for Face ID or Touch ID verification before granting access to their wallets.

Checking Authentication Status

You can check if a user is already authenticated:

let isLoggedIn = await paraManager.isFullyLoggedIn()

if isLoggedIn {
    // User is authenticated, proceed to main app flow
} else {
    // Show login/signup UI
}

Sign out

To sign out a user and clear their session:

try await paraManager.logout()

Basic Wallet Operations

After successful authentication, you can perform wallet operations:

// Get all user wallets
try await paraManager.fetchWallets() // Ensure we have the latest wallets

if paraManager.wallets.isEmpty {
    // No wallets, perhaps create one
    try await paraManager.createWallet(type: .evm, skipDistributable: false)
    // paraManager.wallets is updated internally
    guard let firstWallet = paraManager.wallets.first else {
        print("Failed to create or find a wallet.")
        return
    }
    
    // Sign a simple message (SDK handles Base64 encoding internally)
    let signature = try await paraManager.signMessage(
        walletId: firstWallet.id,
        message: "Hello, Para!"
    )
    print("Signature: \(signature)")
    
} else {
    // Use existing wallet
    guard let firstWallet = paraManager.wallets.first else { return }
    
    // Sign a simple message (SDK handles Base64 encoding internally)
    let signature = try await paraManager.signMessage(
        walletId: firstWallet.id,
        message: "Hello, Para!"
    )
    print("Signature: \(signature)")
}

For detailed transaction signing with specific blockchains (EVM, Solana, Cosmos), please refer to the respective blockchain integration guides.

Example

For a complete implementation example, check out our Swift SDK example app:

Next Steps

After integrating Para into your Swift app, you can explore other features and integrations to enhance your Para experience.