How to do basic transfer SUI with Rust SDK?

Hi, I’m using current 1.20 version on local, and having the same issue visible just in wallet (code response is fine): “CommandArgumentError {arg_idx: 1, InvalidBCSBytes } in command 1”.

The behavior is the same on localnet/devnet. I can see gas fee is removed, but SUI is not transferred. The only error is visible in wallet Activities.

use shared_crypto::intent::Intent;
use std::env;
use std::str::FromStr;
use sui_keys::keystore::{AccountKeystore, FileBasedKeystore};
use sui_sdk::{
    rpc_types::{SuiObjectDataOptions, SuiObjectResponseQuery, SuiTransactionBlockResponseOptions},
    types::{
        base_types::SuiAddress,
        programmable_transaction_builder::ProgrammableTransactionBuilder,
        quorum_driver_types::ExecuteTransactionRequestType,
        transaction::{Argument, Command, Transaction, TransactionData},
    },
    SuiClientBuilder,
};

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    // Read the local key store as provided by the path
    let args: Vec<String> = env::args().collect();
    let sui_keystore = &args[1];
    let keystore = FileBasedKeystore::new(&sui_keystore.into())?;

    // Sui local network
    let sui_local = SuiClientBuilder::default()
        .build("https://fullnode.devnet.sui.io:443")
        //.build("http://127.0.0.1:9000")
        .await?;
    println!("Sui local version: {}", sui_local.api_version());

    // Pick the first PK from keystore file and create SUI address
    let my_address = keystore.addresses()[0];
    println!("My address: {}", my_address);

    // Get all my own objects
    let coins_response = &sui_local
        .read_api()
        .get_owned_objects(
            my_address,
            Some(SuiObjectResponseQuery::new_with_options(
                SuiObjectDataOptions::new().with_type(),
            )),
            None,
            None,
        )
        .await?;
    println!("coins: {:?}", coins_response);

    // Find a coin to use
    let coin = coins_response
        .data
        .iter()
        .find(|obj| obj.data.as_ref().unwrap().is_gas_coin())
        .unwrap();
    let coin = coin.data.as_ref().unwrap();
    println!("coin: {:?}", coin);

    // Make a Programmable Transaction Block with 1 transaction inside
    let mut ptb = ProgrammableTransactionBuilder::new();

    // 2) split coin
    // the amount we want in the new coin, 1000 MIST
    let split_coint_amount = ptb.pure(1000u64)?; // note that we need to specify the u64 type
    ptb.command(Command::SplitCoins(
        Argument::GasCoin,
        vec![split_coint_amount],
    ));

    let recipient = "0x8d5fbe4b69445fbe7dd11133b8221e2f1da482c3e751fbee218cc4953e84de8e";

    // 3) transfer the new coin to a different address
    let argument_address = ptb.pure(recipient)?;
    ptb.command(Command::TransferObjects(
        vec![Argument::Result(0)],
        argument_address,
    ));

    // finish building the transaction block by calling finish on the ptb
    let builder = ptb.finish();

    // Build the transaction data
    let gas_budget: u64 = 5_000_000;
    let gas_price: u64 = sui_local.read_api().get_reference_gas_price().await?;
    // create the transaction data that will be sent to the network
    let tx_data = TransactionData::new_programmable(
        my_address,
        vec![coin.object_ref()],
        builder,
        gas_budget,
        gas_price,
    );

    // Sign the transaction
    let signature = keystore.sign_secure(&my_address, &tx_data, Intent::sui_transaction())?;

    // 5) execute the transaction
    print!("Executing the transaction...");
    let transaction_response = sui_local
        .quorum_driver_api()
        .execute_transaction_block(
            Transaction::from_data(tx_data, vec![signature]),
            SuiTransactionBlockResponseOptions::full_content(),
            Some(ExecuteTransactionRequestType::WaitForLocalExecution),
        )
        .await?;
    print!("done\n Transaction information: ");
    println!("{:?}", transaction_response);

    match SuiAddress::from_str(recipient) {
        Ok(address) => {
            let coins = sui_local
                .coin_read_api()
                .get_coins(address, None, None, None)
                .await?;
            println!(
                "After the transfer, the recipient address {recipient} has {} coins",
                coins.data.len()
            );
        }
        Err(err) => {
            eprintln!("Error parsing SuiAddress: {}", err);
            // Handle the error...
        }
    }

    Ok(())
}

