A example Rust library for converting between Stacks (STX) and Bitcoin (BTC) native SegWit addresses.
This library is only a proof concept example. Do not use in production.
- Convert Stacks addresses to Bitcoin native SegWit addresses (P2WPKH)
- Convert Bitcoin native SegWit addresses back to Stacks addresses (P2WPKH only)
- Maintains the same underlying HASH160 between address formats
- Only supports mainnet addresses
use stx2btc::{stx2btc, btc2stx};
let stx_address = "SP2V0G568F20Q1XCRT43XX8Q32V2DPMMYFHHBD8PP";
let btc_address = stx2btc(stx_address).unwrap();
println!("{}", btc_address); // bc1qkcypfjrcs9c0txx3ql029cckcnd498nuvl6wpy
let back_to_stx = btc2stx(&btc_address).unwrap();
println!("{}", back_to_stx); // SP2V0G568F20Q1XCRT43XX8Q32V2DPMMYFHHBD8PP
This library is distributed as a Swift Package with both source code and pre-built binary components.
Add this package to your Xcode project:
- In Xcode: File → Add Package Dependencies
- Enter the repository URL:
https://github.com/newinternetlabs/stx2btc
- Select version rule (e.g., "Up to Next Major Version")
- Add the package to your target
Then in your Swift code:
import stx2btc
The package includes:
- Pre-built XCFramework binary for iOS (device & simulator)
- Swift source bindings
- Automatic linking configuration
For development or if you need to rebuild the XCFramework:
# Using just
just xcframework
# Or directly
./build-xcframework.sh
This creates an XCFramework at target/xcframework/stx2btc.xcframework
containing:
- iOS device binary (arm64)
- iOS Simulator binary (arm64 for Apple Silicon)
- Headers and module map
- Swift bindings file
To use the XCFramework in your Xcode project:
- Drag
target/xcframework/stx2btc.xcframework
into your Xcode project - Add
target/xcframework/stx2btc.swift
to your project - In your target's build settings, add to "Other Linker Flags":
-lc++ -lresolv
- Import and use as shown in the Swift usage examples below
For contributors: When modifying the Rust library, ensure Swift bindings stay in sync:
# Development workflow: clean, test, and update Swift bindings
just dev
# Quick check: validate Swift bindings and run tests
just check
# Create a new release (builds everything and publishes to GitHub)
just publish v1.0.0
# Full local build workflow (clean, test, build everything)
just release
Note: Swift bindings in Sources/stx2btc/
are committed to git and must be kept in sync with the Rust library.
Using cargo aliases:
cargo build-libs && cargo swift-bindings
Or using just (install with cargo install just
):
just swift
# Build the library (creates both libstx2btc.dylib and libstx2btc.a)
cargo build --release
# Generate Swift bindings (uses .dylib for introspection, but bindings work with both .dylib and .a)
cargo run --bin uniffi-bindgen generate --library target/release/libstx2btc.dylib --language swift --out-dir bindings --no-format
This will create the bindings/
directory with the Swift files.
Note: The binding generation uses the .dylib
file to introspect the interface, but the generated Swift bindings work with both the dynamic library (.dylib
) and static library (.a
) - you choose which to link at build time in Xcode.
The generated Swift bindings are located in the bindings/
directory. You can integrate them into your iOS/macOS project:
Works on both macOS and iOS:
- Copy
stx2btc.swift
,stx2btcFFI.h
, andstx2btcFFI.modulemap
to your Xcode project - Copy the
libstx2btc.a
static library to your project - Link the static library in your Xcode project settings
- Use the functions in your Swift code
For macOS development/testing when you want smaller binaries:
- Copy
stx2btc.swift
,stx2btcFFI.h
, andstx2btcFFI.modulemap
to your Xcode project - Copy the
libstx2btc.dylib
library to your project - Use the functions in your Swift code
Note: iOS requires the static library (.a
) for App Store distribution, but macOS can use either.
import Foundation
do {
let btcAddress = try stx2btc(stxAddress: "SP2V0G568F20Q1XCRT43XX8Q32V2DPMMYFHHBD8PP")
print(btcAddress) // bc1qkcypfjrcs9c0txx3ql029cckcnd498nuvl6wpy
let backToStx = try btc2stx(btcAddress: btcAddress)
print(backToStx) // SP2V0G568F20Q1XCRT43XX8Q32V2DPMMYFHHBD8PP
} catch {
print("Conversion error: \(error)")
}
The Swift bindings include proper error handling with the ConversionError
enum:
enum ConversionError: Error {
case SegwitDecode(String)
case UnsupportedVersion
case SegwitEncode(String)
}
UniFFI supports generating bindings for additional languages including kotlin, python and ruby. These are available but untested:
# Using cargo aliases
cargo kotlin-bindings # Kotlin/Android
cargo python-bindings # Python
cargo ruby-bindings # Ruby
# Using just
just kotlin # Kotlin/Android
just python # Python
just ruby # Ruby
just all # All languages (Swift, Kotlin, Python, Ruby)
See the UniFFI documentation for more details on supported languages and usage.