use ethers::prelude::*;
use ethers::signers::{LocalWallet, Signer};
use ethers::utils::to_checksum;
use lit_rust_sdk::{
create_eth_wallet_auth_data, create_lit_client, create_siwe_message_with_resources,
generate_session_key_pair, naga_dev, sign_siwe_with_eoa, AuthConfig, AuthContext, LitAbility,
PkpMintManager, ResourceAbilityRequest,
};
use std::env;
use std::sync::{Arc, OnceLock};
static MINTED_PKP: OnceLock<String> = OnceLock::new();
const HELLO_WORLD_LIT_ACTION: &str = r#"
const go = async () => {
console.log("hello world from Rust SDK!");
// Return a simple response
Lit.Actions.setResponse({ response: "Hello from Lit Action executed by Rust SDK!" });
};
go();
"#;
fn get_rpc_url() -> Option<String> {
env::var("LIT_RPC_URL")
.or_else(|_| env::var("LIT_TXSENDER_RPC_URL"))
.or_else(|_| env::var("LIT_YELLOWSTONE_PRIVATE_RPC_URL"))
.or_else(|_| env::var("LOCAL_RPC_URL"))
.ok()
}
fn normalize_0x_hex(s: String) -> String {
if s.starts_with("0x") {
s
} else {
format!("0x{s}")
}
}
fn get_eoa_private_key() -> Option<String> {
env::var("LIT_EOA_PRIVATE_KEY")
.or_else(|_| env::var("ETHEREUM_PRIVATE_KEY"))
.or_else(|_| env::var("LIVE_MASTER_ACCOUNT"))
.or_else(|_| env::var("LOCAL_MASTER_ACCOUNT"))
.ok()
.map(normalize_0x_hex)
}
async fn get_or_mint_pkp(rpc_url: &str, eoa_private_key: &str) -> String {
if let Ok(pkp) = env::var("LIT_PKP_PUBLIC_KEY").or_else(|_| env::var("PKP_PUBLIC_KEY")) {
println!("Using existing PKP from environment: {}...", &pkp[..20]);
return pkp;
}
if let Some(pkp) = MINTED_PKP.get() {
println!("Using cached minted PKP: {}...", &pkp[..20]);
return pkp.clone();
}
println!("No PKP in environment or cache, minting a new one...");
let wallet: LocalWallet = eoa_private_key
.parse()
.expect("Failed to parse private key");
let provider = Provider::<Http>::try_from(rpc_url).expect("Failed to create provider");
let chain_id = provider
.get_chainid()
.await
.expect("Failed to get chain ID")
.as_u64();
let signer_wallet = wallet.with_chain_id(chain_id);
let client = Arc::new(SignerMiddleware::new(provider, signer_wallet));
let config = naga_dev().with_rpc_url(rpc_url.to_string());
let mint_manager =
PkpMintManager::new(&config, client).expect("Failed to create PkpMintManager");
let key_type = U256::from(2); let key_set_id = "naga-keyset1";
let mint_result = mint_manager
.mint_next(key_type, key_set_id)
.await
.expect("Failed to mint PKP");
let pkp = mint_result.data.pubkey.clone();
println!("Minted new PKP: {}", pkp);
let _ = MINTED_PKP.set(pkp.clone());
pkp
}
#[tokio::test]
async fn test_execute_js_hello_world() {
let _ = dotenvy::dotenv();
let rpc_url = match get_rpc_url() {
Some(url) => url,
None => {
println!("Skipping test - no RPC URL configured");
return;
}
};
let eoa_private_key = match get_eoa_private_key() {
Some(key) => key,
None => {
println!("Skipping test - no EOA private key configured");
return;
}
};
let wallet: LocalWallet = eoa_private_key
.parse()
.expect("Failed to parse private key");
let wallet_address = to_checksum(&wallet.address(), None);
println!("Using wallet address: {}", wallet_address);
let config = naga_dev().with_rpc_url(rpc_url);
let client = create_lit_client(config)
.await
.expect("Failed to connect to Lit Network");
println!("Connected to Lit Network");
let session_key_pair = generate_session_key_pair();
let auth_config = AuthConfig {
capability_auth_sigs: vec![],
expiration: (chrono::Utc::now() + chrono::Duration::minutes(10)).to_rfc3339(),
statement: "Lit Protocol Rust SDK - executeJs test".into(),
domain: "localhost".into(),
resources: vec![ResourceAbilityRequest {
ability: LitAbility::LitActionExecution,
resource_id: "*".into(),
data: None,
}],
};
let nonce = client
.handshake_result()
.core_node_config
.latest_blockhash
.clone();
let siwe_message = create_siwe_message_with_resources(
&wallet_address,
&session_key_pair.public_key,
&auth_config,
&nonce,
)
.expect("Failed to create SIWE message");
let auth_sig = sign_siwe_with_eoa(&eoa_private_key, &siwe_message)
.await
.expect("Failed to sign SIWE message");
let auth_context = AuthContext {
session_key_pair,
auth_config,
delegation_auth_sig: auth_sig,
};
println!("Created session signatures");
println!("Executing Lit Action...");
let response = client
.execute_js(
Some(HELLO_WORLD_LIT_ACTION.to_string()),
None,
None,
&auth_context,
)
.await
.expect("Failed to execute Lit Action");
println!("Lit Action executed successfully!");
println!("Response: {:?}", response.response);
println!("Logs: {}", response.logs);
assert!(
response.logs.contains("hello world from Rust SDK"),
"Logs should contain our console.log output"
);
println!("executeJs test passed!");
}
#[tokio::test]
async fn test_execute_js_with_params() {
let _ = dotenvy::dotenv();
let rpc_url = match get_rpc_url() {
Some(url) => url,
None => {
println!("Skipping test - no RPC URL configured");
return;
}
};
let eoa_private_key = match get_eoa_private_key() {
Some(key) => key,
None => {
println!("Skipping test - no EOA private key configured");
return;
}
};
let wallet: LocalWallet = eoa_private_key
.parse()
.expect("Failed to parse private key");
let wallet_address = to_checksum(&wallet.address(), None);
let config = naga_dev().with_rpc_url(rpc_url);
let client = create_lit_client(config)
.await
.expect("Failed to connect to Lit Network");
let session_key_pair = generate_session_key_pair();
let auth_config = AuthConfig {
capability_auth_sigs: vec![],
expiration: (chrono::Utc::now() + chrono::Duration::minutes(10)).to_rfc3339(),
statement: "Lit Protocol Rust SDK - executeJs with params test".into(),
domain: "localhost".into(),
resources: vec![ResourceAbilityRequest {
ability: LitAbility::LitActionExecution,
resource_id: "*".into(),
data: None,
}],
};
let nonce = client
.handshake_result()
.core_node_config
.latest_blockhash
.clone();
let siwe_message = create_siwe_message_with_resources(
&wallet_address,
&session_key_pair.public_key,
&auth_config,
&nonce,
)
.expect("Failed to create SIWE message");
let auth_sig = sign_siwe_with_eoa(&eoa_private_key, &siwe_message)
.await
.expect("Failed to sign SIWE message");
let auth_context = AuthContext {
session_key_pair,
auth_config,
delegation_auth_sig: auth_sig,
};
let code = r#"
(async () => {
const { name, value } = jsParams;
console.log(`Received name: ${name}, value: ${value}`);
const result = `Hello ${name}, your value is ${value * 2}`;
Lit.Actions.setResponse({ response: result });
})();
"#;
let js_params = serde_json::json!({
"name": "Lit",
"value": 42
});
println!("Executing Lit Action with params...");
let response = client
.execute_js(Some(code.to_string()), None, Some(js_params), &auth_context)
.await
.expect("Failed to execute Lit Action");
println!("Response: {:?}", response.response);
println!("Logs: {}", response.logs);
assert!(
response.logs.contains("Received name: Lit"),
"Logs should contain name parameter"
);
assert!(
response.logs.contains("value: 42"),
"Logs should contain value parameter"
);
println!("executeJs with params test passed!");
}
#[tokio::test]
async fn test_execute_js_signing() {
let _ = dotenvy::dotenv();
let rpc_url = match get_rpc_url() {
Some(url) => url,
None => {
println!("Skipping signing test - no RPC URL configured");
return;
}
};
let eoa_private_key = match get_eoa_private_key() {
Some(key) => key,
None => {
println!("Skipping signing test - no EOA private key configured");
return;
}
};
let pkp_public_key = get_or_mint_pkp(&rpc_url, &eoa_private_key).await;
println!("Using PKP public key: {}", pkp_public_key);
let config = naga_dev().with_rpc_url(rpc_url);
let client = create_lit_client(config).await.expect("Failed to connect");
let nonce = client
.handshake_result()
.core_node_config
.latest_blockhash
.clone();
let auth_data = create_eth_wallet_auth_data(&eoa_private_key, &nonce)
.await
.expect("Failed to create auth data");
let auth_config = AuthConfig {
capability_auth_sigs: vec![],
expiration: (chrono::Utc::now() + chrono::Duration::minutes(10)).to_rfc3339(),
statement: "Lit Protocol Rust SDK - signing test".into(),
domain: "localhost".into(),
resources: vec![ResourceAbilityRequest {
ability: LitAbility::LitActionExecution,
resource_id: "*".into(),
data: None,
}],
};
let auth_context = client
.create_pkp_auth_context(&pkp_public_key, auth_data, auth_config, None, None, None)
.await
.expect("Failed to create PKP auth context");
let signing_lit_action = format!(
r#"
const go = async () => {{
console.log("Starting signing Lit Action");
const utf8Encode = new TextEncoder();
const toSign = utf8Encode.encode('This message is exactly 32 bytes');
const publicKey = "{}";
const sigName = "sig1";
const sigShare = await Lit.Actions.signEcdsa({{ toSign, publicKey, sigName }});
console.log("Signature generation completed");
}};
go();
"#,
pkp_public_key
);
println!("Executing signing Lit Action...");
match client
.execute_js(Some(signing_lit_action), None, None, &auth_context)
.await
{
Ok(response) => {
println!("Signing Lit Action executed successfully!");
println!("Response: {:?}", response.response);
println!("Logs: {}", response.logs);
println!("Signatures: {:?}", response.signatures);
if !response.signatures.is_empty() {
println!("Got {} signature(s)", response.signatures.len());
}
println!("Signing test completed!");
}
Err(e) => {
let err_str = e.to_string();
if err_str.contains("unrecognized signature share format") {
println!("Note: Signature share format issue detected - this is a known SDK issue");
println!("Error: {}", err_str);
println!("The Lit Action was likely executed successfully on the nodes,");
println!("but the SDK failed to parse/combine the signature shares.");
} else {
panic!("Failed to execute signing Lit Action: {}", e);
}
}
}
}