Skip to content
Draft
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
15 changes: 5 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ alloy-core = { version = "1", default-features = false, features = [
] }
alloy-primitives = { version = "1", default-features = false }
uniffi = { version = "0.31.0", features = ["tokio"] }
world-id-core = { version = "0.10.1", default-features = false }
world-id-proof = { version = "0.10.1", default-features = false }
world-id-core = { git = "https://github.com/worldcoin/world-id-protocol", branch = "wip-102", default-features = false }
world-id-proof = { git = "https://github.com/worldcoin/world-id-protocol", branch = "wip-102", default-features = false }

# internal
walletkit-core = { version = "0.16.0", path = "walletkit-core", default-features = false }
Expand Down
42 changes: 14 additions & 28 deletions walletkit-cli/src/commands/recovery_agent.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! `walletkit recovery-agent` subcommands — recovery agent management.
//! `walletkit recovery-agent` subcommands — recovery agent management (WIP-102).

use clap::Subcommand;
use eyre::WrapErr as _;
Expand All @@ -9,58 +9,44 @@ use super::{init_authenticator, Cli};

#[derive(Subcommand)]
pub enum RecoveryAgentCommand {
/// Initiate a time-locked recovery agent update (14-day cooldown).
Initiate {
/// Update the holder's recovery agent. Effective immediately, reversible
/// during the revert window.
Update {
/// Checksummed hex address of the new recovery agent (e.g. "0x1234…").
new_recovery_agent: String,
},
/// Execute a pending recovery agent update after the cooldown has elapsed.
Execute,
/// Cancel a pending recovery agent update before the cooldown expires.
Cancel,
/// Revert an in-flight recovery agent update during the revert window.
Revert,
}

pub async fn run(cli: &Cli, action: &RecoveryAgentCommand) -> eyre::Result<()> {
let (authenticator, _store) = init_authenticator(cli).await?;

match action {
RecoveryAgentCommand::Initiate { new_recovery_agent } => {
RecoveryAgentCommand::Update { new_recovery_agent } => {
let request_id = authenticator
.initiate_recovery_agent_update(new_recovery_agent.clone())
.update_recovery_agent(new_recovery_agent.clone())
.await
.wrap_err("initiate recovery agent update failed")?;
.wrap_err("update recovery agent failed")?;

let data = serde_json::json!({ "request_id": request_id });
if cli.json {
output::print_json_data(&data, true);
} else {
println!("Recovery agent update initiated. Request ID: {request_id}");
println!("Recovery agent update submitted. Request ID: {request_id}");
}
}
RecoveryAgentCommand::Execute => {
RecoveryAgentCommand::Revert => {
let request_id = authenticator
.execute_recovery_agent_update()
.revert_recovery_agent_update()
.await
.wrap_err("execute recovery agent update failed")?;
.wrap_err("revert recovery agent update failed")?;

let data = serde_json::json!({ "request_id": request_id });
if cli.json {
output::print_json_data(&data, true);
} else {
println!("Recovery agent update executed. Request ID: {request_id}");
}
}
RecoveryAgentCommand::Cancel => {
let request_id = authenticator
.cancel_recovery_agent_update()
.await
.wrap_err("cancel recovery agent update failed")?;

let data = serde_json::json!({ "request_id": request_id });
if cli.json {
output::print_json_data(&data, true);
} else {
println!("Recovery agent update cancelled. Request ID: {request_id}");
println!("Recovery agent update reverted. Request ID: {request_id}");
}
}
}
Expand Down
75 changes: 71 additions & 4 deletions walletkit-core/src/authenticator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
//! The Authenticator is the main component with which users interact with the World ID Protocol.

// `#[uniffi::export]` macro-generated FFI registrations reference the deprecated
// WIP-102 V1 wrappers below (`initiate_/execute_/cancel_recovery_agent_update`).
// Those references emit deprecation warnings at the wrapper definition sites,
// outside the impl-level lint scope. Suppress at module level — the V1 wrappers
// are intentionally retained for backward compat with existing FFI consumers.
#![allow(deprecated)]

use crate::{
defaults::DefaultConfig, error::WalletKitError,
primitives::ParseFromForeignBinding, Environment, FieldElement, Region,
Expand Down Expand Up @@ -265,7 +272,7 @@ impl Authenticator {
})
}

/// Initiates a time-locked recovery agent update (14-day cooldown).
/// Initiates a time-locked recovery agent update (legacy V1, 14-day cooldown).
///
/// Signs an EIP-712 `InitiateRecoveryAgentUpdate` payload and submits it to
/// the gateway. Returns the gateway request ID that can be used to poll
Expand All @@ -279,6 +286,10 @@ impl Authenticator {
/// - Returns [`WalletKitError::InvalidInput`] if `new_recovery_agent` is not
/// a valid address.
/// - Returns a network error if the gateway request fails.
#[deprecated(
note = "WIP-102: use `update_recovery_agent`. The legacy URL still works against a V2-upgraded gateway, but the V2 contract changes the agent immediately (with a revert window) instead of starting a cooldown."
)]
#[allow(deprecated)]
pub async fn initiate_recovery_agent_update(
&self,
new_recovery_agent: String,
Expand All @@ -294,8 +305,37 @@ impl Authenticator {
Ok(request_id.to_string())
}

/// Executes a pending recovery agent update after the 14-day cooldown has
/// elapsed.
/// Updates the holder's recovery agent (WIP-102).
///
/// On a V2 registry the new agent becomes effective immediately, but for a
/// revert window any authenticator can call
/// [`Self::revert_recovery_agent_update`] to roll back. During that window
/// the *previous* agent remains the only valid signer for `recoverAccount`,
/// which mitigates a compromised authenticator silently swapping in an
/// attacker-controlled recovery address.
///
/// # Arguments
/// * `new_recovery_agent` — the checksummed hex address of the new recovery
/// agent (e.g. `"0x1234…"`).
///
/// # Errors
/// - Returns [`WalletKitError::InvalidInput`] if `new_recovery_agent` is not
/// a valid address.
/// - Returns a network error if the gateway request fails.
pub async fn update_recovery_agent(
&self,
new_recovery_agent: String,
) -> Result<String, WalletKitError> {
let new_recovery_agent =
Address::parse_from_ffi(&new_recovery_agent, "new_recovery_agent")?;

let request_id = self.inner.update_recovery_agent(new_recovery_agent).await?;

Ok(request_id.to_string())
}

/// Executes a pending recovery agent update after the legacy V1 cooldown
/// has elapsed.
///
/// This call is **permissionless** — no signature is required. The contract
/// enforces the cooldown and will revert with
Expand All @@ -305,6 +345,10 @@ impl Authenticator {
///
/// # Errors
/// Returns a network error if the gateway request fails.
#[deprecated(
note = "WIP-102: this operation no longer exists. On a V2-upgraded gateway the call is a no-op (returns Queued without touching chain). Remove the call from your flow."
)]
#[allow(deprecated)]
pub async fn execute_recovery_agent_update(
&self,
) -> Result<String, WalletKitError> {
Expand All @@ -314,19 +358,42 @@ impl Authenticator {
}

/// Cancels a pending time-locked recovery agent update before the cooldown
/// expires.
/// expires (legacy V1).
///
/// Signs an EIP-712 `CancelRecoveryAgentUpdate` payload and submits it to
/// the gateway. Returns the gateway request ID that can be used to poll
/// status.
///
/// # Errors
/// Returns a network error if the gateway request fails.
#[deprecated(
note = "WIP-102: use `revert_recovery_agent_update`. The legacy URL still works against a V2-upgraded gateway, but the new method name reflects WIP-102 semantics: the operation can only succeed within the revert window after `update_recovery_agent`."
)]
#[allow(deprecated)]
pub async fn cancel_recovery_agent_update(&self) -> Result<String, WalletKitError> {
let request_id = self.inner.cancel_recovery_agent_update().await?;

Ok(request_id.to_string())
}

/// Reverts an in-flight recovery agent update during the revert window
/// (WIP-102).
///
/// Must be called within the revert window after
/// [`Self::update_recovery_agent`]. During that window any authenticator
/// can revert the update; the previous recovery agent stays effective
/// until the window expires.
///
/// Signs an EIP-712 `CancelRecoveryAgentUpdate` payload (the typehash is
/// reused on V2) and submits it to the gateway.
///
/// # Errors
/// Returns a network error if the gateway request fails.
pub async fn revert_recovery_agent_update(&self) -> Result<String, WalletKitError> {
let request_id = self.inner.revert_recovery_agent_update().await?;

Ok(request_id.to_string())
}
}

#[cfg(not(feature = "storage"))]
Expand Down
Loading