-
Notifications
You must be signed in to change notification settings - Fork 57
feat(platform-wallet): address funding fee quote FFI and Swift wrapper #4447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/address-funding-fee-quote-client
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,194 @@ | ||||||||||||||||||||||||||||||||
| //! FFI for the state-aware address funding fee quote | ||||||||||||||||||||||||||||||||
| //! (`getAddressFundingFeeQuote`). | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| use dashcore::hashes::Hash; | ||||||||||||||||||||||||||||||||
| use dpp::address_funds::PlatformAddress; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| use crate::check_ptr; | ||||||||||||||||||||||||||||||||
| use crate::core_wallet_types::OutPointFFI; | ||||||||||||||||||||||||||||||||
| use crate::error::*; | ||||||||||||||||||||||||||||||||
| use crate::handle::*; | ||||||||||||||||||||||||||||||||
| use crate::runtime::block_on_worker; | ||||||||||||||||||||||||||||||||
| use crate::{unwrap_option_or_return, unwrap_result_or_return}; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// A state-aware fee quote for a 0-input / 1-output address funding from a | ||||||||||||||||||||||||||||||||
| /// fresh asset lock, as computed by a node. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// Plain data — nothing to free. The quote is planning data from a single | ||||||||||||||||||||||||||||||||
| /// node (the response carries no proof): sizing the funding lock stays | ||||||||||||||||||||||||||||||||
| /// governed by `minimum_required_lock_credits` plus the application's own | ||||||||||||||||||||||||||||||||
| /// margin policy. | ||||||||||||||||||||||||||||||||
| #[repr(C)] | ||||||||||||||||||||||||||||||||
| #[derive(Debug, Clone, Copy, Default)] | ||||||||||||||||||||||||||||||||
| pub struct AddressFundingFeeQuoteFFI { | ||||||||||||||||||||||||||||||||
| /// State-aware estimate of the fee the network would charge for the | ||||||||||||||||||||||||||||||||
| /// funding executed near the quoted state (includes the requested user | ||||||||||||||||||||||||||||||||
| /// fee increase). | ||||||||||||||||||||||||||||||||
| pub estimated_fee_credits: u64, | ||||||||||||||||||||||||||||||||
| /// The consensus admission floor for the asset lock. | ||||||||||||||||||||||||||||||||
| pub minimum_required_lock_credits: u64, | ||||||||||||||||||||||||||||||||
| /// The protocol version the node quoted with. | ||||||||||||||||||||||||||||||||
| pub protocol_version: u32, | ||||||||||||||||||||||||||||||||
| /// The committed block height the quote was computed on. | ||||||||||||||||||||||||||||||||
| pub state_height: u64, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Fetch a state-aware fee quote for funding one platform address from a | ||||||||||||||||||||||||||||||||
| /// fresh asset lock (0 address inputs, 1 remainder output). | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// Asynchronous network call executed on the wallet's worker runtime; the | ||||||||||||||||||||||||||||||||
| /// node computes the quote read-only against its committed state. There is | ||||||||||||||||||||||||||||||||
| /// no offline fallback — a network failure returns an error, never a stale | ||||||||||||||||||||||||||||||||
| /// constant. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// - `recipient_address` / `recipient_address_len`: the serialized platform | ||||||||||||||||||||||||||||||||
| /// address of the funding recipient (as produced by the wallet's address | ||||||||||||||||||||||||||||||||
| /// APIs). | ||||||||||||||||||||||||||||||||
| /// - `prepared_outpoint`: the exact asset-lock outpoint when the wallet has | ||||||||||||||||||||||||||||||||
| /// already built and signed the lock transaction; NULL lets the node use a | ||||||||||||||||||||||||||||||||
| /// deterministic placeholder with the same expected search depth. An | ||||||||||||||||||||||||||||||||
| /// outpoint that is already spent on Platform is rejected by the node. | ||||||||||||||||||||||||||||||||
| /// - `user_fee_increase`: the fee increase the quote should include; the | ||||||||||||||||||||||||||||||||
| /// SDK's chain-lock retry loop can raise a funding by up to 14 units. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// # Safety | ||||||||||||||||||||||||||||||||
| /// - `recipient_address` must be valid for `recipient_address_len` bytes. | ||||||||||||||||||||||||||||||||
| /// - `prepared_outpoint` must be NULL or a valid pointer. | ||||||||||||||||||||||||||||||||
| /// - `out_quote` must be a valid, writable pointer. | ||||||||||||||||||||||||||||||||
| #[no_mangle] | ||||||||||||||||||||||||||||||||
| pub unsafe extern "C" fn platform_address_wallet_quote_funding_fee( | ||||||||||||||||||||||||||||||||
| handle: Handle, | ||||||||||||||||||||||||||||||||
| recipient_address: *const u8, | ||||||||||||||||||||||||||||||||
| recipient_address_len: usize, | ||||||||||||||||||||||||||||||||
| prepared_outpoint: *const OutPointFFI, | ||||||||||||||||||||||||||||||||
| user_fee_increase: u16, | ||||||||||||||||||||||||||||||||
| out_quote: *mut AddressFundingFeeQuoteFFI, | ||||||||||||||||||||||||||||||||
| ) -> PlatformWalletFFIResult { | ||||||||||||||||||||||||||||||||
| check_ptr!(out_quote); | ||||||||||||||||||||||||||||||||
| *out_quote = AddressFundingFeeQuoteFFI::default(); | ||||||||||||||||||||||||||||||||
| check_ptr!(recipient_address); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let address_bytes = std::slice::from_raw_parts(recipient_address, recipient_address_len); | ||||||||||||||||||||||||||||||||
| let recipient = match PlatformAddress::from_bytes(address_bytes) { | ||||||||||||||||||||||||||||||||
| Ok(recipient) => recipient, | ||||||||||||||||||||||||||||||||
| Err(e) => { | ||||||||||||||||||||||||||||||||
| return PlatformWalletFFIResult::err( | ||||||||||||||||||||||||||||||||
| PlatformWalletFFIResultCode::ErrorInvalidParameter, | ||||||||||||||||||||||||||||||||
| format!("recipient_address is not a serialized platform address: {e}"), | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| 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()) | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { | ||||||||||||||||||||||||||||||||
| let wallet_clone = wallet.clone(); | ||||||||||||||||||||||||||||||||
| block_on_worker(async move { | ||||||||||||||||||||||||||||||||
| wallet_clone | ||||||||||||||||||||||||||||||||
| .quote_funding_fee(recipient, outpoint, user_fee_increase) | ||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+95
to
+102
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: Release the handle-storage lock before awaiting the network quote
Suggested change
source: ['codex'] |
||||||||||||||||||||||||||||||||
| let result = unwrap_option_or_return!(option); | ||||||||||||||||||||||||||||||||
| let quote = unwrap_result_or_return!(result); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| *out_quote = AddressFundingFeeQuoteFFI { | ||||||||||||||||||||||||||||||||
| estimated_fee_credits: quote.estimated_fee_credits, | ||||||||||||||||||||||||||||||||
| minimum_required_lock_credits: quote.minimum_required_lock_credits, | ||||||||||||||||||||||||||||||||
| protocol_version: quote.protocol_version, | ||||||||||||||||||||||||||||||||
| state_height: quote.state_height, | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| PlatformWalletFFIResult::ok() | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[cfg(test)] | ||||||||||||||||||||||||||||||||
| mod tests { | ||||||||||||||||||||||||||||||||
| use super::*; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Null-pointer and invalid-handle guards fail closed with the right | ||||||||||||||||||||||||||||||||
| /// codes and never touch the out parameter's success path. | ||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||
| fn test_guards_fail_closed() { | ||||||||||||||||||||||||||||||||
| let mut out = AddressFundingFeeQuoteFFI { | ||||||||||||||||||||||||||||||||
| estimated_fee_credits: 0xDEAD, | ||||||||||||||||||||||||||||||||
| ..Default::default() | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Null out pointer. | ||||||||||||||||||||||||||||||||
| let mut result = unsafe { | ||||||||||||||||||||||||||||||||
| platform_address_wallet_quote_funding_fee( | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| std::ptr::null(), | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| std::ptr::null(), | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| std::ptr::null_mut(), | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorNullPointer); | ||||||||||||||||||||||||||||||||
| unsafe { platform_wallet_ffi_result_free(&mut result) }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Null address pointer: out must be reset to the zero sentinel. | ||||||||||||||||||||||||||||||||
| let mut result = unsafe { | ||||||||||||||||||||||||||||||||
| platform_address_wallet_quote_funding_fee( | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| std::ptr::null(), | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| std::ptr::null(), | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| &mut out, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorNullPointer); | ||||||||||||||||||||||||||||||||
| assert_eq!(out.estimated_fee_credits, 0, "sentinel must be written"); | ||||||||||||||||||||||||||||||||
| unsafe { platform_wallet_ffi_result_free(&mut result) }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Malformed address bytes. | ||||||||||||||||||||||||||||||||
| let bad_address = [0xFFu8; 3]; | ||||||||||||||||||||||||||||||||
| let mut result = unsafe { | ||||||||||||||||||||||||||||||||
| platform_address_wallet_quote_funding_fee( | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| bad_address.as_ptr(), | ||||||||||||||||||||||||||||||||
| bad_address.len(), | ||||||||||||||||||||||||||||||||
| std::ptr::null(), | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| &mut out, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| assert_eq!( | ||||||||||||||||||||||||||||||||
| result.code, | ||||||||||||||||||||||||||||||||
| PlatformWalletFFIResultCode::ErrorInvalidParameter | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| unsafe { platform_wallet_ffi_result_free(&mut result) }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Unknown handle with a well-formed address. | ||||||||||||||||||||||||||||||||
| let address = dpp::address_funds::PlatformAddress::P2pkh([7; 20]).to_bytes(); | ||||||||||||||||||||||||||||||||
| let mut result = unsafe { | ||||||||||||||||||||||||||||||||
| platform_address_wallet_quote_funding_fee( | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| address.as_ptr(), | ||||||||||||||||||||||||||||||||
| address.len(), | ||||||||||||||||||||||||||||||||
| std::ptr::null(), | ||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||
| &mut out, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| assert_eq!( | ||||||||||||||||||||||||||||||||
| result.code, | ||||||||||||||||||||||||||||||||
| PlatformWalletFFIResultCode::NotFound, | ||||||||||||||||||||||||||||||||
| "an unknown handle maps to NotFound" | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| unsafe { platform_wallet_ffi_result_free(&mut result) }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -902,4 +902,109 @@ public final class ManagedPlatformAddressWallet: @unchecked Sendable { | |||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // MARK: - Funding fee quote | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// A state-aware fee quote for a 0-input / 1-output address funding from | ||||||||||||||||||||||
| /// a fresh asset lock, computed by a node (`getAddressFundingFeeQuote`). | ||||||||||||||||||||||
| /// | ||||||||||||||||||||||
| /// Planning data from a single node — the response carries no proof. | ||||||||||||||||||||||
| /// Sizing the funding lock stays governed by | ||||||||||||||||||||||
| /// `minimumRequiredLockCredits` plus the application's own margin policy. | ||||||||||||||||||||||
| public struct AddressFundingFeeQuote: Sendable, Equatable { | ||||||||||||||||||||||
| /// State-aware estimate of the fee the network would charge for the | ||||||||||||||||||||||
| /// funding executed near the quoted state (includes the requested | ||||||||||||||||||||||
| /// user fee increase). | ||||||||||||||||||||||
| public let estimatedFeeCredits: UInt64 | ||||||||||||||||||||||
| /// The consensus admission floor for the asset lock. | ||||||||||||||||||||||
| public let minimumRequiredLockCredits: UInt64 | ||||||||||||||||||||||
| /// The protocol version the node quoted with. | ||||||||||||||||||||||
| public let protocolVersion: UInt32 | ||||||||||||||||||||||
| /// The committed block height the quote was computed on. | ||||||||||||||||||||||
| public let stateHeight: UInt64 | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /// Fetch a state-aware fee quote for funding one platform address from a | ||||||||||||||||||||||
| /// fresh asset lock (0 address inputs, 1 remainder output). | ||||||||||||||||||||||
| /// | ||||||||||||||||||||||
| /// Asynchronous network call: the node prices the exact production | ||||||||||||||||||||||
| /// operations against its committed state (measured tree depths instead | ||||||||||||||||||||||
| /// of worst-case constants). There is no offline fallback — a network | ||||||||||||||||||||||
| /// failure throws, never a stale constant. | ||||||||||||||||||||||
| /// | ||||||||||||||||||||||
| /// - Parameters: | ||||||||||||||||||||||
| /// - recipientAddress: the serialized platform address of the funding | ||||||||||||||||||||||
| /// recipient, in the same byte format the `Addresses` query APIs use. | ||||||||||||||||||||||
| /// - 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. | ||||||||||||||||||||||
|
Comment on lines
+938
to
+941
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
source: ['codex'] |
||||||||||||||||||||||
| /// - preparedOutpointVout: the credit output index of the prepared | ||||||||||||||||||||||
| /// lock; ignored when `preparedOutpointTxid` is `nil`. | ||||||||||||||||||||||
| /// - userFeeIncrease: the fee increase the quote should include; the | ||||||||||||||||||||||
| /// SDK's chain-lock retry loop can raise a funding by up to 14 units. | ||||||||||||||||||||||
| public func quoteFundingFee( | ||||||||||||||||||||||
| recipientAddress: Data, | ||||||||||||||||||||||
| preparedOutpointTxid: Data? = nil, | ||||||||||||||||||||||
| preparedOutpointVout: UInt32 = 0, | ||||||||||||||||||||||
| userFeeIncrease: UInt16 = 0 | ||||||||||||||||||||||
| ) async throws -> AddressFundingFeeQuote { | ||||||||||||||||||||||
| if let txid = preparedOutpointTxid, txid.count != 32 { | ||||||||||||||||||||||
| throw PlatformWalletError.invalidParameter( | ||||||||||||||||||||||
| "preparedOutpointTxid must be exactly 32 bytes (was \(txid.count))" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| let handle = self.handle | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return try await Task.detached(priority: .userInitiated) { | ||||||||||||||||||||||
| () -> AddressFundingFeeQuote in | ||||||||||||||||||||||
| var quote = AddressFundingFeeQuoteFFI() | ||||||||||||||||||||||
| let result: PlatformWalletFFIResult | ||||||||||||||||||||||
| if let txid = preparedOutpointTxid { | ||||||||||||||||||||||
| var txidTuple: ( | ||||||||||||||||||||||
| UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, | ||||||||||||||||||||||
| UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, | ||||||||||||||||||||||
| UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, | ||||||||||||||||||||||
| UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8 | ||||||||||||||||||||||
| ) = ( | ||||||||||||||||||||||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||||||||||||||||||||||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| txid.withUnsafeBytes { src in | ||||||||||||||||||||||
| Swift.withUnsafeMutableBytes(of: &txidTuple) { dst in | ||||||||||||||||||||||
| dst.copyMemory(from: src) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| var outPoint = OutPointFFI(txid: txidTuple, vout: preparedOutpointVout) | ||||||||||||||||||||||
| result = recipientAddress.withUnsafeBytes { addr in | ||||||||||||||||||||||
| platform_address_wallet_quote_funding_fee( | ||||||||||||||||||||||
| handle, | ||||||||||||||||||||||
| addr.bindMemory(to: UInt8.self).baseAddress, | ||||||||||||||||||||||
| UInt(recipientAddress.count), | ||||||||||||||||||||||
| &outPoint, | ||||||||||||||||||||||
| userFeeIncrease, | ||||||||||||||||||||||
| "e | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| result = recipientAddress.withUnsafeBytes { addr in | ||||||||||||||||||||||
| platform_address_wallet_quote_funding_fee( | ||||||||||||||||||||||
| handle, | ||||||||||||||||||||||
| addr.bindMemory(to: UInt8.self).baseAddress, | ||||||||||||||||||||||
| UInt(recipientAddress.count), | ||||||||||||||||||||||
| nil, | ||||||||||||||||||||||
| userFeeIncrease, | ||||||||||||||||||||||
| "e | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| try result.check() | ||||||||||||||||||||||
| return AddressFundingFeeQuote( | ||||||||||||||||||||||
| estimatedFeeCredits: quote.estimated_fee_credits, | ||||||||||||||||||||||
| minimumRequiredLockCredits: quote.minimum_required_lock_credits, | ||||||||||||||||||||||
| protocolVersion: quote.protocol_version, | ||||||||||||||||||||||
| stateHeight: quote.state_height | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| }.value | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
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_outpointin 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 exactlyraw_txid || vout.to_le_bytes(). This will catch accidental txid reversal or native-endian vout serialization without requiring a network-backed wallet.source: ['codex']