Skip to content
Merged
382 changes: 382 additions & 0 deletions packages/rs-platform-wallet-ffi/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,385 @@ pub unsafe extern "C" fn platform_wallet_create_document_with_signer(
*out_document_json = json_cstring.into_raw();
PlatformWalletFFIResult::ok()
}

/// Serialize a confirmed `Document` to its canonical query-side JSON
/// string, the same representation the create path returns.
///
/// `to_json_with_identifiers_using_bytes` renders `$id`/`$ownerId`
/// (and `$creatorId`) as base58 strings and emits only the populated
/// system fields — the shape a DOC-01 query display expects. Swift
/// persists this verbatim so the local cache matches the on-chain
/// document. The trait's `platform_version` argument is unused by the
/// V0 impl, so `latest()` (the convention the other entry points in
/// this crate use) is safe here.
fn confirmed_document_to_json(document: &Document) -> Result<String, PlatformWalletError> {
let platform_version = PlatformVersion::latest();
let json_value = document
.to_json_with_identifiers_using_bytes(platform_version)
.map_err(|e| {
PlatformWalletError::InvalidIdentityData(format!(
"Failed to convert confirmed document to JSON: {e}"
))
})?;
serde_json::to_string(&json_value).map_err(|e| {
PlatformWalletError::InvalidIdentityData(format!(
"Failed to serialize confirmed document JSON: {e}"
))
})
}

/// Replace + broadcast `document_id`'s properties on `contract_id`'s
/// `document_type_name`, owned by `owner_identity_id`, signed via the
/// external `signer_handle` with key `signing_key_id`.
///
/// Goes through `IdentityWallet::replace_document_with_signer`, which
/// fetches the current document, applies `properties_json` (schema-
/// sanitized), bumps the revision, validates `signing_key_id` is an
/// AUTHENTICATION + ECDSA key on the owner, broadcasts on the
/// platform-wallet 8 MB worker stack, and waits for the confirmed
/// document.
///
/// On success the confirmed document's 32-byte id is written to
/// `out_document_id`, and a NUL-terminated, owned canonical-document
/// JSON string is written to `*out_document_json` (release with
/// `platform_wallet_string_free`). On any error `*out_document_json`
/// is left null. `properties_json` is the full replacement property
/// object, same hex/base58 encoding rules as the create path.
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub unsafe extern "C" fn platform_wallet_document_replace(
wallet_handle: Handle,
owner_identity_id: *const u8,
contract_id: *const u8,
document_type_name: *const c_char,
document_id: *const u8,
properties_json: *const c_char,
signing_key_id: u32,
signer_handle: *mut SignerHandle,
out_document_id: *mut u8,
out_document_json: *mut *mut c_char,
) -> PlatformWalletFFIResult {
check_ptr!(signer_handle);
check_ptr!(document_type_name);
check_ptr!(properties_json);
check_ptr!(out_document_id);
check_ptr!(out_document_json);
*out_document_json = ptr::null_mut();

let owner_id = unwrap_result_or_return!(read_identifier(owner_identity_id));
let contract_id_value = unwrap_result_or_return!(read_identifier(contract_id));
let document_id_value = unwrap_result_or_return!(read_identifier(document_id));

let document_type_str =
unwrap_result_or_return!(CStr::from_ptr(document_type_name).to_str()).to_string();
let properties_str = unwrap_result_or_return!(CStr::from_ptr(properties_json).to_str());

let signer_addr = signer_handle as usize;

let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| {
let identity_wallet = wallet.identity().clone();
let result: Result<(Identifier, String), PlatformWalletError> =
block_on_worker(async move {
let signer: &VTableSigner = &*(signer_addr as *const VTableSigner);
let confirmed: Document = identity_wallet
.replace_document_with_signer(
&owner_id,
&contract_id_value,
&document_type_str,
&document_id_value,
properties_str,
signing_key_id,
signer,
)
.await?;
let json_string = confirmed_document_to_json(&confirmed)?;
Ok::<_, PlatformWalletError>((confirmed.id(), json_string))
});
result
});
let result = unwrap_option_or_return!(option);
let (confirmed_id, document_json) = unwrap_result_or_return!(result);

let json_cstring = unwrap_result_or_return!(CString::new(document_json));
let bytes = confirmed_id.to_buffer();
let dst = slice::from_raw_parts_mut(out_document_id, 32);
dst.copy_from_slice(&bytes);
*out_document_json = json_cstring.into_raw();
PlatformWalletFFIResult::ok()
}

