Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,28 +1,132 @@
import Foundation
import DashSDKFFI

// This struct is not mapping all fields of FFITransactionRecord
// for the lack of wrappers
public enum TransactionType {
case standard
case coinJoin
case providerRegistration
case providerUpdateRegister
case providerUpdateService
case providerUpdateRevocation
case assetLock
case assetUnlock
case coinbase
case ignored

init(ffi: FFITransactionType) {
switch ffi {
case FFI_TRANSACTION_TYPE_STANDARD: self = .standard
case FFI_TRANSACTION_TYPE_COIN_JOIN: self = .coinJoin
case FFI_TRANSACTION_TYPE_PROVIDER_REGISTRATION: self = .providerRegistration
case FFI_TRANSACTION_TYPE_PROVIDER_UPDATE_REGISTRAR: self = .providerUpdateRegister
case FFI_TRANSACTION_TYPE_PROVIDER_UPDATE_SERVICE: self = .providerUpdateService
case FFI_TRANSACTION_TYPE_PROVIDER_UPDATE_REVOCATION: self = .providerUpdateRevocation
case FFI_TRANSACTION_TYPE_ASSET_LOCK: self = .assetLock
case FFI_TRANSACTION_TYPE_ASSET_UNLOCK: self = .assetUnlock
case FFI_TRANSACTION_TYPE_COINBASE: self = .coinbase
case FFI_TRANSACTION_TYPE_IGNORED: self = .ignored
default: fatalError("Unknown FFITransactionType value: \(ffi)")
}
}
}

public enum TransactionDirection {
case incoming
case outgoing
case internalDir
case coinjoin

init(ffi: FFITransactionDirection) {
switch ffi {
case FFI_TRANSACTION_DIRECTION_INCOMING: self = .incoming
case FFI_TRANSACTION_DIRECTION_OUTGOING: self = .outgoing
case FFI_TRANSACTION_DIRECTION_INTERNAL: self = .internalDir
case FFI_TRANSACTION_DIRECTION_COIN_JOIN: self = .coinjoin
default: fatalError("Unknown FFITransactionDirection value: \(ffi)")
}
}
}

public struct InputDetail {
public let index: UInt32
public let value: UInt64
public let address: String

public init(ffi: FFIInputDetail) {
self.index = ffi.index
self.value = ffi.value
self.address = ffi.address != nil
? String(cString: ffi.address)
: ""
}
Comment on lines +50 to +61
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Nil FFI input address silently becomes empty string

InputDetail.init(ffi:) collapses a missing FFI address pointer to "":

self.address = ffi.address != nil
    ? String(cString: ffi.address)
    : ""

Downstream consumers cannot distinguish "FFI returned no address" (e.g. unparseable script, P2PK input, multisig redemption) from "address is the empty string". TransactionRecord.label already models the absent case correctly with String?, and OutputDetail sidesteps the issue by not exposing an address at all. Modeling InputDetail.address as String? and returning nil on a null pointer preserves the distinction for analyzers and UI without any FFI change.

source: ['claude']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/TransactionRecord.swift`:
- [SUGGESTION] lines 50-61: Nil FFI input address silently becomes empty string
  `InputDetail.init(ffi:)` collapses a missing FFI address pointer to `""`:

self.address = ffi.address != nil
? String(cString: ffi.address)
: ""


Downstream consumers cannot distinguish "FFI returned no address" (e.g. unparseable script, P2PK input, multisig redemption) from "address is the empty string". `TransactionRecord.label` already models the absent case correctly with `String?`, and `OutputDetail` sidesteps the issue by not exposing an address at all. Modeling `InputDetail.address` as `String?` and returning `nil` on a null pointer preserves the distinction for analyzers and UI without any FFI change.

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

public enum OutputRole {
case received
case change
case sent
Comment on lines +62 to +67
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: InputDetail.address erases FFI nullability into an empty string

FFIInputDetail.address is an optional C string pointer, but the new wrapper exposes it as a non-optional String and substitutes "" when the pointer is NULL. That collapses two different boundary states — 'no address was provided by the FFI record' and 'the address is the empty string' — into the same Swift value. This file already preserves that distinction for sibling fields like label, so the safer mapping here is also to keep the address optional on the Swift side instead of silently inventing a value.

💡 Suggested change
Suggested change
}
public enum OutputRole {
case received
case change
case sent
public struct InputDetail {
public let index: UInt32
public let value: UInt64
public let address: String?
public init(ffi: FFIInputDetail) {
self.index = ffi.index
self.value = ffi.value
self.address = ffi.address.map { String(cString: $0) }
}
}

source: ['claude', 'codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/TransactionRecord.swift`:
- [SUGGESTION] lines 62-67: `InputDetail.address` erases FFI nullability into an empty string
  `FFIInputDetail.address` is an optional C string pointer, but the new wrapper exposes it as a non-optional `String` and substitutes `""` when the pointer is `NULL`. That collapses two different boundary states — 'no address was provided by the FFI record' and 'the address is the empty string' — into the same Swift value. This file already preserves that distinction for sibling fields like `label`, so the safer mapping here is also to keep the address optional on the Swift side instead of silently inventing a value.

case unspendable

init(ffi: FFIOutputRole) {
switch ffi {
case FFI_OUTPUT_ROLE_RECEIVED: self = .received
case FFI_OUTPUT_ROLE_CHANGE: self = .change
case FFI_OUTPUT_ROLE_SENT: self = .sent
case FFI_OUTPUT_ROLE_UNSPENDABLE: self = .unspendable
default: fatalError("Unknown FFIOutputRole value: \(ffi)")
Comment thread
ZocoLini marked this conversation as resolved.
}
}
Comment on lines +16 to +78
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: Unknown FFI enum values crash the host app from the SPV transaction callback

All three new FFI-to-Swift enum bridges terminate the process via fatalError(...) on any unrecognized discriminant:

  • TransactionType.init(ffi:) line 28
  • TransactionDirection.init(ffi:) line 45
  • OutputRole.init(ffi:) line 76

These values originate in the externally-built DashSDKFFI.xcframework (a separately-compiled Rust target). They are consumed inside NotOwnedTransactionRecord.init(handle:) (line 117–118 and via OutputDetail.init(ffi:) at line 87), which is invoked synchronously from onSpvTransactionReceivedCallbackC at SPVEventHandler.swift:532 — the public SPVWalletEventsHandler.onTransactionReceived callback path. Any future Rust-side enum growth, version skew between this Swift wrapper and the bundled xcframework, or any unexpected sentinel value will hard-crash the embedding application while delivering a wallet event.

This diverges from the SDK's established convention for FFI enum bridges. TransactionContextType.init(ffiContext:) in TransactionContext.swift:14-16 uses TransactionContextType(rawValue: ffiContext.rawValue) ?? .mempool — a survivable fallback. The new bridges should follow the same pattern: add an .unknown case (or pick a safe default per enum) and return that on the default: arm so wallet callbacks degrade gracefully instead of taking down the host app.

source: ['claude', 'codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/TransactionRecord.swift`:
- [BLOCKING] lines 16-78: Unknown FFI enum values crash the host app from the SPV transaction callback
  All three new FFI-to-Swift enum bridges terminate the process via `fatalError(...)` on any unrecognized discriminant:

- `TransactionType.init(ffi:)` line 28
- `TransactionDirection.init(ffi:)` line 45
- `OutputRole.init(ffi:)` line 76

These values originate in the externally-built `DashSDKFFI.xcframework` (a separately-compiled Rust target). They are consumed inside `NotOwnedTransactionRecord.init(handle:)` (line 117–118 and via `OutputDetail.init(ffi:)` at line 87), which is invoked synchronously from `onSpvTransactionReceivedCallbackC` at `SPVEventHandler.swift:532` — the public `SPVWalletEventsHandler.onTransactionReceived` callback path. Any future Rust-side enum growth, version skew between this Swift wrapper and the bundled xcframework, or any unexpected sentinel value will hard-crash the embedding application while delivering a wallet event.

This diverges from the SDK's established convention for FFI enum bridges. `TransactionContextType.init(ffiContext:)` in `TransactionContext.swift:14-16` uses `TransactionContextType(rawValue: ffiContext.rawValue) ?? .mempool` — a survivable fallback. The new bridges should follow the same pattern: add an `.unknown` case (or pick a safe default per enum) and return that on the `default:` arm so wallet callbacks degrade gracefully instead of taking down the host app.

}

public struct OutputDetail {
public let index: UInt32
public let role: OutputRole
Comment on lines +23 to +83
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking: Unknown FFI enum values now crash transaction decoding

TransactionType.init(ffi:), TransactionDirection.init(ffi:), and OutputRole.init(ffi:) all end in fatalError for any discriminant the Swift source does not recognize. That is a bad failure mode on an FFI boundary: NotOwnedTransactionRecord(handle:) is used in both ManagedAccount.getTransactions() and the SPV event callback, so any enum value added on the Rust/C side before this Swift wrapper is updated will terminate the host app while decoding a transaction record. The rest of this SDK already treats raw FFI enums defensively — for example TransactionContextType(ffiContext:) falls back with rawValue ?? .mempool and several SPV wrappers map unknown raw values to .unknown — so this change introduces a new crash path instead of following the existing boundary contract.

source: ['claude', 'codex']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/TransactionRecord.swift`:
- [BLOCKING] lines 23-83: Unknown FFI enum values now crash transaction decoding
  `TransactionType.init(ffi:)`, `TransactionDirection.init(ffi:)`, and `OutputRole.init(ffi:)` all end in `fatalError` for any discriminant the Swift source does not recognize. That is a bad failure mode on an FFI boundary: `NotOwnedTransactionRecord(handle:)` is used in both `ManagedAccount.getTransactions()` and the SPV event callback, so any enum value added on the Rust/C side before this Swift wrapper is updated will terminate the host app while decoding a transaction record. The rest of this SDK already treats raw FFI enums defensively — for example `TransactionContextType(ffiContext:)` falls back with `rawValue ?? .mempool` and several SPV wrappers map unknown raw values to `.unknown` — so this change introduces a new crash path instead of following the existing boundary contract.


public init(ffi: FFIOutputDetail) {
self.index = ffi.index
self.role = OutputRole(ffi: ffi.role)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

public struct NotOwnedTransactionRecord {
let txid: Data
let net_amount: Int64
let context: TransactionContext
let fee: UInt64
let tx_data: Data
let label: String?
public let txid: Data
public let netAmount: Int64
public let context: TransactionContext
public let transactionType: TransactionType
public let direction: TransactionDirection
public let fee: UInt64
public let inputDetails: [InputDetail]
public let outputDetails: [OutputDetail]
public let txData: Data
public let label: String?

public init(handle: UnsafePointer<FFITransactionRecord>) {
let p = handle.pointee

self.txid = withUnsafeBytes(of: p.txid) { Data($0) }
self.net_amount = p.net_amount
self.netAmount = p.net_amount
self.fee = p.fee
self.tx_data = p.tx_data != nil
self.txData = p.tx_data != nil
? Data(bytes: p.tx_data, count: p.tx_len)
: Data()
self.label = p.label != nil
? String(cString: p.label)
: nil

self.context = TransactionContext(ffi: p.context)
self.transactionType = TransactionType(ffi: p.transaction_type)
self.direction = TransactionDirection(ffi: p.direction)

self.inputDetails = p.input_details != nil
? (0..<p.input_details_count).map { i in
InputDetail(ffi: p.input_details[i])
}
: []

self.outputDetails = p.output_details != nil
? (0..<p.output_details_count).map { i in
OutputDetail(ffi: p.output_details[i])
}
: []
}
}
Comment on lines 1 to 132
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: No tests cover the new FFI-to-Swift bridges

This file adds three enum bridges with hand-written FFI value mappings, two new structs with FFI-pointer initializers, and an init(handle:) that walks unsafe C arrays via p.input_details[i] / p.output_details[i]. The only Swift test file in packages/swift-sdk/Tests/ is WalletSerializationTests.swift; no test exercises this code. The enum mapping is positional and easy to silently mis-align against the C header (note FFI_TRANSACTION_TYPE_PROVIDER_UPDATE_REGISTRAR.providerUpdateRegister), and the *_count fields drive raw pointer indexing. A small unit test that builds an FFITransactionRecord in memory and asserts the resulting wrapper would catch both classes of regression. The PR checklist claims tests were added/updated; none accompany this change.

source: ['claude']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/TransactionRecord.swift`:
- [SUGGESTION] lines 1-132: No tests cover the new FFI-to-Swift bridges
  This file adds three enum bridges with hand-written FFI value mappings, two new structs with FFI-pointer initializers, and an `init(handle:)` that walks unsafe C arrays via `p.input_details[i]` / `p.output_details[i]`. The only Swift test file in `packages/swift-sdk/Tests/` is `WalletSerializationTests.swift`; no test exercises this code. The enum mapping is positional and easy to silently mis-align against the C header (note `FFI_TRANSACTION_TYPE_PROVIDER_UPDATE_REGISTRAR` ↔ `.providerUpdateRegister`), and the `*_count` fields drive raw pointer indexing. A small unit test that builds an `FFITransactionRecord` in memory and asserts the resulting wrapper would catch both classes of regression. The PR checklist claims tests were added/updated; none accompany this change.

Loading