Skip to content

feat(platform-wallet): address funding fee quote FFI and Swift wrapper - #4447

Open
llbartekll wants to merge 1 commit into
feat/address-funding-fee-quote-clientfrom
feat/address-funding-fee-quote-ffi
Open

feat(platform-wallet): address funding fee quote FFI and Swift wrapper#4447
llbartekll wants to merge 1 commit into
feat/address-funding-fee-quote-clientfrom
feat/address-funding-fee-quote-ffi

Conversation

@llbartekll

Copy link
Copy Markdown
Contributor

Issue being fixed or feature implemented

Final PR of the address-funding fee-quote stack (4/4, on top of #4446). Exposes the quote to iOS through the wallet FFI and the Swift SDK, so dashwallet-ios can later replace its static funding-fee heuristic with a state-aware network quote. dashwallet-ios itself is intentionally untouched at this stage.

What was done?

  • rs-platform-wallet-ffi: platform_address_wallet_quote_funding_fee(handle, recipient_address, recipient_address_len, prepared_outpoint, user_fee_increase, out_quote) — resolves the wallet from PLATFORM_ADDRESS_WALLET_STORAGE and runs PlatformAddressWallet::quote_funding_fee on the worker runtime (the wallet already owns its SDK — no separate SDK handle). prepared_outpoint is a nullable *const OutPointFFI — NULL lets the node use its deterministic placeholder; a non-null outpoint is serialized with the chain's exact key layout (raw txid bytes plus vout in little endian) via dashcore's OutPoint conversion. The out parameter is plain data (nothing to free), zeroed as a sentinel before any fallible step.
  • swift-sdk: ManagedPlatformAddressWallet.quoteFundingFee(recipientAddress:preparedOutpointTxid:preparedOutpointVout:userFeeIncrease:) async throws -> AddressFundingFeeQuote, via the established Task.detached wrapper. The docs state the quote is planning data from a single node (no proof) and that lock sizing stays governed by minimumRequiredLockCredits plus the app's own margin policy. There is no offline fallback anywhere in the path.

How Has This Been Tested?

  • FFI guard tests pin the fail-closed codes: null pointers (with the sentinel written), malformed address bytes (ErrorInvalidParameter), unknown handle (NotFound).
  • End to end with build_ios.sh --target sim --profile dev: the regenerated platform-wallet-ffi header carries the new symbol and struct, and SwiftExampleApp (compiling the full SwiftDashSDK, including the new wrapper) builds successfully.

Breaking Changes

None — additive.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

🤖 Generated with Claude Code

…apper

Expose the state-aware getAddressFundingFeeQuote through the wallet FFI
and the Swift SDK, following the existing handle-bound async patterns:

- platform_address_wallet_quote_funding_fee(handle, recipient_address,
  recipient_address_len, prepared_outpoint, user_fee_increase,
  out_quote): resolves the wallet from PLATFORM_ADDRESS_WALLET_STORAGE
  and runs PlatformAddressWallet::quote_funding_fee on the worker
  runtime (the wallet already owns its SDK — no separate SDK handle).
  prepared_outpoint is a nullable *const OutPointFFI — NULL lets the
  node use its deterministic placeholder; a non-null outpoint is
  serialized with the chain's exact key layout (raw txid bytes plus the
  vout in little endian) via dashcore's OutPoint conversion. The out
  parameter is plain data (nothing to free), zeroed as a sentinel before
  any fallible step. Guard tests pin the fail-closed codes: null
  pointers, malformed address bytes, unknown handle.

- Swift: ManagedPlatformAddressWallet.quoteFundingFee(recipientAddress:
  preparedOutpointTxid:preparedOutpointVout:userFeeIncrease:) async
  throws -> AddressFundingFeeQuote, via the established Task.detached
  wrapper. The docs state the quote is planning data from a single node
  (no proof) and that lock sizing stays governed by
  minimumRequiredLockCredits plus the app's own margin policy. There is
  no offline fallback anywhere in the path — a network failure throws,
  never a stale constant.

Verified end to end with build_ios.sh --target sim --profile dev: the
regenerated platform-wallet-ffi header carries the new symbol and
SwiftExampleApp (compiling the full SwiftDashSDK) builds. dashwallet-ios
is intentionally untouched at this stage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e9d2c8f-d96f-432f-938f-d74bb3261095

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit 6f4fda0)

