Skip to content

Commit 314290f

Browse files
committed
feat: guardian typescript bindings
1 parent c26c70f commit 314290f

File tree

15 files changed

+1168
-14
lines changed

15 files changed

+1168
-14
lines changed

packages/sdk-platforms/rust/zksync-sso-erc4337/crates/zksync-sso-erc4337-core/src/erc4337/account/modular_smart_account/guardian/remove.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ use alloy::{
2020

2121
#[derive(Clone)]
2222
pub struct RemoveGuardianParams<P: Provider + Send + Sync + Clone> {
23-
guardian_executor: Address,
24-
guardian_to_remove: Address,
25-
account_address: Address,
26-
entry_point_address: Address,
27-
paymaster: Option<PaymasterParams>,
28-
provider: P,
29-
bundler_client: BundlerClient,
30-
signer: Signer,
23+
pub guardian_executor: Address,
24+
pub guardian_to_remove: Address,
25+
pub account_address: Address,
26+
pub entry_point_address: Address,
27+
pub paymaster: Option<PaymasterParams>,
28+
pub provider: P,
29+
pub bundler_client: BundlerClient,
30+
pub signer: Signer,
3131
}
3232

3333
pub async fn remove_guardian<P>(

packages/sdk-platforms/rust/zksync-sso-erc4337/crates/zksync-sso-erc4337-core/src/erc4337/account/modular_smart_account/session.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ pub mod signature_wasm;
1111
pub mod state;
1212
pub mod status;
1313

14-
pub mod contract;
14+
pub(crate) mod contract;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod modular_smart_account;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod guardian;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub mod accept;
2+
pub mod list;
3+
pub mod propose;
4+
pub mod recovery;
5+
pub mod remove;
6+
pub mod status;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::wasm_transport::WasmHttpTransport;
2+
use alloy::{
3+
network::EthereumWallet,
4+
primitives::Address,
5+
providers::ProviderBuilder,
6+
signers::local::PrivateKeySigner,
7+
};
8+
use alloy_rpc_client::RpcClient;
9+
use wasm_bindgen::prelude::*;
10+
use wasm_bindgen_futures::future_to_promise;
11+
use zksync_sso_erc4337_core::erc4337::account::modular_smart_account::guardian::accept::{
12+
accept_guardian, AcceptGuardianParams,
13+
};
14+
15+
/// Accept a proposed guardian for a smart account
16+
///
17+
/// This function allows a guardian to accept their proposed role for a smart account.
18+
/// The guardian must sign the transaction using their private key.
19+
///
20+
/// # Parameters
21+
/// * `rpc_url` - RPC URL for the blockchain network
22+
/// * `guardian_executor` - Address of the GuardianExecutor contract
23+
/// * `account` - Address of the smart account
24+
/// * `guardian_private_key` - Private key of the guardian (0x-prefixed hex string)
25+
///
26+
/// # Returns
27+
/// Promise that resolves to the transaction receipt hash as a hex string
28+
#[wasm_bindgen]
29+
pub fn accept_guardian_wasm(
30+
rpc_url: String,
31+
guardian_executor: String,
32+
account: String,
33+
guardian_private_key: String,
34+
) -> js_sys::Promise {
35+
future_to_promise(async move {
36+
// Parse addresses
37+
let guardian_executor_addr = match guardian_executor.parse::<Address>()
38+
{
39+
Ok(addr) => addr,
40+
Err(e) => {
41+
return Err(JsValue::from_str(&format!(
42+
"Invalid guardian executor address: {}",
43+
e
44+
)));
45+
}
46+
};
47+
48+
let account_addr = match account.parse::<Address>() {
49+
Ok(addr) => addr,
50+
Err(e) => {
51+
return Err(JsValue::from_str(&format!(
52+
"Invalid account address: {}",
53+
e
54+
)));
55+
}
56+
};
57+
58+
// Parse guardian private key
59+
let guardian_key = match guardian_private_key
60+
.trim_start_matches("0x")
61+
.parse::<PrivateKeySigner>()
62+
{
63+
Ok(signer) => signer,
64+
Err(e) => {
65+
return Err(JsValue::from_str(&format!(
66+
"Invalid guardian private key: {}",
67+
e
68+
)));
69+
}
70+
};
71+
72+
let guardian_wallet = EthereumWallet::from(guardian_key);
73+
74+
// Create transport and provider with wallet
75+
let transport = WasmHttpTransport::new(rpc_url);
76+
let client = RpcClient::new(transport.clone(), false);
77+
let guardian_provider = ProviderBuilder::new()
78+
.wallet(guardian_wallet)
79+
.connect_client(client);
80+
81+
// Call the core function
82+
match accept_guardian(AcceptGuardianParams {
83+
guardian_executor: guardian_executor_addr,
84+
account: account_addr,
85+
guardian_provider,
86+
})
87+
.await
88+
{
89+
Ok(receipt) => {
90+
let tx_hash = receipt.transaction_hash;
91+
Ok(JsValue::from_str(&format!("0x{:x}", tx_hash)))
92+
}
93+
Err(e) => Err(JsValue::from_str(&format!(
94+
"Failed to accept guardian: {}",
95+
e
96+
))),
97+
}
98+
})
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
use crate::wasm_transport::WasmHttpTransport;
2+
use alloy::primitives::Address;
3+
use alloy::providers::ProviderBuilder;
4+
use alloy_rpc_client::RpcClient;
5+
use wasm_bindgen::prelude::*;
6+
use wasm_bindgen_futures::future_to_promise;
7+
use zksync_sso_erc4337_core::erc4337::account::modular_smart_account::guardian::list::get_guardians_list;
8+
9+
/// Get the list of guardians for a smart account
10+
///
11+
/// # Parameters
12+
/// * `rpc_url` - RPC URL for the blockchain network
13+
/// * `account_address` - Address of the smart account
14+
/// * `guardian_executor_address` - Address of the GuardianExecutor contract
15+
///
16+
/// # Returns
17+
/// Promise that resolves to a JSON array of guardian addresses (hex strings)
18+
#[wasm_bindgen]
19+
pub fn get_guardians_list_wasm(
20+
rpc_url: String,
21+
account_address: String,
22+
guardian_executor_address: String,
23+
) -> js_sys::Promise {
24+
future_to_promise(async move {
25+
// Parse addresses
26+
let account_addr = match account_address.parse::<Address>() {
27+
Ok(addr) => addr,
28+
Err(e) => {
29+
return Err(JsValue::from_str(&format!(
30+
"Invalid account address: {}",
31+
e
32+
)));
33+
}
34+
};
35+
36+
let guardian_executor_addr =
37+
match guardian_executor_address.parse::<Address>() {
38+
Ok(addr) => addr,
39+
Err(e) => {
40+
return Err(JsValue::from_str(&format!(
41+
"Invalid guardian executor address: {}",
42+
e
43+
)));
44+
}
45+
};
46+
47+
// Create transport and provider
48+
let transport = WasmHttpTransport::new(rpc_url);
49+
let client = RpcClient::new(transport.clone(), false);
50+
let provider = ProviderBuilder::new().connect_client(client);
51+
52+
// Call the core function
53+
match get_guardians_list(account_addr, guardian_executor_addr, provider)
54+
.await
55+
{
56+
Ok(guardians) => {
57+
let addresses: Vec<String> = guardians
58+
.iter()
59+
.map(|addr| format!("0x{:x}", addr))
60+
.collect();
61+
let json = serde_json::to_string(&addresses).map_err(|e| {
62+
JsValue::from_str(&format!(
63+
"Failed to serialize guardians list: {}",
64+
e
65+
))
66+
})?;
67+
Ok(JsValue::from_str(&json))
68+
}
69+
Err(e) => Err(JsValue::from_str(&format!(
70+
"Failed to get guardians list: {}",
71+
e
72+
))),
73+
}
74+
})
75+
}
76+
77+
#[cfg(test)]
78+
mod tests {
79+
use super::*;
80+
use alloy::primitives::address;
81+
use serde_json;
82+
use wasm_bindgen_futures::JsFuture;
83+
use zksync_sso_erc4337_core::{
84+
erc4337::{
85+
account::{
86+
erc7579::module::add::{
87+
AddModuleParams, AddModulePayload, add_module,
88+
},
89+
modular_smart_account::{
90+
deploy::{DeployAccountParams, EOASigners, deploy_account},
91+
guardian::propose::{
92+
ProposeGuardianParams, propose_guardian,
93+
},
94+
test_utilities::fund_account_with_default_amount,
95+
},
96+
},
97+
signer::create_eoa_signer,
98+
},
99+
utils::alloy_utilities::test_utilities::{
100+
TestInfraConfig,
101+
start_anvil_and_deploy_contracts_and_start_bundler_with_config,
102+
},
103+
};
104+
105+
#[tokio::test]
106+
#[cfg(not(target_arch = "wasm32"))]
107+
async fn test_get_guardians_list_wasm() -> eyre::Result<()> {
108+
let (
109+
_node_url,
110+
anvil_instance,
111+
provider,
112+
contracts,
113+
signer_private_key,
114+
bundler,
115+
bundler_client,
116+
) = {
117+
let signer_private_key = "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6".to_string();
118+
let config = TestInfraConfig {
119+
signer_private_key: signer_private_key.clone(),
120+
};
121+
start_anvil_and_deploy_contracts_and_start_bundler_with_config(
122+
&config,
123+
)
124+
.await?
125+
};
126+
127+
let entry_point_address =
128+
address!("0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108");
129+
130+
let factory_address = contracts.account_factory;
131+
let eoa_validator_address = contracts.eoa_validator;
132+
133+
let signer_address =
134+
address!("0xa0Ee7A142d267C1f36714E4a8F75612F20a79720");
135+
let signers = vec![signer_address];
136+
137+
let eoa_signers = EOASigners {
138+
addresses: signers,
139+
validator_address: eoa_validator_address,
140+
};
141+
142+
let account_address = deploy_account(DeployAccountParams {
143+
factory_address,
144+
eoa_signers: Some(eoa_signers),
145+
webauthn_signer: None,
146+
session_validator: None,
147+
id: None,
148+
provider: provider.clone(),
149+
})
150+
.await?;
151+
152+
println!("Account deployed");
153+
154+
fund_account_with_default_amount(account_address, provider.clone())
155+
.await?;
156+
157+
let signer = create_eoa_signer(
158+
signer_private_key.clone(),
159+
eoa_validator_address,
160+
)?;
161+
162+
// Install WebAuthn module
163+
{
164+
let webauthn_module = contracts.webauthn_validator;
165+
add_module(AddModuleParams {
166+
account_address,
167+
module: AddModulePayload::webauthn(webauthn_module),
168+
entry_point_address,
169+
paymaster: None,
170+
provider: provider.clone(),
171+
bundler_client: bundler_client.clone(),
172+
signer: signer.clone(),
173+
})
174+
.await?;
175+
}
176+
177+
let guardian_module = contracts.guardian_executor;
178+
179+
// Install Guardian module
180+
add_module(AddModuleParams {
181+
account_address,
182+
module: AddModulePayload::guardian(guardian_module),
183+
entry_point_address,
184+
paymaster: None,
185+
provider: provider.clone(),
186+
bundler_client: bundler_client.clone(),
187+
signer: signer.clone(),
188+
})
189+
.await?;
190+
191+
// Use a different address for the guardian (not the account owner)
192+
let new_guardian =
193+
address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
194+
195+
// Propose guardian using core function
196+
propose_guardian(ProposeGuardianParams {
197+
guardian_executor: guardian_module,
198+
new_guardian,
199+
account: account_address,
200+
entry_point: entry_point_address,
201+
paymaster: None,
202+
provider: provider.clone(),
203+
bundler_client: bundler_client.clone(),
204+
signer: signer.clone(),
205+
})
206+
.await?;
207+
208+
// Get guardians list using WASM wrapper
209+
let promise = get_guardians_list_wasm(
210+
"http://127.0.0.1:8545".to_string(), // Use default anvil URL
211+
format!("0x{:x}", account_address),
212+
format!("0x{:x}", guardian_module),
213+
);
214+
215+
let result = JsFuture::from(promise)
216+
.await
217+
.map_err(|e| eyre::eyre!("Failed to await promise: {:?}", e))?;
218+
let json_str =
219+
result.as_string().ok_or(eyre::eyre!("Expected string result"))?;
220+
let guardians: Vec<String> = serde_json::from_str(&json_str)?;
221+
222+
eyre::ensure!(
223+
guardians.len() == 1,
224+
"Expected exactly one guardian in the list"
225+
);
226+
eyre::ensure!(
227+
guardians[0].to_lowercase()
228+
== format!("0x{:x}", new_guardian).to_lowercase(),
229+
"Guardian address mismatch"
230+
);
231+
232+
drop(anvil_instance);
233+
drop(bundler);
234+
235+
Ok(())
236+
}
237+
}

0 commit comments

Comments
 (0)