/// Delete + broadcast `document_id` on `contract_id`'s
/// `document_type_name`, owned by `owner_identity_id`, signed via the
/// external `signer_handle` with key `signing_key_id`.
///
/// Goes through `IdentityWallet::delete_document_with_signer`. On
/// success the deleted document's 32-byte id is written to
/// `out_document_id`. Delete returns no document body, so there is no
/// JSON out-param — Swift removes the local row by id.
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub unsafe extern "C" fn platform_wallet_document_delete(
wallet_handle: Handle,
owner_identity_id: *const u8,
contract_id: *const u8,
document_type_name: *const c_char,
document_id: *const u8,
signing_key_id: u32,
signer_handle: *mut SignerHandle,
out_document_id: *mut u8,
) -> PlatformWalletFFIResult {
check_ptr!(signer_handle);
check_ptr!(document_type_name);
check_ptr!(out_document_id);

let owner_id = unwrap_result_or_return!(read_identifier(owner_identity_id));
let contract_id_value = unwrap_result_or_return!(read_identifier(contract_id));
let document_id_value = unwrap_result_or_return!(read_identifier(document_id));

let document_type_str =
unwrap_result_or_return!(CStr::from_ptr(document_type_name).to_str()).to_string();

let signer_addr = signer_handle as usize;

let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| {
let identity_wallet = wallet.identity().clone();
let result: Result<Identifier, PlatformWalletError> = block_on_worker(async move {
let signer: &VTableSigner = &*(signer_addr as *const VTableSigner);
let deleted_id: Identifier = identity_wallet
.delete_document_with_signer(
&owner_id,
&contract_id_value,
&document_type_str,
&document_id_value,
signing_key_id,
signer,
)
.await?;
Ok::<_, PlatformWalletError>(deleted_id)
});
result
});
let result = unwrap_option_or_return!(option);
let deleted_id = unwrap_result_or_return!(result);

let bytes = deleted_id.to_buffer();
let dst = slice::from_raw_parts_mut(out_document_id, 32);
dst.copy_from_slice(&bytes);
PlatformWalletFFIResult::ok()
}

/// Transfer + broadcast `document_id` on `contract_id`'s
/// `document_type_name`, from `owner_identity_id` to `recipient_id`,
/// signed via the external `signer_handle` with key `signing_key_id`.
///
/// Goes through `IdentityWallet::transfer_document_with_signer`. On
/// success the confirmed document's 32-byte id is written to
/// `out_document_id` and its canonical JSON (now reflecting the new
/// owner) to `*out_document_json` (release with
/// `platform_wallet_string_free`). On any error `*out_document_json`
/// is left null.
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub unsafe extern "C" fn platform_wallet_document_transfer(
wallet_handle: Handle,
owner_identity_id: *const u8,
contract_id: *const u8,
document_type_name: *const c_char,
document_id: *const u8,
recipient_id: *const u8,
signing_key_id: u32,
signer_handle: *mut SignerHandle,
out_document_id: *mut u8,
out_document_json: *mut *mut c_char,
) -> PlatformWalletFFIResult {
check_ptr!(signer_handle);
check_ptr!(document_type_name);
check_ptr!(out_document_id);
check_ptr!(out_document_json);
*out_document_json = ptr::null_mut();

let owner_id = unwrap_result_or_return!(read_identifier(owner_identity_id));
let contract_id_value = unwrap_result_or_return!(read_identifier(contract_id));
let document_id_value = unwrap_result_or_return!(read_identifier(document_id));
let recipient_id_value = unwrap_result_or_return!(read_identifier(recipient_id));

let document_type_str =
unwrap_result_or_return!(CStr::from_ptr(document_type_name).to_str()).to_string();

let signer_addr = signer_handle as usize;

let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| {
let identity_wallet = wallet.identity().clone();
let result: Result<(Identifier, String), PlatformWalletError> =
block_on_worker(async move {
let signer: &VTableSigner = &*(signer_addr as *const VTableSigner);
let confirmed: Document = identity_wallet
.transfer_document_with_signer(
&owner_id,
&contract_id_value,
&document_type_str,
&document_id_value,
&recipient_id_value,
signing_key_id,
signer,
)
.await?;
let json_string = confirmed_document_to_json(&confirmed)?;
Ok::<_, PlatformWalletError>((confirmed.id(), json_string))
});
result
});
let result = unwrap_option_or_return!(option);
let (confirmed_id, document_json) = unwrap_result_or_return!(result);

let json_cstring = unwrap_result_or_return!(CString::new(document_json));
let bytes = confirmed_id.to_buffer();
let dst = slice::from_raw_parts_mut(out_document_id, 32);
dst.copy_from_slice(&bytes);
*out_document_json = json_cstring.into_raw();
PlatformWalletFFIResult::ok()
}