@thepastaclaw thepastaclaw left a comment

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.

Final validation — Codex/Sol only (Phase 2 disabled)

The FFI and Swift wrapper follow the established handle-storage, worker-runtime, and result-ownership patterns, with no blocking correctness or memory-safety issues found. Two non-blocking gaps remain: the public Swift API does not specify the required txid byte order, and the prepared-outpoint serialization branch lacks regression coverage.
Source: Codex general and FFI-specialist reviewers (exact backend model identifier was not included in the supplied evidence); grok-4.5 final verifier backend. Orchestration only: openclaw-agent/cliproxy/gpt-5.6-sol, not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 2 suggestion(s)

🤖 Prompt for all review comments 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/PlatformWallet/ManagedPlatformAddressWallet.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformAddressWallet.swift:938-941: Specify the required txid byte order in the public Swift API
  The wrapper copies `preparedOutpointTxid` byte-for-byte into `OutPointFFI`, and Rust passes those bytes to `Txid::from_byte_array` before serializing the Platform outpoint key. The parameter therefore requires the raw little-endian wire representation rather than the customary display-order txid. The sibling `resumeFundFromAssetLock` API documents this requirement explicitly. Without equivalent documentation here, a caller can reverse the wrong bytes and query a different outpoint, causing a spent prepared lock to appear absent instead of being rejected.

In `packages/rs-platform-wallet-ffi/src/platform_addresses/quote.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/platform_addresses/quote.rs:82-93: Add a regression test for prepared-outpoint serialization
  The guard test supplies a null `prepared_outpoint` in every call, leaving the new non-null conversion branch untested. This branch defines an endian-sensitive chain key used to detect an already-consumed asset lock. Add an offline unit test with a patterned 32-byte txid and a non-symmetric vout, asserting that conversion produces exactly `raw_txid || vout.to_le_bytes()`. This will catch accidental txid reversal or native-endian vout serialization without requiring a network-backed wallet.

Comment on lines +938 to +941
/// - preparedOutpointTxid: the 32-byte txid of an already built and
/// signed lock transaction, when the wallet has one; `nil` lets the
/// node use a deterministic placeholder with the same expected
/// search depth. An outpoint already spent on Platform is rejected.

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: Specify the required txid byte order in the public Swift API

The wrapper copies preparedOutpointTxid byte-for-byte into OutPointFFI, and Rust passes those bytes to Txid::from_byte_array before serializing the Platform outpoint key. The parameter therefore requires the raw little-endian wire representation rather than the customary display-order txid. The sibling resumeFundFromAssetLock API documents this requirement explicitly. Without equivalent documentation here, a caller can reverse the wrong bytes and query a different outpoint, causing a spent prepared lock to appear absent instead of being rejected.

Suggested change
/// - preparedOutpointTxid: the 32-byte txid of an already built and
/// signed lock transaction, when the wallet has one; `nil` lets the
/// node use a deterministic placeholder with the same expected
/// search depth. An outpoint already spent on Platform is rejected.
/// - preparedOutpointTxid: the 32-byte raw txid in little-endian wire
/// order (the same byte order as `OutPointFFI.txid`) of an already
/// built and signed lock transaction. If sourced from display-order
/// hex, decode it back to raw order before passing it here. `nil` lets
/// the node use a deterministic placeholder with the same expected
/// search depth. An outpoint already spent on Platform is rejected.

source: ['codex']

Comment on lines +82 to +93
let outpoint: Option<[u8; 36]> = if prepared_outpoint.is_null() {
None
} else {
let outpoint_ffi = *prepared_outpoint;
// The same byte layout the chain uses for outpoint keys:
// raw txid bytes followed by the vout in little endian.
let out_point = dashcore::OutPoint {
txid: dashcore::Txid::from_byte_array(outpoint_ffi.txid),
vout: outpoint_ffi.vout,
};
Some(out_point.into())
};

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: Add a regression test for prepared-outpoint serialization

The guard test supplies a null prepared_outpoint in every call, leaving the new non-null conversion branch untested. This branch defines an endian-sensitive chain key used to detect an already-consumed asset lock. Add an offline unit test with a patterned 32-byte txid and a non-symmetric vout, asserting that conversion produces exactly raw_txid || vout.to_le_bytes(). This will catch accidental txid reversal or native-endian vout serialization without requiring a network-backed wallet.

source: ['codex']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants