Documentation Index Fetch the complete documentation index at: https://mintlify.com/xmtp/libxmtp/llms.txt
Use this file to discover all available pages before exploring further.
The Swift bindings provide native XMTP functionality for iOS and macOS applications using Mozilla’s UniFFI . These bindings expose the core LibXMTP Rust library to Swift through automatically generated FFI code.
These bindings are low-level FFI interfaces. For most iOS/macOS development, use the XMTPiOS SDK instead, which provides a Swift-native API built on top of these bindings.
Installation
The bindings are distributed as an XCFramework through Swift Package Manager:
Swift Package Manager
Add to your Package.swift:
dependencies : [
. package (
url : "https://github.com/xmtp/libxmtp" ,
from : "1.0.0"
)
]
Or in Xcode:
File → Add Package Dependencies
Enter repository URL: https://github.com/xmtp/libxmtp
Select version and add to your target
Requirements
iOS 14.0+ or macOS 11.0+
Swift 6.1+
Xcode 15.0+
Architecture
The Swift bindings use UniFFI to generate Swift code from Rust, providing memory-safe cross-language communication.
Key Technologies
UniFFI : Mozilla’s tool for generating foreign-language bindings from Rust
XCFramework : Pre-compiled binary framework containing native code for all Apple platforms
FFI (Foreign Function Interface) : Low-level interface between Swift and Rust
Tokio Integration : Rust async runtime with Swift async/await bridging
Binary Artifacts
The package includes two XCFramework variants:
LibXMTPSwiftFFI.xcframework - Statically linked framework (default)
Smaller app size when bundled
No runtime dependencies
Enabled by default trait
LibXMTPSwiftFFIDynamic.xcframework - Dynamically linked framework
Faster incremental builds
Shared library across app extensions
Enable with dynamic trait in Package.swift
Object Lifetimes
UniFFI manages object lifetimes using Arc<> pointers:
Objects crossing the FFI boundary are wrapped in Arc<>
Swift ARC automatically releases Rust objects when no longer referenced
No manual memory management required
Async and Concurrency
The bindings use Tokio’s multi-threaded runtime:
Swift async/await calls map to Rust async functions
Rust operations may resume on different threads after await
All exposed objects are Send + Sync for thread safety
No mutable references (&mut self) across FFI boundary
Basic Usage
Here’s a basic example of creating a client and sending messages:
import XMTPiOS
import LibXMTPSwiftFFI
// Generate encryption key for local database
let encryptionKey = try Crypto. secureRandomBytes ( count : 32 )
// Configure client options
let options = ClientOptions (
api : ClientOptions. Api (
env : . production ,
isSecure : true ,
appVersion : "MyApp/1.0.0"
),
dbEncryptionKey : encryptionKey
)
// Create a client with a wallet
let client = try await Client. create (
account : wallet,
options : options
)
// Check if registered
let isRegistered = client. isRegistered
print ( "Client registered: \( isRegistered ) " )
// Get inbox ID
let inboxID = client. inboxID
print ( "Inbox ID: \( inboxID ) " )
// Create a group conversation
let group = try await client. conversations . newGroup (
with : [ "0x1234..." , "0x5678..." ],
permissions : . allMembers
)
// Send a message
try await group. send ( content : "Hello, XMTP!" , options : nil )
// Stream new messages
for try await message in group. streamMessages () {
print ( "New message: \( message. content ) " )
}
Development
Prerequisites
For development, you need:
macOS with Xcode
Rust toolchain
Cross-compilation tools for iOS targets
The easiest way is using Nix:
# Enter iOS development shell
nix develop .#ios
Build Commands
Build XCFramework
Build Swift Package
Full Build
# Build LibXMTPSwiftFFI.xcframework
./sdks/ios/dev/bindings
# Or using just
just ios build
# Run all linting (SwiftLint + SwiftFormat)
just ios lint
# Format Swift code
just ios format
Configuration files:
.swiftlint.yml - SwiftLint configuration
.swiftformat - SwiftFormat rules
Testing
Running Tests
Tests require a running XMTP backend:
# Start local backend (from repo root)
just backend up
# Run tests
just ios test
Test Structure
Tests are located in sdks/ios/Tests/XMTPTests/:
ClientTests.swift - Client creation and management
ConversationTests.swift - Conversation operations
GroupTests.swift - Group messaging
DmTests.swift - Direct messages
CryptoTests.swift - Cryptographic operations
Example Test
import XCTest
@testable import XMTPiOS
import XMTPTestHelpers
@available ( iOS 15 , * )
class ClientTests : XCTestCase {
override func setUp () {
super . setUp ()
setupLocalEnv ()
}
func testTakesAWallet () async throws {
let key = try Crypto. secureRandomBytes ( count : 32 )
let clientOptions = ClientOptions (
api : ClientOptions. Api (
env : . local ,
isSecure : false ,
appVersion : "Testing/0.0.0"
),
dbEncryptionKey : key
)
let fakeWallet = try PrivateKey. generate ()
let client = try await Client. create (
account : fakeWallet,
options : clientOptions
)
try client. deleteLocalDatabase ()
}
func testStaticCanMessage () async throws {
let fixtures = try await fixtures ()
let canMessageList = try await Client. canMessage (
accountIdentities : [
fixtures. alix . identity ,
fixtures. bo . identity ,
],
api : ClientOptions. Api (
env : . local ,
isSecure : false
)
)
XCTAssertEqual (
canMessageList[fixtures. alix . walletAddress . lowercased ()],
true
)
XCTAssertEqual (
canMessageList[fixtures. bo . walletAddress . lowercased ()],
true
)
try fixtures. cleanUpDatabases ()
}
}
Package Structure
From Package.swift:
let package = Package (
name : "XMTPiOS" ,
platforms : [. iOS (. v14 ), . macOS (. v11 )],
products : [
. library (
name : "XMTPiOS" ,
targets : [ "XMTPiOS" ]
),
. library (
name : "XMTPTestHelpers" ,
targets : [ "XMTPTestHelpers" ]
),
],
traits : [
"static" ,
"dynamic" ,
. default ( enabledTraits : [ "static" ]),
],
dependencies : [
. package ( url : "https://github.com/bufbuild/connect-swift" , exact : "1.2.0" ),
. package ( url : "https://github.com/krzyzanowskim/CryptoSwift.git" , "1.8.4" ..< "2.0.0" ),
. package ( url : "https://github.com/apple/swift-docc-plugin.git" , from : "1.4.3" ),
. package ( url : "https://github.com/SimplyDanny/SwiftLintPlugins" , from : "0.62.1" ),
]
)
Key Dependencies
LibXMTPSwiftFFI - FFI bindings (local XCFramework)
Connect - gRPC client for Swift
CryptoSwift - Cryptographic utilities
swift-docc-plugin - Documentation generation
SwiftLintPlugins - Code linting
Advanced Patterns
Database Encryption
All local data is encrypted using a key you provide:
// Generate a secure random key
let encryptionKey = try Crypto. secureRandomBytes ( count : 32 )
// Store this key securely (Keychain recommended)
let keychain = Keychain ( service : "com.myapp.xmtp" )
try keychain. set (encryptionKey, key : "dbEncryptionKey" )
// Use the key when creating a client
let options = ClientOptions (
api : . init ( env : . production , isSecure : true ),
dbEncryptionKey : encryptionKey
)
Environment Configuration
// Production environment
let prodOptions = ClientOptions (
api : . init (
env : . production ,
isSecure : true ,
appVersion : "MyApp/1.0.0"
),
dbEncryptionKey : key
)
// Development environment
let devOptions = ClientOptions (
api : . init (
env : . dev ,
isSecure : true ,
appVersion : "MyApp/1.0.0-dev"
),
dbEncryptionKey : key
)
// Local testing
let localOptions = ClientOptions (
api : . init (
env : . local ,
isSecure : false ,
appVersion : "MyApp/Testing"
),
dbEncryptionKey : key
)
Inbox State Management
// Get current inbox state
let inboxState = try await client. inboxState ( refreshFromNetwork : true )
print ( "Inbox ID: \( inboxState. inboxId ) " )
print ( "Installations: \( inboxState. installations . count ) " )
print ( "Recovery address: \( inboxState. recoveryAddress ) " )
// Get inbox states for multiple inboxes
let inboxStates = try await Client. inboxStatesForInboxIds (
inboxIds : [inboxId1, inboxId2],
api : . init ( env : . production , isSecure : true )
)
Streaming Updates
// Stream all conversations
for try await conversation in client.conversations. stream () {
print ( "New conversation: \( conversation. id ) " )
}
// Stream messages in a conversation
for try await message in group. streamMessages () {
print ( "From: \( message. senderInboxId ) " )
print ( "Content: \( message. content ) " )
}
// Stream with callback
let stream = try await group. streamMessages { message in
Task {
await handleNewMessage (message)
}
}
Memory Management
UniFFI uses Arc for shared ownership between Swift and Rust
Swift ARC automatically deallocates objects
Large data transfers are zero-copy where possible
Threading
Tokio runtime uses multiple threads for Rust operations
Swift async/await integrates seamlessly
All callbacks are Send + Sync safe
SQLite with encryption (SQLCipher)
Write-ahead logging (WAL) enabled
Connection pooling for concurrent access
Troubleshooting
Framework Not Found
If you see “Framework not found LibXMTPSwiftFFI”:
# Rebuild the xcframework
./sdks/ios/dev/bindings
Database Errors
If you encounter database errors:
// Delete local database
try client. deleteLocalDatabase ()
// Create a new client
let newClient = try await Client. create (
account : wallet,
options : options
)
Async Context Required
Many methods require async context:
// ❌ Wrong - no async context
let client = Client. create ( account : wallet, options : options)
// ✅ Correct - in async function
func setupClient () async throws {
let client = try await Client. create (
account : wallet,
options : options
)
}
// ✅ Correct - with Task
Task {
let client = try await Client. create (
account : wallet,
options : options
)
}
Resources
UniFFI Documentation Learn about the UniFFI framework
Source Code View the bindings source code
XMTPiOS SDK Use the high-level Swift SDK
Example Tests See real usage examples