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
64 changes: 7 additions & 57 deletions programs/hydentity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<WithdrawDirectAccounts>,
ctx: Context<WithdrawDirect>,
amount: u64,
_mint: Option<Pubkey>,
mint: Option<Pubkey>,
) -> 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)
}
}

Expand Down Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions tests/hydentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
Expand Down