11import Foundation
2+ import Darwin
23import DashSDKFFI
34
45/// Transaction utilities for wallet operations
@@ -39,10 +40,23 @@ public class Transaction {
3940 var error = FFIError ( )
4041 var txBytesPtr : UnsafeMutablePointer < UInt8 > ?
4142 var txLen : size_t = 0
42-
43- // Convert outputs to FFI format
44- let ffiOutputs = outputs. map { $0. toFFI ( ) }
45-
43+ // Convert outputs to FFI format with stable C strings
44+ var cStrings : [ UnsafeMutablePointer < CChar > ] = [ ]
45+ cStrings. reserveCapacity ( outputs. count)
46+ var ffiOutputs : [ FFITxOutput ] = [ ]
47+ ffiOutputs. reserveCapacity ( outputs. count)
48+
49+ for output in outputs {
50+ let cstr = output. address. withCString { strdup ( $0) }
51+ guard let cstr else {
52+ // Free any previously allocated strings before throwing
53+ for ptr in cStrings { free ( ptr) }
54+ throw KeyWalletError . invalidInput ( " Failed to allocate C string for address " )
55+ }
56+ cStrings. append ( cstr)
57+ ffiOutputs. append ( FFITxOutput ( address: UnsafePointer ( cstr) , amount: output. amount) )
58+ }
59+
4660 let success = ffiOutputs. withUnsafeBufferPointer { outputsPtr in
4761 wallet_build_transaction (
4862 wallet. ffiHandle,
@@ -57,6 +71,8 @@ public class Transaction {
5771 }
5872
5973 defer {
74+ // Free allocated C strings
75+ for ptr in cStrings { free ( ptr) }
6076 if error. message != nil {
6177 error_message_free ( error. message)
6278 }
@@ -89,8 +105,7 @@ public class Transaction {
89105 var signedTxPtr : UnsafeMutablePointer < UInt8 > ?
90106 var signedLen : size_t = 0
91107
92- let success = transactionData. withUnsafeBytes { txBytes in
93- let txPtr = txBytes. bindMemory ( to: UInt8 . self) . baseAddress
108+ let success = transactionData. withUnsafeBytes { ( txPtr: UnsafePointer < UInt8 > ) in
94109 return wallet_sign_transaction (
95110 wallet. ffiHandle,
96111 NetworkSet ( wallet. network) . ffiNetworks,
@@ -113,10 +128,122 @@ public class Transaction {
113128
114129 // Copy the signed transaction data before freeing
115130 let signedData = Data ( bytes: ptr, count: signedLen)
116-
131+
117132 return signedData
118133 }
119-
134+
135+ /// Build and sign a transaction in one step using managed wallet
136+ /// - Parameters:
137+ /// - managedWallet: The managed wallet with UTXO information
138+ /// - wallet: The wallet with private keys for signing
139+ /// - accountIndex: The account index to use
140+ /// - outputs: The transaction outputs
141+ /// - feePerKB: Fee per kilobyte in satoshis
142+ /// - currentHeight: Current blockchain height for UTXO selection
143+ /// - Returns: The signed transaction bytes ready for broadcast
144+ public static func buildAndSign( managedWallet: ManagedWallet ,
145+ wallet: Wallet ,
146+ accountIndex: UInt32 = 0 ,
147+ outputs: [ Output ] ,
148+ feePerKB: UInt64 ,
149+ currentHeight: UInt32 ) throws -> Data {
150+ guard !outputs. isEmpty else {
151+ throw KeyWalletError . invalidInput ( " Transaction must have at least one output " )
152+ }
153+
154+ guard !wallet. isWatchOnly else {
155+ throw KeyWalletError . invalidState ( " Cannot sign with watch-only wallet " )
156+ }
157+
158+ var error = FFIError ( )
159+ var txBytesPtr : UnsafeMutablePointer < UInt8 > ?
160+ var txLen : size_t = 0
161+
162+ // Get managed wallet handle
163+ guard let managedHandle = managedWallet. getInfoHandle ( ) else {
164+ throw KeyWalletError . invalidState ( " Failed to get managed wallet handle " )
165+ }
166+
167+ // Convert outputs to FFI format with stable C strings
168+ var cStrings : [ UnsafeMutablePointer < CChar > ] = [ ]
169+ cStrings. reserveCapacity ( outputs. count)
170+ var ffiOutputs : [ FFITxOutput ] = [ ]
171+ ffiOutputs. reserveCapacity ( outputs. count)
172+
173+ for output in outputs {
174+ let cstr = output. address. withCString { strdup ( $0) }
175+ guard let cstr else {
176+ for ptr in cStrings { free ( ptr) }
177+ throw KeyWalletError . invalidInput ( " Failed to allocate C string for address " )
178+ }
179+ cStrings. append ( cstr)
180+ ffiOutputs. append ( FFITxOutput ( address: UnsafePointer ( cstr) , amount: output. amount) )
181+ }
182+
183+ let success = ffiOutputs. withUnsafeBufferPointer { outputsPtr in
184+ wallet_build_and_sign_transaction (
185+ managedHandle,
186+ wallet. ffiHandle,
187+ wallet. network. ffiValue,
188+ accountIndex,
189+ outputsPtr. baseAddress,
190+ outputs. count,
191+ feePerKB,
192+ currentHeight,
193+ & txBytesPtr,
194+ & txLen,
195+ & error)
196+ }
197+
198+ defer {
199+ for ptr in cStrings { free ( ptr) }
200+ if error. message != nil {
201+ error_message_free ( error. message)
202+ }
203+ if let ptr = txBytesPtr {
204+ transaction_bytes_free ( ptr)
205+ }
206+ }
207+
208+ guard success, let ptr = txBytesPtr else {
209+ throw KeyWalletError ( ffiError: error)
210+ }
211+
212+ // Copy the transaction data before freeing
213+ let txData = Data ( bytes: ptr, count: txLen)
214+
215+ return txData
216+ }
217+
218+ /// Extract TXID from raw transaction bytes
219+ /// - Parameter transactionData: The transaction bytes
220+ /// - Returns: The transaction ID as a hex string
221+ public static func getTxid( from transactionData: Data ) throws -> String {
222+ var error = FFIError ( )
223+ var txidPtr : UnsafeMutablePointer < CChar > ?
224+
225+ let success = transactionData. withUnsafeBytes { txBytes in
226+ let txPtr = txBytes. bindMemory ( to: UInt8 . self) . baseAddress
227+ txidPtr = transaction_get_txid_from_bytes ( txPtr, transactionData. count, & error)
228+ return txidPtr != nil
229+ }
230+
231+ defer {
232+ if error. message != nil {
233+ error_message_free ( error. message)
234+ }
235+ if let ptr = txidPtr {
236+ string_free ( ptr)
237+ }
238+ }
239+
240+ guard success, let ptr = txidPtr else {
241+ throw KeyWalletError ( ffiError: error)
242+ }
243+
244+ return String ( cString: ptr)
245+ }
246+
120247 /// Check if a transaction belongs to a wallet
121248 /// - Parameters:
122249 /// - wallet: The wallet to check against
@@ -137,12 +264,10 @@ public class Transaction {
137264 var error = FFIError ( )
138265 var result = FFITransactionCheckResult ( )
139266
140- let success = transactionData. withUnsafeBytes { txBytes in
141- let txPtr = txBytes. bindMemory ( to: UInt8 . self) . baseAddress
267+ let success = transactionData. withUnsafeBytes { ( txPtr: UnsafePointer < UInt8 > ) in
142268
143269 if let hash = blockHash {
144- return hash. withUnsafeBytes { hashBytes in
145- let hashPtr = hashBytes. bindMemory ( to: UInt8 . self) . baseAddress
270+ return hash. withUnsafeBytes { ( hashPtr: UnsafePointer < UInt8 > ) in
146271
147272 return wallet_check_transaction (
148273 wallet. ffiHandle,
@@ -181,8 +306,7 @@ public class Transaction {
181306 public static func classify( _ transactionData: Data ) throws -> String {
182307 var error = FFIError ( )
183308
184- let classificationPtr = transactionData. withUnsafeBytes { txBytes in
185- let txPtr = txBytes. bindMemory ( to: UInt8 . self) . baseAddress
309+ let classificationPtr = transactionData. withUnsafeBytes { ( txPtr: UnsafePointer < UInt8 > ) in
186310 return transaction_classify ( txPtr, transactionData. count, & error)
187311 }
188312
@@ -201,4 +325,4 @@ public class Transaction {
201325
202326 return classification
203327 }
204- }
328+ }
0 commit comments