Code output without any issue with successful completion:
sui@server:~/project/sandbox$ cargo run --bin 07_transaction sui.keystore
Compiling sui-by-example v0.1.0 (sandbox)
Finished dev [unoptimized + debuginfo] target(s) in 22.89s
Running target/debug/07_transaction sui.keystore
Sui local version: 1.21.0
My address: 0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2
coins: Page { data: [SuiObjectResponse { data: Some(SuiObjectData { object_id: 0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759, version: SequenceNumber(17), digest: o#ES3YKaQPagGNxjGNC1kqG8Jagj15Wt1sfKXduBPfc1du, type_: Some(Struct(MoveObjectType(GasCoin))), owner: None, previous_transaction: None, storage_rebate: None, display: None, content: None, bcs: None }), error: None }], next_cursor: Some(0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759), has_next_page: false }
coin: SuiObjectData { object_id: 0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759, version: SequenceNumber(17), digest: o#ES3YKaQPagGNxjGNC1kqG8Jagj15Wt1sfKXduBPfc1du, type_: Some(Struct(MoveObjectType(GasCoin))), owner: None, previous_transaction: None, storage_rebate: None, display: None, content: None, bcs: None }
Executing the transaction…done
Transaction information: SuiTransactionBlockResponse { digest: TransactionDigest(9UYu6YyNBdvZZ6iHjTJQrNWzLgtxcFDeZzPaHPcjZT1V), transaction: Some(SuiTransactionBlock { data: V1(SuiTransactionBlockDataV1 { transaction: ProgrammableTransaction(SuiProgrammableTransactionBlock { inputs: [Pure(SuiPureValue { value_type: Some(U64), value: “1000” }), Pure(SuiPureValue { value_type: Some(Address), value: [66,48,120,56,100,53,102,98,101,52,98,54,57,52,52,53,102,98,101,55,100,100,49,49,49,51,51,98,56,50,50,49,101,50,102,49,100,97,52,56,50,99,51,101,55,53,49,102,98,101,101,50,49,56,99,99,52,57,53,51,101,56,52,100,101,56,101] })], commands: [SplitCoins(GasCoin, [Input(0)]), TransferObjects([Result(0)], Input(1))] }), sender: 0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2, gas_data: SuiGasData { payment: [SuiObjectRef { object_id: 0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759, version: SequenceNumber(17), digest: o#ES3YKaQPagGNxjGNC1kqG8Jagj15Wt1sfKXduBPfc1du }], owner: 0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2, price: 1000, budget: 5000000 } }), tx_signatures: [Signature(Ed25519SuiSignature(Ed25519SuiSignature([0, 219, 138, 143, 255, 93, 26, 78, 30, 41, 106, 33, 42, 173, 76, 186, 176, 136, 76, 167, 75, 131, 152, 95, 180, 243, 176, 148, 195, 41, 129, 43, 193, 104, 209, 184, 11, 159, 9, 150, 98, 198, 129, 52, 116, 42, 55, 247, 246, 90, 201, 81, 216, 199, 158, 108, 124, 14, 133, 234, 21, 91, 53, 69, 1, 244, 188, 104, 239, 139, 74, 94, 196, 173, 61, 103, 209, 129, 143, 95, 50, 150, 167, 171, 81, 91, 155, 88, 97, 234, 15, 79, 205, 79, 120, 171, 30])))] }), raw_transaction: [1, 0, 0, 0, 0, 0, 2, 0, 8, 232, 3, 0, 0, 0, 0, 0, 0, 0, 67, 66, 48, 120, 56, 100, 53, 102, 98, 101, 52, 98, 54, 57, 52, 52, 53, 102, 98, 101, 55, 100, 100, 49, 49, 49, 51, 51, 98, 56, 50, 50, 49, 101, 50, 102, 49, 100, 97, 52, 56, 50, 99, 51, 101, 55, 53, 49, 102, 98, 101, 101, 50, 49, 56, 99, 99, 52, 57, 53, 51, 101, 56, 52, 100, 101, 56, 101, 2, 2, 0, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 1, 0, 150, 129, 129, 83, 53, 201, 182, 5, 98, 57, 243, 163, 242, 162, 206, 40, 172, 38, 135, 33, 38, 124, 15, 65, 10, 126, 241, 75, 108, 70, 146, 226, 1, 16, 119, 163, 1, 243, 26, 164, 53, 28, 135, 60, 22, 26, 233, 176, 82, 82, 138, 44, 64, 26, 73, 188, 117, 244, 204, 70, 100, 131, 248, 183, 89, 17, 0, 0, 0, 0, 0, 0, 0, 32, 199, 146, 82, 127, 194, 145, 153, 76, 187, 193, 193, 108, 194, 199, 192, 63, 207, 161, 174, 237, 76, 207, 12, 216, 0, 116, 116, 168, 38, 147, 112, 116, 150, 129, 129, 83, 53, 201, 182, 5, 98, 57, 243, 163, 242, 162, 206, 40, 172, 38, 135, 33, 38, 124, 15, 65, 10, 126, 241, 75, 108, 70, 146, 226, 232, 3, 0, 0, 0, 0, 0, 0, 64, 75, 76, 0, 0, 0, 0, 0, 0, 1, 97, 0, 219, 138, 143, 255, 93, 26, 78, 30, 41, 106, 33, 42, 173, 76, 186, 176, 136, 76, 167, 75, 131, 152, 95, 180, 243, 176, 148, 195, 41, 129, 43, 193, 104, 209, 184, 11, 159, 9, 150, 98, 198, 129, 52, 116, 42, 55, 247, 246, 90, 201, 81, 216, 199, 158, 108, 124, 14, 133, 234, 21, 91, 53, 69, 1, 244, 188, 104, 239, 139, 74, 94, 196, 173, 61, 103, 209, 129, 143, 95, 50, 150, 167, 171, 81, 91, 155, 88, 97, 234, 15, 79, 205, 79, 120, 171, 30], effects: Some(V1(SuiTransactionBlockEffectsV1 { status: Failure { error: “CommandArgumentError { arg_idx: 1, kind: InvalidBCSBytes } in command 1” }, executed_epoch: 49, gas_used: GasCostSummary { computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 }, modified_at_versions: [SuiTransactionBlockEffectsModifiedAtVersions { object_id: 0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759, sequence_number: SequenceNumber(17) }], shared_objects: , transaction_digest: TransactionDigest(9UYu6YyNBdvZZ6iHjTJQrNWzLgtxcFDeZzPaHPcjZT1V), created: , mutated: [OwnedObjectRef { owner: AddressOwner(0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2), reference: SuiObjectRef { object_id: 0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759, version: SequenceNumber(18), digest: o#4s7ipHPMfxLhswYrfmPkJRHtmEe7KWB5BzoLDMQeZiih } }], unwrapped: , deleted: , unwrapped_then_deleted: , wrapped: , gas_object: OwnedObjectRef { owner: AddressOwner(0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2), reference: SuiObjectRef { object_id: 0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759, version: SequenceNumber(18), digest: o#4s7ipHPMfxLhswYrfmPkJRHtmEe7KWB5BzoLDMQeZiih } }, events_digest: None, dependencies: [TransactionDigest(J9JvDJRmhHt2QJTEBjFr4LkQjSkJvFw5bKud4kDFMSX5)] })), events: Some(SuiTransactionBlockEvents { data: }), object_changes: Some([Mutated { sender: 0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2, owner: AddressOwner(0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2), object_type: StructTag { address: 0000000000000000000000000000000000000000000000000000000000000002, module: Identifier(“coin”), name: Identifier(“Coin”), type_params: [Struct(StructTag { address: 0000000000000000000000000000000000000000000000000000000000000002, module: Identifier(“sui”), name: Identifier(“SUI”), type_params: })] }, object_id: 0x1077a301f31aa4351c873c161ae9b052528a2c401a49bc75f4cc466483f8b759, version: SequenceNumber(18), previous_version: SequenceNumber(17), digest: o#4s7ipHPMfxLhswYrfmPkJRHtmEe7KWB5BzoLDMQeZiih }]), balance_changes: Some([BalanceChange { owner: AddressOwner(0x9681815335c9b6056239f3a3f2a2ce28ac268721267c0f410a7ef14b6c4692e2), coin_type: Struct(StructTag { address: 0000000000000000000000000000000000000000000000000000000000000002, module: Identifier(“sui”), name: Identifier(“SUI”), type_params: }), amount: -1009880 }]), timestamp_ms: None, confirmed_local_execution: Some(true), checkpoint: None, errors: }
After the transfer, the recipient address 0x8d5fbe4b69445fbe7dd11133b8221e2f1da482c3e751fbee218cc4953e84de8e has 2 coins

Thanks for a hint!!!

2 Likes

I guess the issue is in signing and commit as it should be for SUI.

1 Like

It looks like this forum is not very Rust-focused, so I have tried my best and put together an article which could help developers solve similar examples and enter the world of SUI from basic transactions: search on Medium janrock or an article called: "sui blockchain how to send transaction with sui in rust 1.
Thanks!