feat(wallet): reclaim an account atomically

This commit is contained in:
Artem Gureev
2026-08-24 21:58:38 +04:00
committed by agureev
parent f903d02f54
commit 0a004a818c
5 changed files with 100 additions and 1 deletions
+12 -1
View File
@@ -23,7 +23,8 @@ use crate::{
programs::{
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand, bridge::BridgeSubcommand,
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
token::TokenProgramAgnosticSubcommand, vault::VaultSubcommand,
system_program::SystemSubcommand, token::TokenProgramAgnosticSubcommand,
vault::VaultSubcommand,
},
statistics::StatisticsSubcommand,
},
@@ -73,6 +74,9 @@ pub enum Command {
/// Vault program interaction subcommand.
#[command(subcommand)]
Vault(VaultSubcommand),
/// System Program interaction subcommand (e.g. reclaim an account).
#[command(subcommand)]
System(SystemSubcommand),
/// Bridge program interaction subcommand.
#[command(subcommand)]
Bridge(BridgeSubcommand),
@@ -218,6 +222,10 @@ impl Default for CliAccountMention {
}
}
#[expect(
clippy::cognitive_complexity,
reason = "flat dispatch over every CLI command"
)]
pub async fn execute_subcommand(
wallet_core: &mut WalletCore,
command: Command,
@@ -235,6 +243,9 @@ pub async fn execute_subcommand(
Command::Pinata(pinata_subcommand) => {
pinata_subcommand.handle_subcommand(wallet_core).await?
}
Command::System(system_subcommand) => {
system_subcommand.handle_subcommand(wallet_core).await?
}
Command::CheckHealth => {
let remote_program_ids = wallet_core
.get_program_ids()
+1
View File
@@ -3,5 +3,6 @@ pub mod ata;
pub mod bridge;
pub mod native_token_transfer;
pub mod pinata;
pub mod system_program;
pub mod token;
pub mod vault;
@@ -0,0 +1,63 @@
use anyhow::Result;
use clap::Subcommand;
use lee::AccountId;
use crate::{
WalletCore,
account::AccountIdWithPrivacy,
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
program_facades::system_program::SystemProgram,
};
/// Represents generic CLI subcommand for a wallet working with the System Program.
#[derive(Subcommand, Debug, Clone)]
pub enum SystemSubcommand {
/// !!!WARNING!!! Reclaim an account: wipe all of its data and hand it to a new owner,
/// bypassing the program that currently owns it. Balance is preserved.
///
/// The account authorizes its own reset. Only public accounts are supported.
Reclaim {
/// Account to reclaim. Must have authorization.
#[arg(long)]
account_id: CliAccountMention,
/// New owner - valid 32 byte base58 string WITHOUT privacy prefix. Defaults to the
/// authenticated transfer program, which keeps the balance spendable.
#[arg(long)]
new_owner: Option<AccountId>,
},
}
impl WalletSubcommand for SystemSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::Reclaim {
account_id,
new_owner,
} => {
let resolved = account_id.resolve(wallet_core.storage())?;
let AccountIdWithPrivacy::Public(pub_account_id) = resolved else {
anyhow::bail!(
"Shielded reclaim is not yet supported; only public accounts can be reclaimed"
);
};
let new_owner = Some(
new_owner.unwrap_or_else(|| programs::authenticated_transfer().id().into()),
);
let tx_hash = SystemProgram(wallet_core)
.clear(
account_id.into_public_identity(pub_account_id, true),
new_owner,
)
.await?;
wallet_core
.poll_and_finalize_public_transaction(tx_hash)
.await
}
}
}
}
+1
View File
@@ -6,5 +6,6 @@ pub mod ata;
pub mod bridge;
pub mod native_token_transfer;
pub mod pinata;
pub mod system_program;
pub mod token;
pub mod vault;
@@ -0,0 +1,23 @@
use common::HashType;
use lee::{AccountId, program::Program};
use lee_core::program::{DEFAULT_PROGRAM_ID, SystemInstruction};
use crate::{AccountIdentity, ExecutionFailureKind, WalletCore};
pub struct SystemProgram<'wallet>(pub &'wallet WalletCore);
impl SystemProgram<'_> {
pub async fn clear(
&self,
account: AccountIdentity,
new_owner: Option<AccountId>,
) -> Result<HashType, ExecutionFailureKind> {
let instruction = SystemInstruction::Clear { new_owner };
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_pub_tx(vec![account], instruction_data, DEFAULT_PROGRAM_ID)
.await
}
}