Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt

Use this file to discover all available pages before exploring further.

Wallet Integration

The WalletDelegate protocol is the bridge between the Privacy Boost SDK and your wallet. This guide covers implementing it for various wallet types, expanding on the brief examples in Authentication.

Protocol Definition

public protocol WalletDelegate: AnyObject {
    /// Get the connected wallet address
    func getAddress() async throws -> String

    /// Get the current chain ID
    func getChainId() async throws -> UInt64

    /// Sign a personal message (EIP-191)
    func signMessage(message: String) async throws -> String

    /// Sign EIP-712 typed data
    func signTypedData(typedDataJson: String) async throws -> String

    /// Send a transaction
    func sendTransaction(toAddress: String, value: String, data: String) async throws -> String
}

Implementation Requirements

Return Values

  • Addresses: Checksummed hex strings with 0x prefix (42 characters)
  • Signatures: Hex strings with 0x prefix (132 characters for 65 bytes)
  • Transaction hashes: Hex strings with 0x prefix (66 characters)
  • Chain ID: Integer matching expected network

Async Behavior

All WalletDelegate methods are async throws. The SDK awaits each call, so your implementation can use native Swift concurrency (async/await).

WalletConnect Integration

Example using WalletConnect Swift SDK:
import WalletConnectSwift

class WalletConnectDelegate: WalletDelegate {
    private let session: Session
    private let client: Client

    init(session: Session, client: Client) {
        self.session = session
        self.client = client
    }

    func getAddress() throws -> String {
        guard let account = session.walletInfo?.accounts.first else {
            throw WalletError.notConnected
        }
        return account
    }

    func getChainId() throws -> UInt64 {
        guard let chainId = session.walletInfo?.chainId else {
            throw WalletError.notConnected
        }
        return UInt64(chainId)
    }

    func signMessage(message: String) throws -> String {
        let semaphore = DispatchSemaphore(value: 0)
        var result: Result<String, Error>?

        let address = try getAddress()

        try client.personal_sign(
            url: session.url,
            message: message,
            account: address
        ) { response in
            switch response {
            case .success(let signature):
                result = .success(signature)
            case .failure(let error):
                result = .failure(error)
            }
            semaphore.signal()
        }

        semaphore.wait()

        switch result {
        case .success(let signature):
            return signature
        case .failure(let error):
            throw error
        case .none:
            throw WalletError.timeout
        }
    }

    func signTypedData(typedDataJson: String) throws -> String {
        let semaphore = DispatchSemaphore(value: 0)
        var result: Result<String, Error>?

        let address = try getAddress()

        try client.eth_signTypedData(
            url: session.url,
            account: address,
            message: typedDataJson
        ) { response in
            switch response {
            case .success(let signature):
                result = .success(signature)
            case .failure(let error):
                result = .failure(error)
            }
            semaphore.signal()
        }

        semaphore.wait()

        switch result {
        case .success(let signature):
            return signature
        case .failure(let error):
            throw error
        case .none:
            throw WalletError.timeout
        }
    }

    func sendTransaction(toAddress: String, value: String, data: String) throws -> String {
        let semaphore = DispatchSemaphore(value: 0)
        var result: Result<String, Error>?

        let address = try getAddress()

        let transaction = Client.Transaction(
            from: address,
            to: toAddress,
            data: data,
            gas: nil,
            gasPrice: nil,
            value: value,
            nonce: nil
        )

        try client.eth_sendTransaction(
            url: session.url,
            transaction: transaction
        ) { response in
            switch response {
            case .success(let txHash):
                result = .success(txHash)
            case .failure(let error):
                result = .failure(error)
            }
            semaphore.signal()
        }

        semaphore.wait()

        switch result {
        case .success(let txHash):
            return txHash
        case .failure(let error):
            throw error
        case .none:
            throw WalletError.timeout
        }
    }
}

enum WalletError: Error {
    case notConnected
    case timeout
    case userRejected
}

Web3.swift Integration

Example using web3.swift with a local keystore:
import web3swift

class Web3Delegate: WalletDelegate {
    private let web3: web3
    private let keystoreManager: KeystoreManager
    private let address: EthereumAddress

    init(web3: web3, keystoreManager: KeystoreManager, address: EthereumAddress) {
        self.web3 = web3
        self.keystoreManager = keystoreManager
        self.address = address
    }

    func getAddress() throws -> String {
        return address.address
    }

    func getChainId() throws -> UInt64 {
        guard let chainId = web3.provider.network?.chainID else {
            throw Web3Error.connectionError
        }
        return UInt64(chainId)
    }

    func signMessage(message: String) throws -> String {
        let messageData = message.data(using: .utf8)!
        let signature = try web3.wallet.signPersonalMessage(
            messageData,
            account: address,
            password: ""  // Or prompt user
        )
        return signature.toHexString().addHexPrefix()
    }

    func signTypedData(typedDataJson: String) throws -> String {
        // Parse typed data and sign
        let data = typedDataJson.data(using: .utf8)!
        let typedData = try JSONDecoder().decode(EIP712TypedData.self, from: data)

        let signature = try web3.wallet.signTypedData(
            typedData,
            account: address,
            password: ""
        )
        return signature.toHexString().addHexPrefix()
    }

    func sendTransaction(toAddress: String, value: String, data: String) throws -> String {
        var transaction = EthereumTransaction(
            to: EthereumAddress(toAddress)!,
            value: BigUInt(value)!,
            data: Data(hex: data)
        )

        let result = try web3.eth.sendTransaction(transaction)
        return result.hash
    }
}

Testing with Mock Wallet

For testing, create a mock delegate:
class MockWalletDelegate: WalletDelegate {
    let mockAddress = "0x1234567890123456789012345678901234567890"
    let mockChainId: UInt64 = 1

    var signMessageHandler: ((String) -> String)?
    var signTypedDataHandler: ((String) -> String)?
    var sendTransactionHandler: ((String, String, String) -> String)?

    func getAddress() throws -> String {
        return mockAddress
    }

    func getChainId() throws -> UInt64 {
        return mockChainId
    }

    func signMessage(message: String) throws -> String {
        if let handler = signMessageHandler {
            return handler(message)
        }
        // Return a valid-looking signature
        return "0x" + String(repeating: "ab", count: 65)
    }

    func signTypedData(typedDataJson: String) throws -> String {
        if let handler = signTypedDataHandler {
            return handler(typedDataJson)
        }
        return "0x" + String(repeating: "cd", count: 65)
    }

    func sendTransaction(toAddress: String, value: String, data: String) throws -> String {
        if let handler = sendTransactionHandler {
            return handler(toAddress, value, data)
        }
        return "0x" + String(repeating: "ef", count: 32)
    }
}

Error Handling

Throw appropriate errors from delegate methods:
enum WalletDelegateError: Error, LocalizedError {
    case notConnected
    case userRejected
    case networkError(String)
    case invalidSignature
    case transactionFailed(String)

    var errorDescription: String? {
        switch self {
        case .notConnected:
            return "Wallet not connected"
        case .userRejected:
            return "User rejected the request"
        case .networkError(let message):
            return "Network error: \(message)"
        case .invalidSignature:
            return "Invalid signature returned"
        case .transactionFailed(let message):
            return "Transaction failed: \(message)"
        }
    }
}

Best Practices

  1. Validate inputs - Check addresses and data formats before processing
  2. Handle timeouts - Set reasonable timeouts for wallet operations
  3. User feedback - Show loading indicators during signing/transactions
  4. Error messages - Provide clear error messages for user-facing errors
  5. Thread safety - Ensure delegate methods can be called from any thread

Next Steps