diff --git a/programs/hydentity/src/lib.rs b/programs/hydentity/src/lib.rs index e932ed2..5317966 100644 --- a/programs/hydentity/src/lib.rs +++ b/programs/hydentity/src/lib.rs @@ -10,6 +10,7 @@ pub mod events; use constants::*; use errors::HydentityError; +use instructions::{withdraw_handler, WithdrawDirect}; #[cfg(feature = "arcium")] use events::ConfigStored; use state::{NameVault, VaultAuthority, PrivacyPolicy}; @@ -246,29 +247,15 @@ pub mod hydentity { // ========== Withdrawal Instructions ========== /// Direct withdrawal - bypass privacy features (owner only) + /// + /// Uses the instruction-module handler so SOL transfers keep the vault authority + /// rent-exempt and SPL withdrawals use the token program CPI path. pub fn withdraw_direct( - ctx: Context, + ctx: Context, amount: u64, - _mint: Option, + mint: Option, ) -> Result<()> { - let vault_authority = &ctx.accounts.vault_authority; - let destination = &ctx.accounts.destination; - - msg!("Emergency direct withdrawal initiated by owner: {}", ctx.accounts.owner.key()); - - // For now, just do SOL transfer from vault authority - let balance = vault_authority.to_account_info().lamports(); - if balance < amount { - return Err(HydentityError::InsufficientBalance.into()); - } - - // Direct lamport transfer (required for PDAs with data - System Program transfer won't work) - **vault_authority.to_account_info().try_borrow_mut_lamports()? -= amount; - **destination.to_account_info().try_borrow_mut_lamports()? += amount; - - msg!("Transferred {} lamports to {}", amount, destination.key()); - - Ok(()) + withdraw_handler(ctx, amount, mint) } } @@ -346,43 +333,6 @@ pub struct ReclaimDomainAccounts<'info> { pub sns_name_program: UncheckedAccount<'info>, } -/// Accounts for withdraw_direct instruction -#[derive(Accounts)] -pub struct WithdrawDirectAccounts<'info> { - /// The vault owner (must be signer) - #[account(mut)] - pub owner: Signer<'info>, - - /// The SNS name account - /// CHECK: Validated via vault's sns_name field - pub sns_name_account: UncheckedAccount<'info>, - - /// The vault holding the funds - #[account( - seeds = [VAULT_SEED, sns_name_account.key().as_ref()], - bump = vault.bump, - constraint = vault.sns_name == sns_name_account.key() @ HydentityError::InvalidSnsName, - constraint = vault.owner == owner.key() @ HydentityError::Unauthorized - )] - pub vault: Account<'info, NameVault>, - - /// The vault authority for signing transfers - #[account( - mut, - seeds = [VAULT_AUTH_SEED, sns_name_account.key().as_ref()], - bump = vault_authority.bump, - )] - pub vault_authority: Account<'info, VaultAuthority>, - - /// The destination for the withdrawal - /// CHECK: Any valid account can receive funds - #[account(mut)] - pub destination: UncheckedAccount<'info>, - - /// System program for SOL transfers - pub system_program: Program<'info, System>, -} - // ========== Vault Lifecycle Account Structs ========== /// Accounts for close_vault instruction diff --git a/tests/hydentity.ts b/tests/hydentity.ts index 823e969..3238e4c 100644 --- a/tests/hydentity.ts +++ b/tests/hydentity.ts @@ -274,16 +274,19 @@ describe("hydentity", () => { describe("withdraw_direct", () => { it("should allow owner to withdraw directly", async () => { - // First, fund the vault + // SOL for direct withdrawal lives on the vault authority PDA (not the NameVault metadata account) const fundAmount = LAMPORTS_PER_SOL; - const fundTx = await provider.connection.requestAirdrop(vaultPda, fundAmount); + const fundTx = await provider.connection.requestAirdrop( + vaultAuthorityPda, + fundAmount + ); await provider.connection.confirmTransaction(fundTx); const destination = Keypair.generate(); const withdrawAmount = LAMPORTS_PER_SOL / 2; - // Get initial balances - const vaultBalanceBefore = await provider.connection.getBalance(vaultPda); + // Get initial balances (authority holds spendable SOL) + const vaultBalanceBefore = await provider.connection.getBalance(vaultAuthorityPda); const tx = await program.methods .withdrawDirect(new anchor.BN(withdrawAmount), null) @@ -304,7 +307,7 @@ describe("hydentity", () => { console.log("Withdraw direct tx:", tx); // Verify withdrawal - const vaultBalanceAfter = await provider.connection.getBalance(vaultPda); + const vaultBalanceAfter = await provider.connection.getBalance(vaultAuthorityPda); const destinationBalance = await provider.connection.getBalance(destination.publicKey); expect(vaultBalanceAfter).to.be.lessThan(vaultBalanceBefore);