Skip to main content
The Swift SDK can spin up an Alchemy smart account for any EVM wallet your user holds. The Para wallet signs; the smart account is what shows up on-chain. You get gas sponsorship through Alchemy’s Gas Manager and atomic batched calls through the Account Kit.
Only Alchemy (EIP-4337) is wired through the native bridge today. ZeroDev, Pimlico, and the other providers already available on web and React Native are on the roadmap for iOS.

Setup

  1. Create an Alchemy account, grab your API key, and create a Gas Manager policy for the chain you’re targeting. The policy ID is what tells the paymaster to sponsor gas.
  2. Stash the values wherever you keep other credentials. A plain Config struct works:
enum AlchemyConfig {
    static let apiKey = "YOUR_ALCHEMY_API_KEY"
    static let gasPolicyId = "YOUR_GAS_POLICY_ID"
    static let sepoliaChainId = 11155111
}
You don’t need to add a package. ParaSwift already knows how to reach @getpara/aa-alchemy through the bridge.

Usage

Create the smart account, then send a gasless transaction with it:
import ParaSwift

// `paraManager` is your authenticated ParaManager instance
let info = try await paraManager.createSmartAccount(
    apiKey: AlchemyConfig.apiKey,
    chainId: AlchemyConfig.sepoliaChainId,
    gasPolicyId: AlchemyConfig.gasPolicyId
)
print("Smart account:", info.smartAccountAddress)

// Zero-value tx to the burn address, paid for by the paymaster.
let receipt = try await paraManager.sendSmartAccountTransaction(
    smartAccountAddress: info.smartAccountAddress,
    chainId: AlchemyConfig.sepoliaChainId,
    to: "0x000000000000000000000000000000000000dEaD"
)
print("tx:", receipt.transactionHash, "status:", receipt.status)
Heads up on timing: UserOp inclusion on a public chain usually lands in 30 to 90 seconds, and the call blocks until it does. ParaWebView defaults its request timeout to 120s for this reason. Pass a shorter value to ParaWebView.init if you’d rather bail earlier.

Batched transactions

One UserOp, multiple calls, one receipt. Use it for approve + swap, approve + transfer, or anything that should succeed or fail together:
let batchReceipt = try await paraManager.sendSmartAccountBatchTransaction(
    smartAccountAddress: info.smartAccountAddress,
    chainId: AlchemyConfig.sepoliaChainId,
    calls: [
        SmartAccountCall(to: "0xRecipientA", value: "10000000000000000"), // 0.01 ETH
        SmartAccountCall(to: "0xRecipientB", data: "0xEncodedCallData")
    ]
)

Error handling

Provider failures come back with a SmartAccountErrorCode from @getpara/core-sdk. The ones you’re most likely to see:
  • PROVIDER_RATE_LIMITED: back off and retry.
  • SPONSORSHIP_DENIED: the gas policy rejected this UserOp. Check the Alchemy dashboard.
  • TRANSACTION_REVERTED: the target contract reverted at execution time.
  • MISSING_ACCOUNT_ADDRESS: you called sendSmartAccountTransaction before createSmartAccount for this chain + address.
Errors surface as ParaError.bridgeError and keep the provider’s original message. If a permissions policy blocks the tx, the call throws ParaError.transactionDenied(pendingTransactionId:transactionReviewUrl:) and fires setTransactionReviewHandler if you’ve registered one. Same pattern as signTransaction.

What gets returned

createSmartAccount returns a SmartAccountInfo:
struct SmartAccountInfo {
    let smartAccountAddress: String
    let mode: String       // "4337"
    let provider: String   // "ALCHEMY"
    let chainId: Int
}
sendSmartAccountTransaction and sendSmartAccountBatchTransaction return an AATransactionReceipt:
struct AATransactionReceipt {
    let transactionHash: String
    let blockHash: String
    let blockNumber: String       // uint256, decimal string
    let from: String
    let to: String?
    let status: String            // "success" or "reverted"
    let gasUsed: String
    let effectiveGasPrice: String
}
BigInt fields arrive as decimal strings so you don’t lose uint256 precision across the bridge.

Reference

Full working implementation: examples-hub/mobile/with-swift/example/SmartAccount/SmartAccountView.swift.