/// Set (update) the trade price of `document_id` on `contract_id`'s
/// `document_type_name`, owned by `owner_identity_id`, to `price`
/// credits — signed via the external `signer_handle` with key
/// `signing_key_id`.
///
/// Goes through `IdentityWallet::set_document_price_with_signer`. On
/// success the confirmed document's 32-byte id is written to
/// `out_document_id` and its canonical JSON (now carrying `$price`) to
/// `*out_document_json` (release with `platform_wallet_string_free`).
/// On any error `*out_document_json` is left null.
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub unsafe extern "C" fn platform_wallet_document_set_price(
wallet_handle: Handle,
owner_identity_id: *const u8,
contract_id: *const u8,
document_type_name: *const c_char,
document_id: *const u8,
price: u64,
signing_key_id: u32,
signer_handle: *mut SignerHandle,
out_document_id: *mut u8,
out_document_json: *mut *mut c_char,
) -> PlatformWalletFFIResult {
check_ptr!(signer_handle);
check_ptr!(document_type_name);
check_ptr!(out_document_id);
check_ptr!(out_document_json);
*out_document_json = ptr::null_mut();

let owner_id = unwrap_result_or_return!(read_identifier(owner_identity_id));
let contract_id_value = unwrap_result_or_return!(read_identifier(contract_id));
let document_id_value = unwrap_result_or_return!(read_identifier(document_id));

let document_type_str =
unwrap_result_or_return!(CStr::from_ptr(document_type_name).to_str()).to_string();

let signer_addr = signer_handle as usize;

let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| {
let identity_wallet = wallet.identity().clone();
let result: Result<(Identifier, String), PlatformWalletError> =
block_on_worker(async move {
let signer: &VTableSigner = &*(signer_addr as *const VTableSigner);
let confirmed: Document = identity_wallet
.set_document_price_with_signer(
&owner_id,
&contract_id_value,
&document_type_str,
&document_id_value,
price,
signing_key_id,
signer,
)
.await?;
let json_string = confirmed_document_to_json(&confirmed)?;
Ok::<_, PlatformWalletError>((confirmed.id(), json_string))
});
result
});
let result = unwrap_option_or_return!(option);
let (confirmed_id, document_json) = unwrap_result_or_return!(result);

let json_cstring = unwrap_result_or_return!(CString::new(document_json));
let bytes = confirmed_id.to_buffer();
let dst = slice::from_raw_parts_mut(out_document_id, 32);
dst.copy_from_slice(&bytes);
*out_document_json = json_cstring.into_raw();
PlatformWalletFFIResult::ok()
}

/// Purchase + broadcast for-sale `document_id` on `contract_id`'s
/// `document_type_name` for `price` credits, with `purchaser_id` as
/// the buyer (and new owner) — signed via the external `signer_handle`
/// with key `signing_key_id` (resolved on the purchaser).
///
/// Goes through `IdentityWallet::purchase_document_with_signer`. On
/// success the confirmed document's 32-byte id is written to
/// `out_document_id` and its canonical JSON (now owned by the
/// purchaser) to `*out_document_json` (release with
/// `platform_wallet_string_free`). On any error `*out_document_json`
/// is left null. The buyer must differ from the current owner — the
/// caller gates against the self-buy consensus rejection.
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub unsafe extern "C" fn platform_wallet_document_purchase(
wallet_handle: Handle,
purchaser_id: *const u8,
contract_id: *const u8,
document_type_name: *const c_char,
document_id: *const u8,
price: u64,
signing_key_id: u32,
signer_handle: *mut SignerHandle,
out_document_id: *mut u8,
out_document_json: *mut *mut c_char,
) -> PlatformWalletFFIResult {
check_ptr!(signer_handle);
check_ptr!(document_type_name);
check_ptr!(out_document_id);
check_ptr!(out_document_json);
*out_document_json = ptr::null_mut();

let purchaser_id_value = unwrap_result_or_return!(read_identifier(purchaser_id));
let contract_id_value = unwrap_result_or_return!(read_identifier(contract_id));
let document_id_value = unwrap_result_or_return!(read_identifier(document_id));

let document_type_str =
unwrap_result_or_return!(CStr::from_ptr(document_type_name).to_str()).to_string();

let signer_addr = signer_handle as usize;

let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| {
let identity_wallet = wallet.identity().clone();
let result: Result<(Identifier, String), PlatformWalletError> =
block_on_worker(async move {
let signer: &VTableSigner = &*(signer_addr as *const VTableSigner);
let confirmed: Document = identity_wallet
.purchase_document_with_signer(
&purchaser_id_value,
&contract_id_value,
&document_type_str,
&document_id_value,
price,
signing_key_id,
signer,
)
.await?;
let json_string = confirmed_document_to_json(&confirmed)?;
Ok::<_, PlatformWalletError>((confirmed.id(), json_string))
});
result
});
let result = unwrap_option_or_return!(option);
let (confirmed_id, document_json) = unwrap_result_or_return!(result);

let json_cstring = unwrap_result_or_return!(CString::new(document_json));
let bytes = confirmed_id.to_buffer();
let dst = slice::from_raw_parts_mut(out_document_id, 32);
dst.copy_from_slice(&bytes);
*out_document_json = json_cstring.into_raw();
PlatformWalletFFIResult::ok()
}
Loading
Loading