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
- Validate inputs - Check addresses and data formats before processing
- Handle timeouts - Set reasonable timeouts for wallet operations
- User feedback - Show loading indicators during signing/transactions
- Error messages - Provide clear error messages for user-facing errors
- Thread safety - Ensure delegate methods can be called from any thread
Next Steps