From ab1351df1d5cd81598177f47864915d56b857115 Mon Sep 17 00:00:00 2001 From: musitdev Date: Thu, 18 Jun 2026 16:22:46 +0200 Subject: [PATCH 1/6] feat(confidential-assets): add set_chain_auditor, set_asset_auditor SDK calls and set_auditor_example - add set_chain_auditor and set_asset_auditor to transaction builder and public API - expose get_chain_auditor_encryption_key on the public ConfidentialAsset API - add set_auditor_example demonstrating chain and asset auditor set/verify/clear flows --- crates/confidential-assets/Cargo.toml | 4 + .../examples/set_auditor_example.rs | 188 ++++++++++++++++++ .../src/api/confidential_asset.rs | 30 +++ .../src/internal/transaction_builder.rs | 46 +++++ 4 files changed, 268 insertions(+) create mode 100644 crates/confidential-assets/examples/set_auditor_example.rs diff --git a/crates/confidential-assets/Cargo.toml b/crates/confidential-assets/Cargo.toml index b7ff11e..57a9101 100644 --- a/crates/confidential-assets/Cargo.toml +++ b/crates/confidential-assets/Cargo.toml @@ -18,6 +18,10 @@ e2e = [] name = "simple_ca_transfer" required-features = ["e2e"] +[[example]] +name = "set_auditor_example" +required-features = ["e2e"] + [dependencies] movement-sdk = { workspace = true, features = ["twisted_ed25519", "ed25519", "faucet"] } aptos-bcs = { workspace = true } diff --git a/crates/confidential-assets/examples/set_auditor_example.rs b/crates/confidential-assets/examples/set_auditor_example.rs new file mode 100644 index 0000000..58bce43 --- /dev/null +++ b/crates/confidential-assets/examples/set_auditor_example.rs @@ -0,0 +1,188 @@ +// Example: Set (and clear) chain-level and per-asset auditors on confidential assets +// +// Demonstrates two independent auditor flows: +// +// A. Chain auditor — applies to every confidential transfer on the network: +// 1. Set the chain auditor EK (signed by the chain auditor admin) +// 2. Read it back from chain to verify +// 3. Clear it (signed by the chain auditor admin) +// +// B. Asset auditor — applies only to transfers of a specific FA token: +// 1. Set the asset auditor EK (signed by the FA root owner / issuer) +// 2. Read it back from chain to verify +// 3. Clear it (signed by the issuer) +// +// Prerequisites: +// - A. The chain auditor admin must already be assigned via the governance Move script +// (see deploy/scripts/setup_chain_auditor.move). Provide that admin's private key +// in CHAIN_AUDITOR_ADMIN_PRIVATE_KEY. +// - B. A fungible asset token whose root owner you control. +// +// Environment variables: +// CHAIN_AUDITOR_ADMIN_PRIVATE_KEY — hex Ed25519 private key of the chain auditor admin (required for A) +// TOKEN_ADDRESS — hex address of the FA metadata object (required for B) +// ISSUER_PRIVATE_KEY — hex Ed25519 private key of the FA root owner (required for B) +// MOVEMENT_NETWORK — TESTNET | MAINNET | LOCAL (optional, default LOCAL) +// MOVEMENT_RPC_URL — custom RPC endpoint, used when MOVEMENT_NETWORK is not set +// CONFIDENTIAL_MODULE_ADDRESS — defaults to 0x1 +// +// Run: +// CHAIN_AUDITOR_ADMIN_PRIVATE_KEY= TOKEN_ADDRESS=0x ISSUER_PRIVATE_KEY= \ +// cargo run -p confidential-assets --example set_auditor_example --features confidential-assets/e2e + +use confidential_assets::api::ConfidentialAsset; +use confidential_assets::crypto::twisted_ed25519::TwistedEd25519PrivateKey; +use movement_sdk::{ + Movement, MovementConfig, MovementError, MovementResult, + account::Ed25519Account, + types::AccountAddress, +}; +use std::env; + +#[tokio::main] +async fn main() -> MovementResult<()> { + println!("=== Set Auditor Example ===\n"); + + // ── 1. Build the Movement client ───────────────────────────────────────── + let config = match env::var("MOVEMENT_NETWORK") { + Ok(network) => match network.to_uppercase().as_str() { + "TESTNET" => MovementConfig::testnet(), + "MAINNET" => MovementConfig::mainnet(), + _ => MovementConfig::local(), + }, + Err(_) => match env::var("MOVEMENT_RPC_URL") { + Ok(url) => MovementConfig::custom(&url)?, + Err(_) => MovementConfig::local(), + }, + }; + let movement = Movement::new(config)?; + println!("Connected to Movement network.\n"); + + let module_address = + env::var("CONFIDENTIAL_MODULE_ADDRESS").unwrap_or_else(|_| "0x1".to_string()); + let ca = ConfidentialAsset::new(&movement, Some(&module_address))?; + + // ── 2. Generate a shared auditor key pair ───────────────────────────────── + // The same dk/ek pair is reused for both the chain and asset auditor sections. + let auditor_dk = TwistedEd25519PrivateKey::generate(); + let auditor_ek = auditor_dk.public_key(); + println!("Generated auditor decryption key (dk): {}", hex::encode(auditor_dk.to_bytes())); + println!("Generated auditor encryption key (ek): {}", hex::encode(auditor_ek.to_bytes())); + println!(); + + // ════════════════════════════════════════════════════════════════════════ + // A. Chain auditor + // ════════════════════════════════════════════════════════════════════════ + + let chain_admin_key_hex = env::var("CHAIN_AUDITOR_ADMIN_PRIVATE_KEY").map_err(|_| { + MovementError::Internal( + "CHAIN_AUDITOR_ADMIN_PRIVATE_KEY is required (hex Ed25519 key of the chain auditor admin)".to_string(), + ) + })?; + let chain_admin = Ed25519Account::from_private_key_hex(&chain_admin_key_hex) + .map_err(|e| MovementError::Internal(format!("invalid CHAIN_AUDITOR_ADMIN_PRIVATE_KEY: {e}")))?; + + println!("── A. Chain auditor ─────────────────────────────────────────────"); + println!("Chain auditor admin: {}", chain_admin.address()); + + let current_chain = ca.get_chain_auditor_encryption_key().await?; + println!( + "Current chain auditor EK: {}", + current_chain + .as_ref() + .map(|ek| hex::encode(ek.to_bytes())) + .unwrap_or_else(|| "(none)".to_string()) + ); + + // Set + println!("Setting chain auditor EK …"); + let payload = ca.set_chain_auditor(Some(&auditor_ek))?; + movement.sign_submit_and_wait(&chain_admin, payload, None).await?; + + let stored_chain = ca + .get_chain_auditor_encryption_key() + .await? + .ok_or_else(|| MovementError::Internal("Chain auditor EK not found after set".to_string()))?; + assert_eq!( + stored_chain.to_bytes(), + auditor_ek.to_bytes(), + "Stored chain auditor EK does not match" + ); + println!(" Verified — stored EK: {}", hex::encode(stored_chain.to_bytes())); + + // Clear + println!("Clearing chain auditor EK …"); + let payload = ca.set_chain_auditor(None)?; + movement.sign_submit_and_wait(&chain_admin, payload, None).await?; + + assert!( + ca.get_chain_auditor_encryption_key().await?.is_none(), + "Chain auditor EK should be None after clearing" + ); + println!(" Verified — chain auditor EK is cleared."); + println!(); + + // ════════════════════════════════════════════════════════════════════════ + // B. Asset auditor + // ════════════════════════════════════════════════════════════════════════ + + let token_raw = env::var("TOKEN_ADDRESS").map_err(|_| { + MovementError::Internal( + "TOKEN_ADDRESS is required (hex address of the FA metadata object)".to_string(), + ) + })?; + let token = AccountAddress::from_hex(&token_raw) + .map_err(|e| MovementError::Internal(format!("invalid TOKEN_ADDRESS: {e}")))?; + + let issuer_key_hex = env::var("ISSUER_PRIVATE_KEY").map_err(|_| { + MovementError::Internal( + "ISSUER_PRIVATE_KEY is required (hex Ed25519 key of the FA root owner)".to_string(), + ) + })?; + let issuer = Ed25519Account::from_private_key_hex(&issuer_key_hex) + .map_err(|e| MovementError::Internal(format!("invalid ISSUER_PRIVATE_KEY: {e}")))?; + + println!("── B. Asset auditor ─────────────────────────────────────────────"); + println!("Issuer : {}", issuer.address()); + println!("Token : {token}"); + + let current_asset = ca.get_asset_auditor_encryption_key(&token).await?; + println!( + "Current asset auditor EK: {}", + current_asset + .as_ref() + .map(|ek| hex::encode(ek.to_bytes())) + .unwrap_or_else(|| "(none)".to_string()) + ); + + // Set + println!("Setting asset auditor EK …"); + let payload = ca.set_asset_auditor(&token, Some(&auditor_ek))?; + movement.sign_submit_and_wait(&issuer, payload, None).await?; + + let stored_asset = ca + .get_asset_auditor_encryption_key(&token) + .await? + .ok_or_else(|| MovementError::Internal("Asset auditor EK not found after set".to_string()))?; + assert_eq!( + stored_asset.to_bytes(), + auditor_ek.to_bytes(), + "Stored asset auditor EK does not match" + ); + println!(" Verified — stored EK: {}", hex::encode(stored_asset.to_bytes())); + + // Clear + println!("Clearing asset auditor EK …"); + let payload = ca.set_asset_auditor(&token, None)?; + movement.sign_submit_and_wait(&issuer, payload, None).await?; + + assert!( + ca.get_asset_auditor_encryption_key(&token).await?.is_none(), + "Asset auditor EK should be None after clearing" + ); + println!(" Verified — asset auditor EK is cleared."); + println!(); + + println!("All assertions passed. Example complete!"); + Ok(()) +} diff --git a/crates/confidential-assets/src/api/confidential_asset.rs b/crates/confidential-assets/src/api/confidential_asset.rs index f6e9b03..9ef7b87 100644 --- a/crates/confidential-assets/src/api/confidential_asset.rs +++ b/crates/confidential-assets/src/api/confidential_asset.rs @@ -389,6 +389,36 @@ impl<'a> ConfidentialAsset<'a> { .await } + /// Build a `set_chain_auditor` transaction. + /// + /// Must be signed by the chain auditor admin assigned via `set_chain_auditor_admin`. + /// Pass `None` to clear the chain auditor (disables all confidential transfers). + pub fn set_chain_auditor( + &self, + new_chain_auditor_ek: Option<&TwistedEd25519PublicKey>, + ) -> Result { + self.transaction.set_chain_auditor(new_chain_auditor_ek) + } + + /// Get the global chain auditor encryption key, if set. + pub async fn get_chain_auditor_encryption_key( + &self, + ) -> Result, MovementError> { + self.transaction.get_chain_auditor_encryption_key().await + } + + /// Build a `set_asset_auditor` transaction. + /// + /// Must be signed by the FA metadata object's root owner (the issuer). + /// Pass `None` for `new_auditor_ek` to clear the per-asset auditor. + pub fn set_asset_auditor( + &self, + token_address: &AccountAddress, + new_auditor_ek: Option<&TwistedEd25519PublicKey>, + ) -> Result { + self.transaction.set_asset_auditor(token_address, new_auditor_ek) + } + /// Get the asset auditor encryption key for a token. pub async fn get_asset_auditor_encryption_key( &self, diff --git a/crates/confidential-assets/src/internal/transaction_builder.rs b/crates/confidential-assets/src/internal/transaction_builder.rs index 346e936..4ab736c 100644 --- a/crates/confidential-assets/src/internal/transaction_builder.rs +++ b/crates/confidential-assets/src/internal/transaction_builder.rs @@ -469,6 +469,52 @@ impl<'a> ConfidentialAssetTransactionBuilder<'a> { .into()) } + /// Build a `set_chain_auditor` entry function payload. + /// + /// Must be signed by the account previously assigned via `set_chain_auditor_admin`. + /// Pass `None` to clear the chain auditor (disables all confidential transfers). + pub fn set_chain_auditor( + &self, + new_chain_auditor_ek: Option<&TwistedEd25519PublicKey>, + ) -> Result { + let ek_bytes: Vec = match new_chain_auditor_ek { + Some(ek) => ek.to_bytes().to_vec(), + None => vec![], + }; + Ok(EntryFunction::new( + module_id(self.contract_address), + "set_chain_auditor", + vec![], + vec![serialize_vector_u8(&ek_bytes)], + ) + .into()) + } + + /// Build a `set_asset_auditor` entry function payload. + /// + /// The issuer (FA metadata object root owner) sets or clears the per-asset auditor EK. + /// Pass `None` to clear the auditor (empty `vector` on-chain). + pub fn set_asset_auditor( + &self, + token_address: &AccountAddress, + new_auditor_ek: Option<&TwistedEd25519PublicKey>, + ) -> Result { + let ek_bytes: Vec = match new_auditor_ek { + Some(ek) => ek.to_bytes().to_vec(), + None => vec![], + }; + Ok(EntryFunction::new( + module_id(self.contract_address), + "set_asset_auditor", + vec![], + vec![ + bcs_addr(token_address), + serialize_vector_u8(&ek_bytes), + ], + ) + .into()) + } + /// Get the global chain auditor encryption key for a token, if set. pub async fn get_chain_auditor_encryption_key( &self, From 604b91911456b480f1e3eb2fb62fb84a4e3c7622 Mon Sep 17 00:00:00 2001 From: musitdev Date: Thu, 18 Jun 2026 17:35:40 +0200 Subject: [PATCH 2/6] feat(movement-sdk): add Ed25519Account::with_address to override sender address - add with_address() builder method to Ed25519Account to decouple the signing key from the on-chain account address (needed for genesis accounts like @core_resources whose address is fixed regardless of authentication key) - use ISSUER_ADDRESS env var in set_auditor_example to support this case --- .../examples/set_auditor_example.rs | 9 ++++++++- crates/movement-sdk/src/account/ed25519.rs | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/confidential-assets/examples/set_auditor_example.rs b/crates/confidential-assets/examples/set_auditor_example.rs index 58bce43..ebe5e74 100644 --- a/crates/confidential-assets/examples/set_auditor_example.rs +++ b/crates/confidential-assets/examples/set_auditor_example.rs @@ -22,6 +22,8 @@ // CHAIN_AUDITOR_ADMIN_PRIVATE_KEY — hex Ed25519 private key of the chain auditor admin (required for A) // TOKEN_ADDRESS — hex address of the FA metadata object (required for B) // ISSUER_PRIVATE_KEY — hex Ed25519 private key of the FA root owner (required for B) +// ISSUER_ADDRESS — on-chain address of the issuer when it differs from the key-derived +// address (e.g. 0xa550c18 for the CoreResources account on localnet) // MOVEMENT_NETWORK — TESTNET | MAINNET | LOCAL (optional, default LOCAL) // MOVEMENT_RPC_URL — custom RPC endpoint, used when MOVEMENT_NETWORK is not set // CONFIDENTIAL_MODULE_ADDRESS — defaults to 0x1 @@ -139,8 +141,13 @@ async fn main() -> MovementResult<()> { "ISSUER_PRIVATE_KEY is required (hex Ed25519 key of the FA root owner)".to_string(), ) })?; - let issuer = Ed25519Account::from_private_key_hex(&issuer_key_hex) + let mut issuer = Ed25519Account::from_private_key_hex(&issuer_key_hex) .map_err(|e| MovementError::Internal(format!("invalid ISSUER_PRIVATE_KEY: {e}")))?; + if let Ok(addr_hex) = env::var("ISSUER_ADDRESS") { + let addr = AccountAddress::from_hex(&addr_hex) + .map_err(|e| MovementError::Internal(format!("invalid ISSUER_ADDRESS: {e}")))?; + issuer = issuer.with_address(addr); + } println!("── B. Asset auditor ─────────────────────────────────────────────"); println!("Issuer : {}", issuer.address()); diff --git a/crates/movement-sdk/src/account/ed25519.rs b/crates/movement-sdk/src/account/ed25519.rs index e74ceaa..bcd44f2 100644 --- a/crates/movement-sdk/src/account/ed25519.rs +++ b/crates/movement-sdk/src/account/ed25519.rs @@ -82,6 +82,16 @@ impl Ed25519Account { Ok(Self::from_private_key(private_key)) } + /// Overrides the account address while keeping the same signing key. + /// + /// Use this when the on-chain account address differs from the address derived + /// from the public key — for example, framework accounts like `@core_resources` + /// whose address is fixed at genesis regardless of the authentication key. + pub fn with_address(mut self, address: AccountAddress) -> Self { + self.address = address; + self + } + /// Creates an account from a BIP-39 mnemonic phrase. /// /// Uses the standard Movement derivation path: `m/44'/637'/0'/0'/index'` From d9cc7575dcfcc34ebe2b4dcdb056d510846737c6 Mon Sep 17 00:00:00 2001 From: musitdev Date: Fri, 19 Jun 2026 11:53:20 +0200 Subject: [PATCH 3/6] feat(confidential-assets): add auditor pre-flight check to simple_ca_transfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check chain and asset auditor keys before transfer and fail early with a clear message if chain auditor is not configured - make section B of set_auditor_example optional (skipped when TOKEN_ADDRESS is not set) with a helpful message explaining how to proceed - renumber transfer example steps 7–11 after inserting the auditor check --- .../examples/set_auditor_example.rs | 48 +++++++++++++------ .../examples/simple_ca_transfer.rs | 40 +++++++++++++--- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/crates/confidential-assets/examples/set_auditor_example.rs b/crates/confidential-assets/examples/set_auditor_example.rs index ebe5e74..ddcc5f2 100644 --- a/crates/confidential-assets/examples/set_auditor_example.rs +++ b/crates/confidential-assets/examples/set_auditor_example.rs @@ -7,28 +7,38 @@ // 2. Read it back from chain to verify // 3. Clear it (signed by the chain auditor admin) // -// B. Asset auditor — applies only to transfers of a specific FA token: +// B. Asset auditor — applies only to transfers of a specific FA token (optional): // 1. Set the asset auditor EK (signed by the FA root owner / issuer) // 2. Read it back from chain to verify // 3. Clear it (signed by the issuer) // +// Section B is skipped when TOKEN_ADDRESS is not set. It requires a user-created +// FA token whose root owner you control — the built-in MOVE FA at 0xa is owned +// by 0x1 (@aptos_framework) and cannot be used here. +// To create your own FA, deploy a Move module that calls +// `fungible_asset::add_fungibility`, then pass its metadata address as TOKEN_ADDRESS. +// // Prerequisites: // - A. The chain auditor admin must already be assigned via the governance Move script // (see deploy/scripts/setup_chain_auditor.move). Provide that admin's private key // in CHAIN_AUDITOR_ADMIN_PRIVATE_KEY. -// - B. A fungible asset token whose root owner you control. +// - B. (optional) A user-created FA token whose root owner you control. // // Environment variables: // CHAIN_AUDITOR_ADMIN_PRIVATE_KEY — hex Ed25519 private key of the chain auditor admin (required for A) -// TOKEN_ADDRESS — hex address of the FA metadata object (required for B) -// ISSUER_PRIVATE_KEY — hex Ed25519 private key of the FA root owner (required for B) +// TOKEN_ADDRESS — hex address of your FA metadata object (optional, enables B) +// ISSUER_PRIVATE_KEY — hex Ed25519 private key of the FA root owner (required when TOKEN_ADDRESS is set) // ISSUER_ADDRESS — on-chain address of the issuer when it differs from the key-derived -// address (e.g. 0xa550c18 for the CoreResources account on localnet) +// address (e.g. a genesis account whose address is fixed at node init) // MOVEMENT_NETWORK — TESTNET | MAINNET | LOCAL (optional, default LOCAL) // MOVEMENT_RPC_URL — custom RPC endpoint, used when MOVEMENT_NETWORK is not set // CONFIDENTIAL_MODULE_ADDRESS — defaults to 0x1 // -// Run: +// Run (section A only): +// CHAIN_AUDITOR_ADMIN_PRIVATE_KEY= \ +// cargo run -p confidential-assets --example set_auditor_example --features confidential-assets/e2e +// +// Run (sections A + B): // CHAIN_AUDITOR_ADMIN_PRIVATE_KEY= TOKEN_ADDRESS=0x ISSUER_PRIVATE_KEY= \ // cargo run -p confidential-assets --example set_auditor_example --features confidential-assets/e2e @@ -125,20 +135,28 @@ async fn main() -> MovementResult<()> { println!(); // ════════════════════════════════════════════════════════════════════════ - // B. Asset auditor + // B. Asset auditor (optional — skipped when TOKEN_ADDRESS is not set) // ════════════════════════════════════════════════════════════════════════ - let token_raw = env::var("TOKEN_ADDRESS").map_err(|_| { - MovementError::Internal( - "TOKEN_ADDRESS is required (hex address of the FA metadata object)".to_string(), - ) - })?; + let token_raw = match env::var("TOKEN_ADDRESS") { + Ok(v) => v, + Err(_) => { + println!("── B. Asset auditor ─────────────────────────────────────────────"); + println!(" Skipped — TOKEN_ADDRESS not set."); + println!(" To run this section, deploy a Move module that creates a fungible asset,"); + println!(" then re-run with TOKEN_ADDRESS=0x ISSUER_PRIVATE_KEY=."); + println!(); + println!("Section A complete!"); + return Ok(()); + } + }; + let token = AccountAddress::from_hex(&token_raw) .map_err(|e| MovementError::Internal(format!("invalid TOKEN_ADDRESS: {e}")))?; let issuer_key_hex = env::var("ISSUER_PRIVATE_KEY").map_err(|_| { MovementError::Internal( - "ISSUER_PRIVATE_KEY is required (hex Ed25519 key of the FA root owner)".to_string(), + "ISSUER_PRIVATE_KEY is required when TOKEN_ADDRESS is set (hex Ed25519 key of the FA root owner)".to_string(), ) })?; let mut issuer = Ed25519Account::from_private_key_hex(&issuer_key_hex) @@ -150,8 +168,8 @@ async fn main() -> MovementResult<()> { } println!("── B. Asset auditor ─────────────────────────────────────────────"); - println!("Issuer : {}", issuer.address()); - println!("Token : {token}"); + println!("Issuer : {}", issuer.address()); + println!("Token : {token}"); let current_asset = ca.get_asset_auditor_encryption_key(&token).await?; println!( diff --git a/crates/confidential-assets/examples/simple_ca_transfer.rs b/crates/confidential-assets/examples/simple_ca_transfer.rs index 26d8cdd..c560e03 100644 --- a/crates/confidential-assets/examples/simple_ca_transfer.rs +++ b/crates/confidential-assets/examples/simple_ca_transfer.rs @@ -147,7 +147,33 @@ async fn main() -> MovementResult<()> { ); println!(); - // ── 7. Confidential transfer: Alice → Bob ──────────────────────────────── + // ── 7. Check active auditors ───────────────────────────────────────────── + // The SDK fetches chain and asset auditors from chain and includes them in + // the ZK proof automatically — they do not need to be passed explicitly. + // We read them here to give a clear error before attempting the transfer if + // the chain auditor (required by the Move contract) is not configured. + let chain_auditor_ek = ca.get_chain_auditor_encryption_key().await?; + let asset_auditor_ek = ca.get_asset_auditor_encryption_key(&token).await?; + + println!("Active auditors:"); + match &chain_auditor_ek { + Some(_) => println!(" Chain auditor : set (included in transfer proof automatically)"), + None => { + return Err(MovementError::Internal( + "No chain auditor is configured on this network. \ + Confidential transfers require a chain auditor — \ + run the set_auditor_example to configure one first." + .to_string(), + )) + } + } + match &asset_auditor_ek { + Some(_) => println!(" Asset auditor : set (included in transfer proof automatically)"), + None => println!(" Asset auditor : not set"), + } + println!(); + + // ── 8. Confidential transfer: Alice → Bob ──────────────────────────────── println!("Transferring {TRANSFER_AMOUNT} tokens from Alice to Bob (confidential) …"); let payload = ca .transfer( @@ -156,14 +182,14 @@ async fn main() -> MovementResult<()> { &token, TRANSFER_AMOUNT, &alice_dk, - &[], // no additional auditors - &[], // no auditor hint + &[], // chain and asset auditors above are included automatically + &[], // no sender auditor hint ) .await?; movement.sign_submit_and_wait(&alice, payload, None).await?; println!(); - // ── 8. Verify balances after transfer ──────────────────────────────────── + // ── 9. Verify balances after transfer ──────────────────────────────────── let alice_bal = ca.get_balance(&alice.address(), &token, &alice_dk).await?; let bob_bal = ca.get_balance(&bob.address(), &token, &bob_dk).await?; @@ -191,7 +217,7 @@ async fn main() -> MovementResult<()> { "Bob's pending balance should equal the transfer amount" ); - // ── 9. Normalize Alice's available balance ──────────────────────────────── + // ── 10. Normalize Alice's available balance ─────────────────────────────── println!("Normalizing Alice's balance …"); let payload = ca .normalize_balance(&alice.address(), &alice_dk, &token) @@ -211,7 +237,7 @@ async fn main() -> MovementResult<()> { ); println!(); - // ── 10. Rollover Bob's pending balance → available ──────────────────────── + // ── 11. Rollover Bob's pending balance → available ─────────────────────── println!("Rolling over Bob's pending balance …"); let payloads = ca .rollover_pending_balance(&bob.address(), &token, Some(&bob_dk), false) @@ -238,7 +264,7 @@ async fn main() -> MovementResult<()> { ); println!(); - // ── 11. Normalize Bob's available balance ───────────────────────────────── + // ── 12. Normalize Bob's available balance ──────────────────────────────── println!("Normalizing Bob's balance …"); let payload = ca .normalize_balance(&bob.address(), &bob_dk, &token) From ea27b0566644e864da7b3b91974b06325a7f23f1 Mon Sep 17 00:00:00 2001 From: musitdev Date: Fri, 19 Jun 2026 14:09:06 +0200 Subject: [PATCH 4/6] correct simple transfer example --- .../examples/simple_ca_transfer.rs | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/crates/confidential-assets/examples/simple_ca_transfer.rs b/crates/confidential-assets/examples/simple_ca_transfer.rs index c560e03..6ed5ef2 100644 --- a/crates/confidential-assets/examples/simple_ca_transfer.rs +++ b/crates/confidential-assets/examples/simple_ca_transfer.rs @@ -20,13 +20,13 @@ use confidential_assets::api::ConfidentialAsset; use confidential_assets::crypto::twisted_ed25519::{ - DECRYPTION_KEY_DERIVATION_MESSAGE, TwistedEd25519PrivateKey, + TwistedEd25519PrivateKey, DECRYPTION_KEY_DERIVATION_MESSAGE, }; use movement_sdk::{ - Movement, MovementConfig, MovementError, MovementResult, account::Ed25519Account, transaction::{EntryFunction, TransactionPayload}, types::{AccountAddress, Identifier, MoveModuleId, TypeTag}, + Movement, MovementConfig, MovementError, MovementResult, }; use std::env; @@ -217,26 +217,6 @@ async fn main() -> MovementResult<()> { "Bob's pending balance should equal the transfer amount" ); - // ── 10. Normalize Alice's available balance ─────────────────────────────── - println!("Normalizing Alice's balance …"); - let payload = ca - .normalize_balance(&alice.address(), &alice_dk, &token) - .await?; - movement.sign_submit_and_wait(&alice, payload, None).await?; - - let alice_bal = ca.get_balance(&alice.address(), &token, &alice_dk).await?; - println!( - " Alice after normalize — available: {}, pending: {}", - alice_bal.available_balance(), - alice_bal.pending_balance() - ); - assert_eq!( - alice_bal.available_balance(), - (DEPOSIT_AMOUNT - TRANSFER_AMOUNT) as u128, - "Alice's available balance should be unchanged after normalize" - ); - println!(); - // ── 11. Rollover Bob's pending balance → available ─────────────────────── println!("Rolling over Bob's pending balance …"); let payloads = ca From c2afa4bdce191069261772ae45cfb9b078bfc264 Mon Sep 17 00:00:00 2001 From: musitdev Date: Fri, 19 Jun 2026 15:10:30 +0200 Subject: [PATCH 5/6] correct clippy --- crates/movement-sdk/src/account/ed25519.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/movement-sdk/src/account/ed25519.rs b/crates/movement-sdk/src/account/ed25519.rs index bcd44f2..7eb5401 100644 --- a/crates/movement-sdk/src/account/ed25519.rs +++ b/crates/movement-sdk/src/account/ed25519.rs @@ -11,12 +11,12 @@ //! **Note**: The two account types produce DIFFERENT addresses for the same private key //! because they use different authentication key derivation schemes. +use crate::account::account::{Account, AuthenticationKey}; #[cfg(feature = "mnemonic")] use crate::account::Mnemonic; -use crate::account::account::{Account, AuthenticationKey}; use crate::crypto::{ - ED25519_SCHEME, Ed25519PrivateKey, Ed25519PublicKey, SINGLE_KEY_SCHEME, - derive_authentication_key, + derive_authentication_key, Ed25519PrivateKey, Ed25519PublicKey, ED25519_SCHEME, + SINGLE_KEY_SCHEME, }; use crate::error::MovementResult; use crate::types::AccountAddress; @@ -87,6 +87,7 @@ impl Ed25519Account { /// Use this when the on-chain account address differs from the address derived /// from the public key — for example, framework accounts like `@core_resources` /// whose address is fixed at genesis regardless of the authentication key. + #[must_use] pub fn with_address(mut self, address: AccountAddress) -> Self { self.address = address; self From 8cd9b444a5abdf5f6b4729684a9993f7f2d22d0a Mon Sep 17 00:00:00 2001 From: musitdev Date: Fri, 19 Jun 2026 15:12:11 +0200 Subject: [PATCH 6/6] correct cargo fmt --- .../examples/set_auditor_example.rs | 56 ++++++++++++++----- .../examples/simple_ca_transfer.rs | 6 +- .../src/api/confidential_asset.rs | 3 +- .../src/internal/transaction_builder.rs | 5 +- crates/movement-sdk/src/account/ed25519.rs | 6 +- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/crates/confidential-assets/examples/set_auditor_example.rs b/crates/confidential-assets/examples/set_auditor_example.rs index ddcc5f2..1e19466 100644 --- a/crates/confidential-assets/examples/set_auditor_example.rs +++ b/crates/confidential-assets/examples/set_auditor_example.rs @@ -45,8 +45,7 @@ use confidential_assets::api::ConfidentialAsset; use confidential_assets::crypto::twisted_ed25519::TwistedEd25519PrivateKey; use movement_sdk::{ - Movement, MovementConfig, MovementError, MovementResult, - account::Ed25519Account, + Movement, MovementConfig, MovementError, MovementResult, account::Ed25519Account, types::AccountAddress, }; use std::env; @@ -78,8 +77,14 @@ async fn main() -> MovementResult<()> { // The same dk/ek pair is reused for both the chain and asset auditor sections. let auditor_dk = TwistedEd25519PrivateKey::generate(); let auditor_ek = auditor_dk.public_key(); - println!("Generated auditor decryption key (dk): {}", hex::encode(auditor_dk.to_bytes())); - println!("Generated auditor encryption key (ek): {}", hex::encode(auditor_ek.to_bytes())); + println!( + "Generated auditor decryption key (dk): {}", + hex::encode(auditor_dk.to_bytes()) + ); + println!( + "Generated auditor encryption key (ek): {}", + hex::encode(auditor_ek.to_bytes()) + ); println!(); // ════════════════════════════════════════════════════════════════════════ @@ -91,8 +96,9 @@ async fn main() -> MovementResult<()> { "CHAIN_AUDITOR_ADMIN_PRIVATE_KEY is required (hex Ed25519 key of the chain auditor admin)".to_string(), ) })?; - let chain_admin = Ed25519Account::from_private_key_hex(&chain_admin_key_hex) - .map_err(|e| MovementError::Internal(format!("invalid CHAIN_AUDITOR_ADMIN_PRIVATE_KEY: {e}")))?; + let chain_admin = Ed25519Account::from_private_key_hex(&chain_admin_key_hex).map_err(|e| { + MovementError::Internal(format!("invalid CHAIN_AUDITOR_ADMIN_PRIVATE_KEY: {e}")) + })?; println!("── A. Chain auditor ─────────────────────────────────────────────"); println!("Chain auditor admin: {}", chain_admin.address()); @@ -109,23 +115,32 @@ async fn main() -> MovementResult<()> { // Set println!("Setting chain auditor EK …"); let payload = ca.set_chain_auditor(Some(&auditor_ek))?; - movement.sign_submit_and_wait(&chain_admin, payload, None).await?; + movement + .sign_submit_and_wait(&chain_admin, payload, None) + .await?; let stored_chain = ca .get_chain_auditor_encryption_key() .await? - .ok_or_else(|| MovementError::Internal("Chain auditor EK not found after set".to_string()))?; + .ok_or_else(|| { + MovementError::Internal("Chain auditor EK not found after set".to_string()) + })?; assert_eq!( stored_chain.to_bytes(), auditor_ek.to_bytes(), "Stored chain auditor EK does not match" ); - println!(" Verified — stored EK: {}", hex::encode(stored_chain.to_bytes())); + println!( + " Verified — stored EK: {}", + hex::encode(stored_chain.to_bytes()) + ); // Clear println!("Clearing chain auditor EK …"); let payload = ca.set_chain_auditor(None)?; - movement.sign_submit_and_wait(&chain_admin, payload, None).await?; + movement + .sign_submit_and_wait(&chain_admin, payload, None) + .await?; assert!( ca.get_chain_auditor_encryption_key().await?.is_none(), @@ -144,7 +159,9 @@ async fn main() -> MovementResult<()> { println!("── B. Asset auditor ─────────────────────────────────────────────"); println!(" Skipped — TOKEN_ADDRESS not set."); println!(" To run this section, deploy a Move module that creates a fungible asset,"); - println!(" then re-run with TOKEN_ADDRESS=0x ISSUER_PRIVATE_KEY=."); + println!( + " then re-run with TOKEN_ADDRESS=0x ISSUER_PRIVATE_KEY=." + ); println!(); println!("Section A complete!"); return Ok(()); @@ -183,23 +200,32 @@ async fn main() -> MovementResult<()> { // Set println!("Setting asset auditor EK …"); let payload = ca.set_asset_auditor(&token, Some(&auditor_ek))?; - movement.sign_submit_and_wait(&issuer, payload, None).await?; + movement + .sign_submit_and_wait(&issuer, payload, None) + .await?; let stored_asset = ca .get_asset_auditor_encryption_key(&token) .await? - .ok_or_else(|| MovementError::Internal("Asset auditor EK not found after set".to_string()))?; + .ok_or_else(|| { + MovementError::Internal("Asset auditor EK not found after set".to_string()) + })?; assert_eq!( stored_asset.to_bytes(), auditor_ek.to_bytes(), "Stored asset auditor EK does not match" ); - println!(" Verified — stored EK: {}", hex::encode(stored_asset.to_bytes())); + println!( + " Verified — stored EK: {}", + hex::encode(stored_asset.to_bytes()) + ); // Clear println!("Clearing asset auditor EK …"); let payload = ca.set_asset_auditor(&token, None)?; - movement.sign_submit_and_wait(&issuer, payload, None).await?; + movement + .sign_submit_and_wait(&issuer, payload, None) + .await?; assert!( ca.get_asset_auditor_encryption_key(&token).await?.is_none(), diff --git a/crates/confidential-assets/examples/simple_ca_transfer.rs b/crates/confidential-assets/examples/simple_ca_transfer.rs index c2e94a8..3324086 100644 --- a/crates/confidential-assets/examples/simple_ca_transfer.rs +++ b/crates/confidential-assets/examples/simple_ca_transfer.rs @@ -20,13 +20,13 @@ use confidential_assets::api::ConfidentialAsset; use confidential_assets::crypto::twisted_ed25519::{ - TwistedEd25519PrivateKey, DECRYPTION_KEY_DERIVATION_MESSAGE, + DECRYPTION_KEY_DERIVATION_MESSAGE, TwistedEd25519PrivateKey, }; use movement_sdk::{ + Movement, MovementConfig, MovementError, MovementResult, account::Ed25519Account, transaction::{EntryFunction, TransactionPayload}, types::{AccountAddress, Identifier, MoveModuleId, TypeTag}, - Movement, MovementConfig, MovementError, MovementResult, }; use std::env; @@ -164,7 +164,7 @@ async fn main() -> MovementResult<()> { Confidential transfers require a chain auditor — \ run the set_auditor_example to configure one first." .to_string(), - )) + )); } } match &asset_auditor_ek { diff --git a/crates/confidential-assets/src/api/confidential_asset.rs b/crates/confidential-assets/src/api/confidential_asset.rs index 9ef7b87..d592f30 100644 --- a/crates/confidential-assets/src/api/confidential_asset.rs +++ b/crates/confidential-assets/src/api/confidential_asset.rs @@ -416,7 +416,8 @@ impl<'a> ConfidentialAsset<'a> { token_address: &AccountAddress, new_auditor_ek: Option<&TwistedEd25519PublicKey>, ) -> Result { - self.transaction.set_asset_auditor(token_address, new_auditor_ek) + self.transaction + .set_asset_auditor(token_address, new_auditor_ek) } /// Get the asset auditor encryption key for a token. diff --git a/crates/confidential-assets/src/internal/transaction_builder.rs b/crates/confidential-assets/src/internal/transaction_builder.rs index 4ab736c..08ab7a5 100644 --- a/crates/confidential-assets/src/internal/transaction_builder.rs +++ b/crates/confidential-assets/src/internal/transaction_builder.rs @@ -507,10 +507,7 @@ impl<'a> ConfidentialAssetTransactionBuilder<'a> { module_id(self.contract_address), "set_asset_auditor", vec![], - vec![ - bcs_addr(token_address), - serialize_vector_u8(&ek_bytes), - ], + vec![bcs_addr(token_address), serialize_vector_u8(&ek_bytes)], ) .into()) } diff --git a/crates/movement-sdk/src/account/ed25519.rs b/crates/movement-sdk/src/account/ed25519.rs index 7eb5401..5c1d751 100644 --- a/crates/movement-sdk/src/account/ed25519.rs +++ b/crates/movement-sdk/src/account/ed25519.rs @@ -11,12 +11,12 @@ //! **Note**: The two account types produce DIFFERENT addresses for the same private key //! because they use different authentication key derivation schemes. -use crate::account::account::{Account, AuthenticationKey}; #[cfg(feature = "mnemonic")] use crate::account::Mnemonic; +use crate::account::account::{Account, AuthenticationKey}; use crate::crypto::{ - derive_authentication_key, Ed25519PrivateKey, Ed25519PublicKey, ED25519_SCHEME, - SINGLE_KEY_SCHEME, + ED25519_SCHEME, Ed25519PrivateKey, Ed25519PublicKey, SINGLE_KEY_SCHEME, + derive_authentication_key, }; use crate::error::MovementResult; use crate::types::AccountAddress;