commit 38403e00b4c786cd6d866827a26934d99d9a574c Author: Steve Myers Date: Mon Nov 22 14:17:03 2021 -0800 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b18ca41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +/.build +/.swiftpm +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +/*.xcframework +!bdkFFI-umbrella.h +!module.modulemap +!info.plist +*.xcframework.zip diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9c7f2ac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "bdk-ffi"] + path = bdk-ffi + url = git@github.com:bitcoindevkit/bdk-ffi.git diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..485d25e --- /dev/null +++ b/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version:5.5 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "bdk-swift", + platforms: [ + .macOS(.v12), + .iOS(.v15) + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "BitcoinDevKit", + targets: ["bdkFFI", "BitcoinDevKit"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .binaryTarget( + name: "bdkFFI", + url: "https://github.com/notmandatory/bdk-swift/releases/download/0.1.0/bdkFFI.xcframework.zip", + checksum: "b34dc1dea2e53bc894f1ad61269e45de6c77dd6391bbb1318cfb0be17435c4db"), + .target( + name: "BitcoinDevKit", + dependencies: ["bdkFFI"]), + .testTarget( + name: "BitcoinDevKitTests", + dependencies: ["BitcoinDevKit"]), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c49608d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# bdk-swift + +A description of this package. diff --git a/Sources/BitcoinDevKit/BitcoinDevKit.swift b/Sources/BitcoinDevKit/BitcoinDevKit.swift new file mode 100644 index 0000000..1c9432e --- /dev/null +++ b/Sources/BitcoinDevKit/BitcoinDevKit.swift @@ -0,0 +1,2113 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(bdkFFI) +import bdkFFI +#endif + +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_bdk_2e4d_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_bdk_2e4d_rustbuffer_free(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a libray of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// A helper class to read values out of a byte buffer. +fileprivate class Reader { + let data: Data + var offset: Data.Index + + init(data: Data) { + self.data = data + self.offset = 0 + } + + // Reads an integer at the current offset, in big-endian order, and advances + // the offset on success. Throws if reading the integer would move the + // offset past the end of the buffer. + func readInt() throws -> T { + let range = offset...size + guard data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = data[offset] + offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0, from: range)}) + offset = range.upperBound + return value.bigEndian + } + + // Reads an arbitrary number of bytes, to be used to read + // raw bytes, this is useful when lifting strings + func readBytes(count: Int) throws -> Array { + let range = offset..<(offset+count) + guard data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + data.copyBytes(to: buffer, from: range) + }) + offset = range.upperBound + return value + } + + // Reads a float at the current offset. + @inlinable + func readFloat() throws -> Float { + return Float(bitPattern: try readInt()) + } + + // Reads a float at the current offset. + @inlinable + func readDouble() throws -> Double { + return Double(bitPattern: try readInt()) + } + + // Indicates if the offset has reached the end of the buffer. + @inlinable + func hasRemaining() -> Bool { + return offset < data.count + } +} + +// A helper class to write values into a byte buffer. +fileprivate class Writer { + var bytes: [UInt8] + var offset: Array.Index + + init() { + self.bytes = [] + self.offset = 0 + } + + func writeBytes(_ byteArr: S) where S: Sequence, S.Element == UInt8 { + bytes.append(contentsOf: byteArr) + } + + // Writes an integer in big-endian order. + // + // Warning: make sure what you are trying to write + // is in the correct type! + func writeInt(_ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { bytes.append(contentsOf: $0) } + } + + @inlinable + func writeFloat(_ value: Float) { + writeInt(value.bitPattern) + } + + @inlinable + func writeDouble(_ value: Double) { + writeInt(value.bitPattern) + } +} + + +// Types conforming to `Serializable` can be read and written in a bytebuffer. +fileprivate protocol Serializable { + func write(into: Writer) + static func read(from: Reader) throws -> Self +} + +// Types confirming to `ViaFfi` can be transferred back-and-for over the FFI. +// This is analogous to the Rust trait of the same name. +fileprivate protocol ViaFfi: Serializable { + associatedtype FfiType + static func lift(_ v: FfiType) throws -> Self + func lower() -> FfiType +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol Primitive {} + +extension Primitive { + fileprivate typealias FfiType = Self + + fileprivate static func lift(_ v: Self) throws -> Self { + return v + } + + fileprivate func lower() -> Self { + return self + } +} + +// Types conforming to `ViaFfiUsingByteBuffer` lift and lower into a bytebuffer. +// Use this for complex types where it's hard to write a custom lift/lower. +fileprivate protocol ViaFfiUsingByteBuffer: Serializable {} + +extension ViaFfiUsingByteBuffer { + fileprivate typealias FfiType = RustBuffer + + fileprivate static func lift(_ buf: FfiType) throws -> Self { + let reader = Reader(data: Data(rustBuffer: buf)) + let value = try Self.read(from: reader) + if reader.hasRemaining() { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + fileprivate func lower() -> FfiType { + let writer = Writer() + self.write(into: writer) + return RustBuffer(bytes: writer.bytes) + } +} +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_PANIC: Int8 = 2 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: { + $0.deallocate() + return UniffiInternalError.unexpectedRustCallError + }) +} + +private func rustCallWithError(_ errorClass: E.Type, _ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: { return try E.lift($0) }) +} + +private func makeRustCall(_ callback: (UnsafeMutablePointer) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T { + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + switch callStatus.code { + case CALL_SUCCESS: + return returnedVal + + case CALL_ERROR: + throw try errorHandler(callStatus.errorBuf) + + case CALL_PANIC: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try String.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} +// Protocols for converters we'll implement in templates + +fileprivate protocol FfiConverter { + associatedtype SwiftType + associatedtype FfiType + + static func lift(_ ffiValue: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + + static func read(from: Reader) throws -> SwiftType + static func write(_ value: SwiftType, into: Writer) +} + +fileprivate protocol FfiConverterUsingByteBuffer: FfiConverter where FfiType == RustBuffer { + // Empty, because we want to declare some helper methods in the extension below. +} + +extension FfiConverterUsingByteBuffer { + static func lower(_ value: SwiftType) -> FfiType { + let writer = Writer() + Self.write(value, into: writer) + return RustBuffer(bytes: writer.bytes) + } + + static func lift(_ buf: FfiType) throws -> SwiftType { + let reader = Reader(data: Data(rustBuffer: buf)) + let value = try Self.read(from: reader) + if reader.hasRemaining() { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } +} + +// Helpers for structural types. Note that because of canonical_names, it /should/ be impossible +// to make another `FfiConverterSequence` etc just using the UDL. +fileprivate enum FfiConverterSequence { + static func write(_ value: [T], into buf: Writer, writeItem: (T, Writer) -> Void) { + let len = Int32(value.count) + buf.writeInt(len) + for item in value { + writeItem(item, buf) + } + } + + static func read(from buf: Reader, readItem: (Reader) throws -> T) throws -> [T] { + let len: Int32 = try buf.readInt() + var seq = [T]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try readItem(buf)) + } + return seq + } +} + +fileprivate enum FfiConverterOptional { + static func write(_ value: T?, into buf: Writer, writeItem: (T, Writer) -> Void) { + guard let value = value else { + buf.writeInt(Int8(0)) + return + } + buf.writeInt(Int8(1)) + writeItem(value, buf) + } + + static func read(from buf: Reader, readItem: (Reader) throws -> T) throws -> T? { + switch try buf.readInt() as Int8 { + case 0: return nil + case 1: return try readItem(buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate enum FfiConverterDictionary { + static func write(_ value: [String: T], into buf: Writer, writeItem: (String, T, Writer) -> Void) { + let len = Int32(value.count) + buf.writeInt(len) + for (key, value) in value { + writeItem(key, value, buf) + } + } + + static func read(from buf: Reader, readItem: (Reader) throws -> (String, T)) throws -> [String: T] { + let len: Int32 = try buf.readInt() + var dict = [String: T]() + dict.reserveCapacity(Int(len)) + for _ in 0..(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate typealias Handle = UInt64 +fileprivate class ConcurrentHandleMap { + private var leftMap: [Handle: T] = [:] + private var counter: [Handle: UInt64] = [:] + private var rightMap: [ObjectIdentifier: Handle] = [:] + + private let lock = NSLock() + private var currentHandle: Handle = 0 + private let stride: Handle = 1 + + func insert(obj: T) -> Handle { + lock.withLock { + let id = ObjectIdentifier(obj as AnyObject) + let handle = rightMap[id] ?? { + currentHandle += stride + let handle = currentHandle + leftMap[handle] = obj + rightMap[id] = handle + return handle + }() + counter[handle] = (counter[handle] ?? 0) + 1 + return handle + } + } + + func get(handle: Handle) -> T? { + lock.withLock { + leftMap[handle] + } + } + + func delete(handle: Handle) { + remove(handle: handle) + } + + @discardableResult + func remove(handle: Handle) -> T? { + lock.withLock { + defer { counter[handle] = (counter[handle] ?? 1) - 1 } + guard counter[handle] == 1 else { return leftMap[handle] } + let obj = leftMap.removeValue(forKey: handle) + if let obj = obj { + rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject)) + } + return obj + } + } +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +private let IDX_CALLBACK_FREE: Int32 = 0 + +fileprivate class FfiConverterCallbackInterface { + fileprivate let handleMap = ConcurrentHandleMap() + + func drop(handle: Handle) { + handleMap.remove(handle: handle) + } + + func lift(_ handle: Handle) throws -> CallbackInterface { + guard let callback = handleMap.get(handle: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return callback + } + + func read(from buf: Reader) throws -> CallbackInterface { + let handle: Handle = try buf.readInt() + return try lift(handle) + } + + func lower(_ v: CallbackInterface) -> Handle { + let handle = handleMap.insert(obj: v) + return handle + // assert(handleMap.get(handle: obj) == v, "Handle map is not returning the object we just placed there. This is a bug in the HandleMap.") + } + + func write(_ v: CallbackInterface, into buf: Writer) { + buf.writeInt(lower(v)) + } +} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum Network { + + case bitcoin + case testnet + case signet + case regtest +} + +extension Network: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> Network { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .bitcoin + case 2: return .testnet + case 3: return .signet + case 4: return .regtest + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .bitcoin: + buf.writeInt(Int32(1)) + + + case .testnet: + buf.writeInt(Int32(2)) + + + case .signet: + buf.writeInt(Int32(3)) + + + case .regtest: + buf.writeInt(Int32(4)) + + } + } +} + + +extension Network: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum DatabaseConfig { + + case memory(junk: String ) + case sled(config: SledDbConfiguration ) +} + +extension DatabaseConfig: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> DatabaseConfig { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .memory( + junk: try String.read(from: buf) + ) + case 2: return .sled( + config: try SledDbConfiguration.read(from: buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case let .memory(junk): + buf.writeInt(Int32(1)) + junk.write(into: buf) + + + + case let .sled(config): + buf.writeInt(Int32(2)) + config.write(into: buf) + + + } + } +} + + +extension DatabaseConfig: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum Transaction { + + case unconfirmed(details: TransactionDetails ) + case confirmed(details: TransactionDetails, confirmation: Confirmation ) +} + +extension Transaction: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> Transaction { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .unconfirmed( + details: try TransactionDetails.read(from: buf) + ) + case 2: return .confirmed( + details: try TransactionDetails.read(from: buf), + confirmation: try Confirmation.read(from: buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case let .unconfirmed(details): + buf.writeInt(Int32(1)) + details.write(into: buf) + + + + case let .confirmed(details,confirmation): + buf.writeInt(Int32(2)) + details.write(into: buf) + confirmation.write(into: buf) + + + } + } +} + + +extension Transaction: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum BlockchainConfig { + + case electrum(config: ElectrumConfig ) + case esplora(config: EsploraConfig ) +} + +extension BlockchainConfig: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> BlockchainConfig { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .electrum( + config: try ElectrumConfig.read(from: buf) + ) + case 2: return .esplora( + config: try EsploraConfig.read(from: buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case let .electrum(config): + buf.writeInt(Int32(1)) + config.write(into: buf) + + + + case let .esplora(config): + buf.writeInt(Int32(2)) + config.write(into: buf) + + + } + } +} + + +extension BlockchainConfig: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum MnemonicType { + + case words12 + case words15 + case words18 + case words21 + case words24 +} + +extension MnemonicType: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> MnemonicType { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .words12 + case 2: return .words15 + case 3: return .words18 + case 4: return .words21 + case 5: return .words24 + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .words12: + buf.writeInt(Int32(1)) + + + case .words15: + buf.writeInt(Int32(2)) + + + case .words18: + buf.writeInt(Int32(3)) + + + case .words21: + buf.writeInt(Int32(4)) + + + case .words24: + buf.writeInt(Int32(5)) + + } + } +} + + +extension MnemonicType: Equatable, Hashable {} + + + +public func generateExtendedKey(network: Network, mnemonicType: MnemonicType, password: String? ) throws -> ExtendedKeyInfo { + let _retval = try + + + rustCallWithError(BdkError.self) { + + bdk_2e4d_generate_extended_key(network.lower(), mnemonicType.lower(), FfiConverterOptionString.lower(password) , $0) +} + return try ExtendedKeyInfo.lift(_retval) +} + + + +public func restoreExtendedKey(network: Network, mnemonic: String, password: String? ) throws -> ExtendedKeyInfo { + let _retval = try + + + rustCallWithError(BdkError.self) { + + bdk_2e4d_restore_extended_key(network.lower(), mnemonic.lower(), FfiConverterOptionString.lower(password) , $0) +} + return try ExtendedKeyInfo.lift(_retval) +} + + + +public protocol OfflineWalletProtocol { + func getNewAddress() -> String + func getLastUnusedAddress() -> String + func getBalance() throws -> UInt64 + func sign(psbt: PartiallySignedBitcoinTransaction ) throws + func getTransactions() throws -> [Transaction] + +} + +public class OfflineWallet: OfflineWalletProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) throws { + self.init(unsafeFromRawPointer: try + + + rustCallWithError(BdkError.self) { + + bdk_2e4d_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() , $0) +}) + } + + deinit { + try! rustCall { ffi_bdk_2e4d_OfflineWallet_object_free(pointer, $0) } + } + + + + + public func getNewAddress() -> String { + let _retval = try! + rustCall() { + + bdk_2e4d_OfflineWallet_get_new_address(self.pointer, $0 + ) +} + return try! String.lift(_retval) + } + public func getLastUnusedAddress() -> String { + let _retval = try! + rustCall() { + + bdk_2e4d_OfflineWallet_get_last_unused_address(self.pointer, $0 + ) +} + return try! String.lift(_retval) + } + public func getBalance() throws -> UInt64 { + let _retval = try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OfflineWallet_get_balance(self.pointer, $0 + ) +} + return try UInt64.lift(_retval) + } + public func sign(psbt: PartiallySignedBitcoinTransaction ) throws { + try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OfflineWallet_sign(self.pointer, psbt.lower() , $0 + ) +} + } + public func getTransactions() throws -> [Transaction] { + let _retval = try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OfflineWallet_get_transactions(self.pointer, $0 + ) +} + return try FfiConverterSequenceEnumTransaction.lift(_retval) + } + +} + + +fileprivate extension OfflineWallet { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension OfflineWallet : ViaFfi, Serializable {} + + +public protocol OnlineWalletProtocol { + func getNewAddress() -> String + func getLastUnusedAddress() -> String + func getBalance() throws -> UInt64 + func sign(psbt: PartiallySignedBitcoinTransaction ) throws + func getTransactions() throws -> [Transaction] + func getNetwork() -> Network + func sync(progressUpdate: BdkProgress, maxAddressParam: UInt32? ) throws + func broadcast(psbt: PartiallySignedBitcoinTransaction ) throws -> Transaction + +} + +public class OnlineWallet: OnlineWalletProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(descriptor: String, changeDescriptor: String?, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) throws { + self.init(unsafeFromRawPointer: try + + + rustCallWithError(BdkError.self) { + + bdk_2e4d_OnlineWallet_new(descriptor.lower(), FfiConverterOptionString.lower(changeDescriptor), network.lower(), databaseConfig.lower(), blockchainConfig.lower() , $0) +}) + } + + deinit { + try! rustCall { ffi_bdk_2e4d_OnlineWallet_object_free(pointer, $0) } + } + + + + + public func getNewAddress() -> String { + let _retval = try! + rustCall() { + + bdk_2e4d_OnlineWallet_get_new_address(self.pointer, $0 + ) +} + return try! String.lift(_retval) + } + public func getLastUnusedAddress() -> String { + let _retval = try! + rustCall() { + + bdk_2e4d_OnlineWallet_get_last_unused_address(self.pointer, $0 + ) +} + return try! String.lift(_retval) + } + public func getBalance() throws -> UInt64 { + let _retval = try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OnlineWallet_get_balance(self.pointer, $0 + ) +} + return try UInt64.lift(_retval) + } + public func sign(psbt: PartiallySignedBitcoinTransaction ) throws { + try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OnlineWallet_sign(self.pointer, psbt.lower() , $0 + ) +} + } + public func getTransactions() throws -> [Transaction] { + let _retval = try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OnlineWallet_get_transactions(self.pointer, $0 + ) +} + return try FfiConverterSequenceEnumTransaction.lift(_retval) + } + public func getNetwork() -> Network { + let _retval = try! + rustCall() { + + bdk_2e4d_OnlineWallet_get_network(self.pointer, $0 + ) +} + return try! Network.lift(_retval) + } + public func sync(progressUpdate: BdkProgress, maxAddressParam: UInt32? ) throws { + try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OnlineWallet_sync(self.pointer, ffiConverterCallbackInterfaceBdkProgress.lower(progressUpdate), FfiConverterOptionUInt32.lower(maxAddressParam) , $0 + ) +} + } + public func broadcast(psbt: PartiallySignedBitcoinTransaction ) throws -> Transaction { + let _retval = try + rustCallWithError(BdkError.self) { + + bdk_2e4d_OnlineWallet_broadcast(self.pointer, psbt.lower() , $0 + ) +} + return try Transaction.lift(_retval) + } + +} + + +fileprivate extension OnlineWallet { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension OnlineWallet : ViaFfi, Serializable {} + + +public protocol PartiallySignedBitcoinTransactionProtocol { + +} + +public class PartiallySignedBitcoinTransaction: PartiallySignedBitcoinTransactionProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(wallet: OnlineWallet, recipient: String, amount: UInt64, feeRate: Float? ) throws { + self.init(unsafeFromRawPointer: try + + + rustCallWithError(BdkError.self) { + + bdk_2e4d_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower(), FfiConverterOptionFloat.lower(feeRate) , $0) +}) + } + + deinit { + try! rustCall { ffi_bdk_2e4d_PartiallySignedBitcoinTransaction_object_free(pointer, $0) } + } + + + + + +} + + +fileprivate extension PartiallySignedBitcoinTransaction { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension PartiallySignedBitcoinTransaction : ViaFfi, Serializable {} + +public struct SledDbConfiguration { + public var path: String + public var treeName: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(path: String, treeName: String ) { + self.path = path + self.treeName = treeName + } +} + + +extension SledDbConfiguration: Equatable, Hashable { + public static func ==(lhs: SledDbConfiguration, rhs: SledDbConfiguration) -> Bool { + if lhs.path != rhs.path { + return false + } + if lhs.treeName != rhs.treeName { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(path) + hasher.combine(treeName) + } +} + + +fileprivate extension SledDbConfiguration { + static func read(from buf: Reader) throws -> SledDbConfiguration { + return try SledDbConfiguration( + path: String.read(from: buf), + treeName: String.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.path.write(into: buf) + self.treeName.write(into: buf) + } +} + +extension SledDbConfiguration: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct TransactionDetails { + public var fees: UInt64? + public var received: UInt64 + public var sent: UInt64 + public var txid: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(fees: UInt64?, received: UInt64, sent: UInt64, txid: String ) { + self.fees = fees + self.received = received + self.sent = sent + self.txid = txid + } +} + + +extension TransactionDetails: Equatable, Hashable { + public static func ==(lhs: TransactionDetails, rhs: TransactionDetails) -> Bool { + if lhs.fees != rhs.fees { + return false + } + if lhs.received != rhs.received { + return false + } + if lhs.sent != rhs.sent { + return false + } + if lhs.txid != rhs.txid { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(fees) + hasher.combine(received) + hasher.combine(sent) + hasher.combine(txid) + } +} + + +fileprivate extension TransactionDetails { + static func read(from buf: Reader) throws -> TransactionDetails { + return try TransactionDetails( + fees: FfiConverterOptionUInt64.read(from: buf), + received: UInt64.read(from: buf), + sent: UInt64.read(from: buf), + txid: String.read(from: buf) + ) + } + + func write(into buf: Writer) { + FfiConverterOptionUInt64.write(self.fees, into: buf) + self.received.write(into: buf) + self.sent.write(into: buf) + self.txid.write(into: buf) + } +} + +extension TransactionDetails: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct Confirmation { + public var height: UInt32 + public var timestamp: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(height: UInt32, timestamp: UInt64 ) { + self.height = height + self.timestamp = timestamp + } +} + + +extension Confirmation: Equatable, Hashable { + public static func ==(lhs: Confirmation, rhs: Confirmation) -> Bool { + if lhs.height != rhs.height { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(height) + hasher.combine(timestamp) + } +} + + +fileprivate extension Confirmation { + static func read(from buf: Reader) throws -> Confirmation { + return try Confirmation( + height: UInt32.read(from: buf), + timestamp: UInt64.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.height.write(into: buf) + self.timestamp.write(into: buf) + } +} + +extension Confirmation: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct ElectrumConfig { + public var url: String + public var socks5: String? + public var retry: UInt8 + public var timeout: UInt8? + public var stopGap: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(url: String, socks5: String?, retry: UInt8, timeout: UInt8?, stopGap: UInt64 ) { + self.url = url + self.socks5 = socks5 + self.retry = retry + self.timeout = timeout + self.stopGap = stopGap + } +} + + +extension ElectrumConfig: Equatable, Hashable { + public static func ==(lhs: ElectrumConfig, rhs: ElectrumConfig) -> Bool { + if lhs.url != rhs.url { + return false + } + if lhs.socks5 != rhs.socks5 { + return false + } + if lhs.retry != rhs.retry { + return false + } + if lhs.timeout != rhs.timeout { + return false + } + if lhs.stopGap != rhs.stopGap { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(url) + hasher.combine(socks5) + hasher.combine(retry) + hasher.combine(timeout) + hasher.combine(stopGap) + } +} + + +fileprivate extension ElectrumConfig { + static func read(from buf: Reader) throws -> ElectrumConfig { + return try ElectrumConfig( + url: String.read(from: buf), + socks5: FfiConverterOptionString.read(from: buf), + retry: UInt8.read(from: buf), + timeout: FfiConverterOptionUInt8.read(from: buf), + stopGap: UInt64.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.url.write(into: buf) + FfiConverterOptionString.write(self.socks5, into: buf) + self.retry.write(into: buf) + FfiConverterOptionUInt8.write(self.timeout, into: buf) + self.stopGap.write(into: buf) + } +} + +extension ElectrumConfig: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct EsploraConfig { + public var baseUrl: String + public var proxy: String? + public var timeoutRead: UInt64 + public var timeoutWrite: UInt64 + public var stopGap: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(baseUrl: String, proxy: String?, timeoutRead: UInt64, timeoutWrite: UInt64, stopGap: UInt64 ) { + self.baseUrl = baseUrl + self.proxy = proxy + self.timeoutRead = timeoutRead + self.timeoutWrite = timeoutWrite + self.stopGap = stopGap + } +} + + +extension EsploraConfig: Equatable, Hashable { + public static func ==(lhs: EsploraConfig, rhs: EsploraConfig) -> Bool { + if lhs.baseUrl != rhs.baseUrl { + return false + } + if lhs.proxy != rhs.proxy { + return false + } + if lhs.timeoutRead != rhs.timeoutRead { + return false + } + if lhs.timeoutWrite != rhs.timeoutWrite { + return false + } + if lhs.stopGap != rhs.stopGap { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(baseUrl) + hasher.combine(proxy) + hasher.combine(timeoutRead) + hasher.combine(timeoutWrite) + hasher.combine(stopGap) + } +} + + +fileprivate extension EsploraConfig { + static func read(from buf: Reader) throws -> EsploraConfig { + return try EsploraConfig( + baseUrl: String.read(from: buf), + proxy: FfiConverterOptionString.read(from: buf), + timeoutRead: UInt64.read(from: buf), + timeoutWrite: UInt64.read(from: buf), + stopGap: UInt64.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.baseUrl.write(into: buf) + FfiConverterOptionString.write(self.proxy, into: buf) + self.timeoutRead.write(into: buf) + self.timeoutWrite.write(into: buf) + self.stopGap.write(into: buf) + } +} + +extension EsploraConfig: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct ExtendedKeyInfo { + public var mnemonic: String + public var xprv: String + public var fingerprint: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(mnemonic: String, xprv: String, fingerprint: String ) { + self.mnemonic = mnemonic + self.xprv = xprv + self.fingerprint = fingerprint + } +} + + +extension ExtendedKeyInfo: Equatable, Hashable { + public static func ==(lhs: ExtendedKeyInfo, rhs: ExtendedKeyInfo) -> Bool { + if lhs.mnemonic != rhs.mnemonic { + return false + } + if lhs.xprv != rhs.xprv { + return false + } + if lhs.fingerprint != rhs.fingerprint { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(mnemonic) + hasher.combine(xprv) + hasher.combine(fingerprint) + } +} + + +fileprivate extension ExtendedKeyInfo { + static func read(from buf: Reader) throws -> ExtendedKeyInfo { + return try ExtendedKeyInfo( + mnemonic: String.read(from: buf), + xprv: String.read(from: buf), + fingerprint: String.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.mnemonic.write(into: buf) + self.xprv.write(into: buf) + self.fingerprint.write(into: buf) + } +} + +extension ExtendedKeyInfo: ViaFfiUsingByteBuffer, ViaFfi {} + +public enum BdkError { + + + + // Simple error enums only carry a message + case InvalidU32Bytes(message: String) + + // Simple error enums only carry a message + case Generic(message: String) + + // Simple error enums only carry a message + case ScriptDoesntHaveAddressForm(message: String) + + // Simple error enums only carry a message + case NoRecipients(message: String) + + // Simple error enums only carry a message + case NoUtxosSelected(message: String) + + // Simple error enums only carry a message + case OutputBelowDustLimit(message: String) + + // Simple error enums only carry a message + case InsufficientFunds(message: String) + + // Simple error enums only carry a message + case BnBTotalTriesExceeded(message: String) + + // Simple error enums only carry a message + case BnBNoExactMatch(message: String) + + // Simple error enums only carry a message + case UnknownUtxo(message: String) + + // Simple error enums only carry a message + case TransactionNotFound(message: String) + + // Simple error enums only carry a message + case TransactionConfirmed(message: String) + + // Simple error enums only carry a message + case IrreplaceableTransaction(message: String) + + // Simple error enums only carry a message + case FeeRateTooLow(message: String) + + // Simple error enums only carry a message + case FeeTooLow(message: String) + + // Simple error enums only carry a message + case FeeRateUnavailable(message: String) + + // Simple error enums only carry a message + case MissingKeyOrigin(message: String) + + // Simple error enums only carry a message + case Key(message: String) + + // Simple error enums only carry a message + case ChecksumMismatch(message: String) + + // Simple error enums only carry a message + case SpendingPolicyRequired(message: String) + + // Simple error enums only carry a message + case InvalidPolicyPathError(message: String) + + // Simple error enums only carry a message + case Signer(message: String) + + // Simple error enums only carry a message + case InvalidNetwork(message: String) + + // Simple error enums only carry a message + case InvalidProgressValue(message: String) + + // Simple error enums only carry a message + case ProgressUpdateError(message: String) + + // Simple error enums only carry a message + case InvalidOutpoint(message: String) + + // Simple error enums only carry a message + case Descriptor(message: String) + + // Simple error enums only carry a message + case AddressValidator(message: String) + + // Simple error enums only carry a message + case Encode(message: String) + + // Simple error enums only carry a message + case Miniscript(message: String) + + // Simple error enums only carry a message + case Bip32(message: String) + + // Simple error enums only carry a message + case Secp256k1(message: String) + + // Simple error enums only carry a message + case Json(message: String) + + // Simple error enums only carry a message + case Hex(message: String) + + // Simple error enums only carry a message + case Psbt(message: String) + + // Simple error enums only carry a message + case PsbtParse(message: String) + + // Simple error enums only carry a message + case Electrum(message: String) + + // Simple error enums only carry a message + case Esplora(message: String) + + // Simple error enums only carry a message + case Sled(message: String) + +} + +extension BdkError: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> BdkError { + let variant: Int32 = try buf.readInt() + switch variant { + + + + + case 1: return .InvalidU32Bytes( + message: try String.read(from: buf) + ) + + case 2: return .Generic( + message: try String.read(from: buf) + ) + + case 3: return .ScriptDoesntHaveAddressForm( + message: try String.read(from: buf) + ) + + case 4: return .NoRecipients( + message: try String.read(from: buf) + ) + + case 5: return .NoUtxosSelected( + message: try String.read(from: buf) + ) + + case 6: return .OutputBelowDustLimit( + message: try String.read(from: buf) + ) + + case 7: return .InsufficientFunds( + message: try String.read(from: buf) + ) + + case 8: return .BnBTotalTriesExceeded( + message: try String.read(from: buf) + ) + + case 9: return .BnBNoExactMatch( + message: try String.read(from: buf) + ) + + case 10: return .UnknownUtxo( + message: try String.read(from: buf) + ) + + case 11: return .TransactionNotFound( + message: try String.read(from: buf) + ) + + case 12: return .TransactionConfirmed( + message: try String.read(from: buf) + ) + + case 13: return .IrreplaceableTransaction( + message: try String.read(from: buf) + ) + + case 14: return .FeeRateTooLow( + message: try String.read(from: buf) + ) + + case 15: return .FeeTooLow( + message: try String.read(from: buf) + ) + + case 16: return .FeeRateUnavailable( + message: try String.read(from: buf) + ) + + case 17: return .MissingKeyOrigin( + message: try String.read(from: buf) + ) + + case 18: return .Key( + message: try String.read(from: buf) + ) + + case 19: return .ChecksumMismatch( + message: try String.read(from: buf) + ) + + case 20: return .SpendingPolicyRequired( + message: try String.read(from: buf) + ) + + case 21: return .InvalidPolicyPathError( + message: try String.read(from: buf) + ) + + case 22: return .Signer( + message: try String.read(from: buf) + ) + + case 23: return .InvalidNetwork( + message: try String.read(from: buf) + ) + + case 24: return .InvalidProgressValue( + message: try String.read(from: buf) + ) + + case 25: return .ProgressUpdateError( + message: try String.read(from: buf) + ) + + case 26: return .InvalidOutpoint( + message: try String.read(from: buf) + ) + + case 27: return .Descriptor( + message: try String.read(from: buf) + ) + + case 28: return .AddressValidator( + message: try String.read(from: buf) + ) + + case 29: return .Encode( + message: try String.read(from: buf) + ) + + case 30: return .Miniscript( + message: try String.read(from: buf) + ) + + case 31: return .Bip32( + message: try String.read(from: buf) + ) + + case 32: return .Secp256k1( + message: try String.read(from: buf) + ) + + case 33: return .Json( + message: try String.read(from: buf) + ) + + case 34: return .Hex( + message: try String.read(from: buf) + ) + + case 35: return .Psbt( + message: try String.read(from: buf) + ) + + case 36: return .PsbtParse( + message: try String.read(from: buf) + ) + + case 37: return .Electrum( + message: try String.read(from: buf) + ) + + case 38: return .Esplora( + message: try String.read(from: buf) + ) + + case 39: return .Sled( + message: try String.read(from: buf) + ) + + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + + + case let .InvalidU32Bytes(message): + buf.writeInt(Int32(1)) + message.write(into: buf) + case let .Generic(message): + buf.writeInt(Int32(2)) + message.write(into: buf) + case let .ScriptDoesntHaveAddressForm(message): + buf.writeInt(Int32(3)) + message.write(into: buf) + case let .NoRecipients(message): + buf.writeInt(Int32(4)) + message.write(into: buf) + case let .NoUtxosSelected(message): + buf.writeInt(Int32(5)) + message.write(into: buf) + case let .OutputBelowDustLimit(message): + buf.writeInt(Int32(6)) + message.write(into: buf) + case let .InsufficientFunds(message): + buf.writeInt(Int32(7)) + message.write(into: buf) + case let .BnBTotalTriesExceeded(message): + buf.writeInt(Int32(8)) + message.write(into: buf) + case let .BnBNoExactMatch(message): + buf.writeInt(Int32(9)) + message.write(into: buf) + case let .UnknownUtxo(message): + buf.writeInt(Int32(10)) + message.write(into: buf) + case let .TransactionNotFound(message): + buf.writeInt(Int32(11)) + message.write(into: buf) + case let .TransactionConfirmed(message): + buf.writeInt(Int32(12)) + message.write(into: buf) + case let .IrreplaceableTransaction(message): + buf.writeInt(Int32(13)) + message.write(into: buf) + case let .FeeRateTooLow(message): + buf.writeInt(Int32(14)) + message.write(into: buf) + case let .FeeTooLow(message): + buf.writeInt(Int32(15)) + message.write(into: buf) + case let .FeeRateUnavailable(message): + buf.writeInt(Int32(16)) + message.write(into: buf) + case let .MissingKeyOrigin(message): + buf.writeInt(Int32(17)) + message.write(into: buf) + case let .Key(message): + buf.writeInt(Int32(18)) + message.write(into: buf) + case let .ChecksumMismatch(message): + buf.writeInt(Int32(19)) + message.write(into: buf) + case let .SpendingPolicyRequired(message): + buf.writeInt(Int32(20)) + message.write(into: buf) + case let .InvalidPolicyPathError(message): + buf.writeInt(Int32(21)) + message.write(into: buf) + case let .Signer(message): + buf.writeInt(Int32(22)) + message.write(into: buf) + case let .InvalidNetwork(message): + buf.writeInt(Int32(23)) + message.write(into: buf) + case let .InvalidProgressValue(message): + buf.writeInt(Int32(24)) + message.write(into: buf) + case let .ProgressUpdateError(message): + buf.writeInt(Int32(25)) + message.write(into: buf) + case let .InvalidOutpoint(message): + buf.writeInt(Int32(26)) + message.write(into: buf) + case let .Descriptor(message): + buf.writeInt(Int32(27)) + message.write(into: buf) + case let .AddressValidator(message): + buf.writeInt(Int32(28)) + message.write(into: buf) + case let .Encode(message): + buf.writeInt(Int32(29)) + message.write(into: buf) + case let .Miniscript(message): + buf.writeInt(Int32(30)) + message.write(into: buf) + case let .Bip32(message): + buf.writeInt(Int32(31)) + message.write(into: buf) + case let .Secp256k1(message): + buf.writeInt(Int32(32)) + message.write(into: buf) + case let .Json(message): + buf.writeInt(Int32(33)) + message.write(into: buf) + case let .Hex(message): + buf.writeInt(Int32(34)) + message.write(into: buf) + case let .Psbt(message): + buf.writeInt(Int32(35)) + message.write(into: buf) + case let .PsbtParse(message): + buf.writeInt(Int32(36)) + message.write(into: buf) + case let .Electrum(message): + buf.writeInt(Int32(37)) + message.write(into: buf) + case let .Esplora(message): + buf.writeInt(Int32(38)) + message.write(into: buf) + case let .Sled(message): + buf.writeInt(Int32(39)) + message.write(into: buf) + } + } +} + + +extension BdkError: Equatable, Hashable {} + +extension BdkError: Error { } + + +// Declaration and FfiConverters for BdkProgress Callback Interface + +public protocol BdkProgress : AnyObject { + func update(progress: Float, message: String? ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceBdkProgress : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeUpdate(_ swiftCallbackInterface: BdkProgress, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.update( + progress: try Float.read(from: reader), + message: try FfiConverterOptionString.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceBdkProgress.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceBdkProgress.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeUpdate(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceBdkProgress: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_bdk_2e4d_BdkProgress_init_callback(foreignCallbackCallbackInterfaceBdkProgress, err) + } + return FfiConverterCallbackInterface() +}() +extension UInt8: Primitive, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> Self { + return try self.lift(buf.readInt()) + } + + fileprivate func write(into buf: Writer) { + buf.writeInt(self.lower()) + } +} +extension UInt32: Primitive, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> Self { + return try self.lift(buf.readInt()) + } + + fileprivate func write(into buf: Writer) { + buf.writeInt(self.lower()) + } +} +extension UInt64: Primitive, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> Self { + return try self.lift(buf.readInt()) + } + + fileprivate func write(into buf: Writer) { + buf.writeInt(self.lower()) + } +} +extension Float: Primitive, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> Self { + return try self.lift(buf.readFloat()) + } + + fileprivate func write(into buf: Writer) { + buf.writeFloat(self.lower()) + } +} +extension String: ViaFfi { + fileprivate typealias FfiType = RustBuffer + + fileprivate static func lift(_ v: FfiType) throws -> Self { + defer { + v.deallocate() + } + if v.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: v.data!, count: Int(v.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + fileprivate func lower() -> FfiType { + return self.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + fileprivate static func read(from buf: Reader) throws -> Self { + let len: Int32 = try buf.readInt() + return String(bytes: try buf.readBytes(count: Int(len)), encoding: String.Encoding.utf8)! + } + + fileprivate func write(into buf: Writer) { + let len = Int32(self.utf8.count) + buf.writeInt(len) + buf.writeBytes(self.utf8) + } +} +// Helper code for OfflineWallet class is found in ObjectTemplate.swift +// Helper code for OnlineWallet class is found in ObjectTemplate.swift +// Helper code for PartiallySignedBitcoinTransaction class is found in ObjectTemplate.swift +// Helper code for Confirmation record is found in RecordTemplate.swift +// Helper code for ElectrumConfig record is found in RecordTemplate.swift +// Helper code for EsploraConfig record is found in RecordTemplate.swift +// Helper code for ExtendedKeyInfo record is found in RecordTemplate.swift +// Helper code for SledDbConfiguration record is found in RecordTemplate.swift +// Helper code for TransactionDetails record is found in RecordTemplate.swift +// Helper code for BlockchainConfig enum is found in EnumTemplate.swift +// Helper code for DatabaseConfig enum is found in EnumTemplate.swift +// Helper code for MnemonicType enum is found in EnumTemplate.swift +// Helper code for Network enum is found in EnumTemplate.swift +// Helper code for Transaction enum is found in EnumTemplate.swift +// Helper code for BdkError error is found in ErrorTemplate.swift + +fileprivate enum FfiConverterOptionUInt8: FfiConverterUsingByteBuffer { + typealias SwiftType = UInt8? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try UInt8.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionUInt32: FfiConverterUsingByteBuffer { + typealias SwiftType = UInt32? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try UInt32.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionUInt64: FfiConverterUsingByteBuffer { + typealias SwiftType = UInt64? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try UInt64.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionFloat: FfiConverterUsingByteBuffer { + typealias SwiftType = Float? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try Float.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionString: FfiConverterUsingByteBuffer { + typealias SwiftType = String? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try String.read(from: buf) + } + } +} + +fileprivate enum FfiConverterSequenceEnumTransaction: FfiConverterUsingByteBuffer { + typealias SwiftType = [Transaction] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterSequence.write(value, into: buf) { (item, buf) in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterSequence.read(from: buf) { buf in + try Transaction.read(from: buf) + } + } +} + + +/** + * Top level initializers and tear down methods. + * + * This is generated by uniffi. + */ +public enum BdkLifecycle { + /** + * Initialize the FFI and Rust library. This should be only called once per application. + */ + func initialize() { + + // No initialization code needed + + } +} \ No newline at end of file diff --git a/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift b/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift new file mode 100644 index 0000000..88b9a69 --- /dev/null +++ b/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import BitcoinDevKit + +final class BitcoinDevKitTests: XCTestCase { + func testMemoryWalletNewAddress() throws { + let desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + let config = DatabaseConfig.memory(junk: "") + let wallet = try OfflineWallet.init(descriptor: desc, network: Network.regtest, databaseConfig: config) + let address = wallet.getNewAddress() + XCTAssertEqual(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + } +} diff --git a/bdk-ffi b/bdk-ffi new file mode 160000 index 0000000..4e087ef --- /dev/null +++ b/bdk-ffi @@ -0,0 +1 @@ +Subproject commit 4e087ef21c69986090ad8157bba112842156b8b9 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..7c4ee81 --- /dev/null +++ b/build.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -eo pipefail + +BUILD_PROFILE=release +BDKFFI_DIR=bdk-ffi +TARGET_DIR=$BDKFFI_DIR/target +STATIC_LIB_NAME=libbdkffi.a +SWIFT_DIR="$BDKFFI_DIR/bindings/bdk-swift" +XCFRAMEWORK_NAME="bdkFFI" +XCFRAMEWORK_ROOT="$XCFRAMEWORK_NAME.xcframework" +XCFRAMEWORK_COMMON="$XCFRAMEWORK_ROOT/common/$XCFRAMEWORK_NAME.framework" + +## build bdk-ffi rust libs +echo "Build bdk-ffi rust library" +pushd $BDKFFI_DIR +cargo build --release + +echo "Generate bdk-ffi swift bindings" +uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift +swiftc -module-name bdk -emit-library -o libbdkffi.dylib -emit-module -emit-module-path bindings/bdk-swift/ -parse-as-library -L target/release/ -lbdkffi -Xcc -fmodule-map-file=bindings/bdk-swift/$XCFRAMEWORK_NAME.modulemap bindings/bdk-swift/bdk.swift -suppress-warnings + +## build bdk-ffi rust libs into xcframework +echo "Build bdk-ffi libs into swift xcframework" + +APPLE_TRIPLES=("x86_64-apple-darwin" "x86_64-apple-ios" "aarch64-apple-ios") +for TARGET in $APPLE_TRIPLES; do + echo "Build bdk-ffi lib for target $TARGET" + cargo build --release --target $TARGET +done +popd + +## Manually construct xcframework + +# Cleanup prior build +rm -rf "$XCFRAMEWORK_ROOT" +rm -f $XCFRAMEWORK_ROOT.zip + +# Common files +mkdir -p "$XCFRAMEWORK_COMMON/Modules" +cp "$SWIFT_DIR/$XCFRAMEWORK_NAME.modulemap" "$XCFRAMEWORK_COMMON/Modules/" +mkdir -p "$XCFRAMEWORK_COMMON/Headers" +cp "$SWIFT_DIR/$XCFRAMEWORK_NAME-umbrella.h" "$XCFRAMEWORK_COMMON/Headers" +cp "$SWIFT_DIR/$XCFRAMEWORK_NAME.h" "$XCFRAMEWORK_COMMON/Headers" + +# macOS x86_64 hardware +mkdir -p "$XCFRAMEWORK_ROOT/macos-x86_64" +cp -R "$XCFRAMEWORK_COMMON" "$XCFRAMEWORK_ROOT/macos-x86_64/$XCFRAMEWORK_NAME.framework" +cp "$TARGET_DIR/x86_64-apple-darwin/$BUILD_PROFILE/$STATIC_LIB_NAME" "$XCFRAMEWORK_ROOT/macos-x86_64/$XCFRAMEWORK_NAME.framework/$XCFRAMEWORK_NAME" + +# iOS hardware +mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64" +cp -R "$XCFRAMEWORK_COMMON" "$XCFRAMEWORK_ROOT/ios-arm64/$XCFRAMEWORK_NAME.framework" +cp "$TARGET_DIR/aarch64-apple-ios/$BUILD_PROFILE/$STATIC_LIB_NAME" "$XCFRAMEWORK_ROOT/ios-arm64/$XCFRAMEWORK_NAME.framework/$XCFRAMEWORK_NAME" + +# iOS simulator, currently x86_64 only (need to make fat binary to add M1) +mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator" +cp -R "$XCFRAMEWORK_COMMON" "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$XCFRAMEWORK_NAME.framework" +cp "$TARGET_DIR/x86_64-apple-ios/$BUILD_PROFILE/$STATIC_LIB_NAME" "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$XCFRAMEWORK_NAME.framework/$XCFRAMEWORK_NAME" + +# Set up the metadata for the XCFramework as a whole. +cp "$SWIFT_DIR/Info.plist" "$XCFRAMEWORK_ROOT/Info.plist" +# TODO add license info + +# Remove common +rm -rf "$XCFRAMEWORK_ROOT/common" + +# Zip it all up into a bundle for distribution. +zip -9 -r "$XCFRAMEWORK_ROOT.zip" "$XCFRAMEWORK_ROOT" + +swift package compute-checksum bdkFFI.xcframework.zip + +# Cleanup build ? +# rm -rf "$XCFRAMEWORK_ROOT"