Skip to content
Open
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions wallet-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub enum WalletFfiError {
InvalidTypeConversion = 15,
/// Invalid Key value.
InvalidKeyValue = 16,
/// Invalid argument value.
InvalidArgument = 17,
/// Internal error (catch-all).
InternalError = 99,
}
Expand Down
101 changes: 101 additions & 0 deletions wallet-ffi/src/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,107 @@ pub unsafe extern "C" fn wallet_ffi_transfer_public(
}
}

/// Send an arbitrary public transaction to a program.
///
/// Builds a `PublicTransaction` from the given program, accounts, and instruction data,
/// signs it with the signer's key, and submits to the sequencer.
///
/// # Parameters
/// - `handle`: Valid wallet handle
/// - `program_id`: 32-byte program ID
/// - `accounts`: Pointer to array of 32-byte account IDs
/// - `num_accounts`: Number of accounts in the array
/// - `instruction_data`: Pointer to raw instruction bytes (Vec<u32> serialized as LE bytes)
/// - `instruction_len`: Length of instruction data in bytes (must be multiple of 4)
/// - `signer`: Signer account ID (must be owned by this wallet)
/// - `out_result`: Output pointer for transfer result
///
/// # Safety
/// All pointers must be valid. `instruction_len` must be a multiple of 4.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_send_public_transaction(
handle: *mut WalletHandle,
program_id: *const FfiBytes32,
accounts: *const FfiBytes32,
num_accounts: usize,
instruction_data: *const u8,
instruction_len: usize,
signer: *const FfiBytes32,
out_result: *mut FfiTransferResult,
) -> WalletFfiError {
let wrapper = match get_wallet(handle) {
Ok(w) => w,
Err(e) => return e,
};

if program_id.is_null()
|| accounts.is_null()
|| instruction_data.is_null()
|| signer.is_null()
|| out_result.is_null()
{
print_error("Null pointer argument");
return WalletFfiError::NullPointer;
}

if instruction_len % 4 != 0 {
print_error("instruction_len must be a multiple of 4");
return WalletFfiError::InvalidArgument;
}

let wallet = match wrapper.core.lock() {
Ok(w) => w,
Err(e) => {
print_error(format!("Failed to lock wallet: {e}"));
return WalletFfiError::InternalError;
}
};

let pid_bytes = unsafe { (*program_id).data };
let mut pid: nssa_core::program::ProgramId = [0u32; 8];
for i in 0..8 {
pid[i] = u32::from_le_bytes([
pid_bytes[i * 4],
pid_bytes[i * 4 + 1],
pid_bytes[i * 4 + 2],
pid_bytes[i * 4 + 3],
]);
}
let signer_id = AccountId::new(unsafe { (*signer).data });

let account_ids: Vec<AccountId> = (0..num_accounts)
.map(|i| AccountId::new(unsafe { (*accounts.add(i)).data }))
.collect();

// Convert raw bytes to Vec<u32> (LE)
let instr_slice = unsafe { std::slice::from_raw_parts(instruction_data, instruction_len) };
let instr_u32: Vec<u32> = instr_slice
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();

match block_on(wallet.send_public_transaction(pid, account_ids, instr_u32, signer_id)) {
Ok(tx_hash) => {
let tx_hash = CString::new(tx_hash.to_string())
.map_or(ptr::null_mut(), std::ffi::CString::into_raw);

unsafe {
(*out_result).tx_hash = tx_hash;
(*out_result).success = true;
}
WalletFfiError::Success
}
Err(e) => {
print_error(format!("Transaction failed: {e:?}"));
unsafe {
(*out_result).tx_hash = ptr::null_mut();
(*out_result).success = false;
}
map_execution_error(e)
}
}
}

/// Send a shielded token transfer.
///
/// Transfers tokens from a public account to a private account.
Expand Down
32 changes: 32 additions & 0 deletions wallet-ffi/wallet_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ typedef enum WalletFfiError {
* Invalid Key value.
*/
INVALID_KEY_VALUE = 16,
/**
* Invalid argument value.
*/
INVALID_ARGUMENT = 17,
/**
* Internal error (catch-all).
*/
Expand Down Expand Up @@ -676,6 +680,34 @@ enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle,
const uint8_t (*amount)[16],
struct FfiTransferResult *out_result);

/**
* Send an arbitrary public transaction to a program.
*
* Builds a `PublicTransaction` from the given program, accounts, and instruction data,
* signs it with the signer's key, and submits to the sequencer.
*
* # Parameters
* - `handle`: Valid wallet handle
* - `program_id`: 32-byte program ID
* - `accounts`: Pointer to array of 32-byte account IDs
* - `num_accounts`: Number of accounts in the array
* - `instruction_data`: Pointer to raw instruction bytes (Vec<u32> serialized as LE bytes)
* - `instruction_len`: Length of instruction data in bytes (must be multiple of 4)
* - `signer`: Signer account ID (must be owned by this wallet)
* - `out_result`: Output pointer for transfer result
*
* # Safety
* All pointers must be valid. `instruction_len` must be a multiple of 4.
*/
enum WalletFfiError wallet_ffi_send_public_transaction(struct WalletHandle *handle,
const struct FfiBytes32 *program_id,
const struct FfiBytes32 *accounts,
uintptr_t num_accounts,
const uint8_t *instruction_data,
uintptr_t instruction_len,
const struct FfiBytes32 *signer,
struct FfiTransferResult *out_result);

/**
* Send a shielded token transfer.
*
Expand Down
35 changes: 35 additions & 0 deletions wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,41 @@ impl WalletCore {
.get_pub_account_signing_key(account_id)
}

/// Send an arbitrary public transaction to a program.
pub async fn send_public_transaction(
&self,
program_id: nssa_core::program::ProgramId,
account_ids: Vec<AccountId>,
instruction_data: InstructionData,
signer: AccountId,
) -> Result<HashType, ExecutionFailureKind> {
let nonces = self
.get_accounts_nonces(vec![signer])
.await
.map_err(ExecutionFailureKind::SequencerError)?;

let message = nssa::public_transaction::Message::new_preserialized(
program_id,
account_ids,
nonces,
instruction_data,
);

let signing_key = self.storage.user_data.get_pub_account_signing_key(signer);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};

let witness_set =
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let tx = nssa::PublicTransaction::new(message, witness_set);

Ok(self
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
}

#[must_use]
pub fn get_account_private(&self, account_id: AccountId) -> Option<Account> {
self.storage
Expand Down