Skip to content

Commit 8714039

Browse files
author
Vadim Nicolai
committed
Add complete multi-signature functionality with EIP-712 signing, account conversion, address management, and transaction operations for orders, USDC transfers, and spot transfers including comprehensive tests and examples.
1 parent ae5dd8f commit 8714039

File tree

9 files changed

+726
-102
lines changed

9 files changed

+726
-102
lines changed

src/bin/convert_to_multi_sig_user.rs

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,79 @@ async fn main() {
2323

2424
let (address, exchange_client) = setup_exchange_client().await;
2525

26+
// Ensure we're using the actual user's wallet, not an agent
2627
if address != exchange_client.wallet.address() {
2728
panic!("Agents do not have permission to convert to multi-sig user");
2829
}
2930

31+
// Addresses that will be authorized to sign for the multi-sig account
3032
let authorized_user_1: Address = "0x0000000000000000000000000000000000000000"
3133
.parse()
3234
.unwrap();
3335
let authorized_user_2: Address = "0x0000000000000000000000000000000000000001"
3436
.parse()
3537
.unwrap();
38+
39+
// Threshold: minimum number of signatures required to execute any transaction
40+
// This matches the Python example where threshold is 1
3641
let threshold = 1;
3742

38-
info!("Converting user {} to multi-sig", address);
39-
info!(
40-
"Authorized users: {}, {}",
41-
authorized_user_1, authorized_user_2
42-
);
43-
info!("Threshold: {}", threshold);
43+
info!("=== Convert to Multi-Sig User Example ===");
44+
info!("Current user address: {}", address);
45+
info!("Connected to: {:?}", exchange_client.http_client.base_url);
46+
info!("");
47+
info!("Configuration:");
48+
info!(" Authorized user 1: {}", authorized_user_1);
49+
info!(" Authorized user 2: {}", authorized_user_2);
50+
info!(" Threshold: {}", threshold);
51+
info!("");
52+
53+
// Step 1: Convert the user to a multi-sig account
54+
info!("Step 1: Converting to multi-sig account...");
55+
match exchange_client.convert_to_multi_sig(threshold, None).await {
56+
Ok(response) => {
57+
info!("Convert to multi-sig response: {:?}", response);
58+
info!("Successfully converted to multi-sig!");
59+
}
60+
Err(e) => {
61+
info!("Convert to multi-sig failed (this is expected if already converted or on testnet): {}", e);
62+
}
63+
}
64+
65+
// Step 2: Add authorized addresses
66+
info!("Step 2: Adding authorized addresses...");
67+
match exchange_client
68+
.update_multi_sig_addresses(
69+
vec![authorized_user_1, authorized_user_2],
70+
vec![], // No addresses to remove
71+
None,
72+
)
73+
.await
74+
{
75+
Ok(response) => {
76+
info!("Update multi-sig addresses response: {:?}", response);
77+
info!("Successfully added authorized addresses!");
78+
}
79+
Err(e) => {
80+
info!("Update multi-sig addresses failed: {}", e);
81+
}
82+
}
4483

45-
info!("Multi-sig conversion functionality is not yet implemented in the Rust SDK");
46-
info!("This example shows the structure and parameters that would be used:");
84+
info!("");
85+
info!("Multi-sig setup complete!");
86+
info!("Now you can use the multi-sig methods with the authorized wallets:");
87+
info!("- multi_sig_order()");
88+
info!("- multi_sig_usdc_transfer()");
89+
info!("- multi_sig_spot_transfer()");
90+
info!("");
91+
info!("IMPORTANT: After converting to multi-sig:");
92+
info!("1. The account can only be controlled by the authorized addresses");
4793
info!(
48-
"- Authorized users: [{}, {}]",
49-
authorized_user_1, authorized_user_2
94+
"2. You need {} signatures to execute any transaction",
95+
threshold
5096
);
51-
info!("- Threshold: {}", threshold);
97+
info!("3. Make sure you have access to the authorized private keys!");
98+
info!("4. This is a one-way conversion - test on testnet first!");
5299

53-
info!("Example completed successfully - multi-sig parameters validated");
100+
info!("Example completed - multi-sig conversion functionality demonstrated");
54101
}

src/bin/multi_sig_order.rs

Lines changed: 66 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use alloy::{primitives::Address, signers::local::PrivateKeySigner};
22
use hyperliquid_rust_sdk::{BaseUrl, ClientLimit, ClientOrder, ClientOrderRequest, ExchangeClient};
33
use log::info;
4-
use serde_json::json;
54

65
fn setup_multi_sig_wallets() -> Vec<PrivateKeySigner> {
6+
// These are example private keys - in production, these would be the authorized
7+
// user wallets that have permission to sign for the multi-sig account
78
let wallets = vec![
89
"0x1234567890123456789012345678901234567890123456789012345678901234",
910
"0x2345678901234567890123456789012345678901234567890123456789012345",
@@ -37,58 +38,85 @@ async fn main() {
3738

3839
let (address, exchange_client) = setup_exchange_client().await;
3940

41+
// Set up the multi-sig wallets that are authorized to sign for the multi-sig user
42+
// Each wallet must belong to a user that has been added as an authorized signer
43+
let multi_sig_wallets = setup_multi_sig_wallets();
44+
45+
// The outer signer is required to be an authorized user or an agent of the
46+
// authorized user of the multi-sig user.
47+
48+
// Address of the multi-sig user that the action will be executed for
49+
// Executing the action requires at least the specified threshold of signatures
50+
// required for that multi-sig user
4051
let multi_sig_user: Address = "0x0000000000000000000000000000000000000005"
4152
.parse()
4253
.unwrap();
4354

44-
let timestamp = chrono::Utc::now().timestamp_millis() as u64;
45-
46-
let action = json!({
47-
"type": "order",
48-
"orders": [{
49-
"a": 0, // ETH asset index
50-
"b": true,
51-
"p": "1800",
52-
"s": "0.01",
53-
"r": false,
54-
"t": {"limit": {"tif": "Gtc"}}
55-
}],
56-
"grouping": "na",
57-
});
58-
59-
let typed_order = ClientOrderRequest {
60-
asset: "ETH".to_string(),
61-
is_buy: true,
62-
reduce_only: false,
63-
limit_px: 1800.0,
64-
sz: 0.01,
65-
cloid: None,
66-
order_type: ClientOrder::Limit(ClientLimit {
67-
tif: "Gtc".to_string(),
68-
}),
69-
};
70-
71-
info!("Multi-sig user: {}", multi_sig_user);
55+
info!("=== Multi-Sig Order Example ===");
56+
info!("Multi-sig user address: {}", multi_sig_user);
7257
info!("Outer signer (current wallet): {}", address);
7358
info!(
7459
"Exchange client connected to: {:?}",
7560
exchange_client.http_client.base_url
7661
);
77-
info!("Action: {}", action);
78-
info!("Typed order: {:?}", typed_order);
79-
info!("Timestamp: {}", timestamp);
80-
81-
let multi_sig_wallets = setup_multi_sig_wallets();
8262
info!(
83-
"Multi-sig wallets: {:?}",
63+
"Authorized wallets ({} total): {:?}",
64+
multi_sig_wallets.len(),
8465
multi_sig_wallets
8566
.iter()
8667
.map(|w| w.address())
8768
.collect::<Vec<_>>()
8869
);
8970

90-
info!("Multi-sig order functionality is not yet implemented in the Rust SDK");
91-
info!("This example shows the structure and parameters that would be used:");
71+
// Define the multi-sig inner action - in this case, placing an order
72+
// This matches the Python example: asset index 4, buy, price 1100, size 0.2
73+
let order = ClientOrderRequest {
74+
asset: "ETH".to_string(), // Asset index 4 in Python corresponds to ETH
75+
is_buy: true,
76+
reduce_only: false,
77+
limit_px: 1100.0,
78+
sz: 0.2,
79+
cloid: None,
80+
order_type: ClientOrder::Limit(ClientLimit {
81+
tif: "Gtc".to_string(),
82+
}),
83+
};
84+
85+
info!("");
86+
info!("Order details: {:?}", order);
87+
info!("Executing multi-sig order...");
88+
info!(
89+
"Collecting signatures from {} authorized wallets...",
90+
multi_sig_wallets.len()
91+
);
92+
93+
// Execute the multi-sig order
94+
// This will collect signatures from all provided wallets and submit them together
95+
// The action will only succeed if enough valid signatures are provided (>= threshold)
96+
match exchange_client
97+
.multi_sig_order(multi_sig_user, order, &multi_sig_wallets)
98+
.await
99+
{
100+
Ok(response) => {
101+
info!("✓ Multi-sig order placed successfully!");
102+
info!("Response: {:?}", response);
103+
}
104+
Err(e) => {
105+
info!("✗ Multi-sig order failed: {}", e);
106+
info!("");
107+
info!("This is expected if:");
108+
info!(" • The multi-sig user is not properly configured");
109+
info!(" • The provided wallets are not authorized signers");
110+
info!(" • Not enough signatures provided to meet threshold");
111+
info!("");
112+
info!("To use in production:");
113+
info!(" 1. Convert a user to multi-sig: convert_to_multi_sig()");
114+
info!(" 2. Add authorized addresses: update_multi_sig_addresses()");
115+
info!(" 3. Use those authorized wallets to sign transactions");
116+
info!(" 4. Ensure you provide >= threshold number of valid signatures");
117+
}
118+
}
92119

93-
info!("Example completed successfully - multi-sig order parameters validated");
120+
info!("");
121+
info!("Example completed");
94122
}
Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use alloy::{primitives::Address, signers::local::PrivateKeySigner};
22
use hyperliquid_rust_sdk::{BaseUrl, ExchangeClient};
33
use log::info;
4-
use serde_json::json;
54

65
fn setup_multi_sig_wallets() -> Vec<PrivateKeySigner> {
76
let wallets = vec![
@@ -27,45 +26,30 @@ async fn setup_exchange_client() -> (Address, ExchangeClient) {
2726
let exchange_client = ExchangeClient::new(None, wallet, Some(BaseUrl::Testnet), None, None)
2827
.await
2928
.unwrap();
30-
29+
3130
(address, exchange_client)
3231
}
3332

3433
#[tokio::main]
3534
async fn main() {
3635
env_logger::init();
37-
36+
3837
let (address, exchange_client) = setup_exchange_client().await;
3938

39+
// The multi-sig user address (this would be the address that was converted to multi-sig)
4040
let multi_sig_user: Address = "0x0000000000000000000000000000000000000005"
4141
.parse()
4242
.unwrap();
4343

44-
let timestamp = chrono::Utc::now().timestamp_millis() as u64;
45-
46-
let action = json!({
47-
"type": "spotDeploy",
48-
"registerToken2": {
49-
"spec": {
50-
"name": "TESTH",
51-
"szDecimals": 2,
52-
"weiDecimals": 8
53-
},
54-
"maxGas": 1000000000000u64,
55-
"fullName": "Example multi-sig spot deploy"
56-
}
57-
});
44+
// Set up the multi-sig wallets that are authorized to sign for the multi-sig user
45+
let multi_sig_wallets = setup_multi_sig_wallets();
5846

5947
info!("Multi-sig user: {}", multi_sig_user);
6048
info!("Outer signer (current wallet): {}", address);
6149
info!(
6250
"Exchange client connected to: {:?}",
6351
exchange_client.http_client.base_url
6452
);
65-
info!("Action: {}", action);
66-
info!("Timestamp: {}", timestamp);
67-
68-
let multi_sig_wallets = setup_multi_sig_wallets();
6953
info!(
7054
"Multi-sig wallets: {:?}",
7155
multi_sig_wallets
@@ -74,8 +58,19 @@ async fn main() {
7458
.collect::<Vec<_>>()
7559
);
7660

77-
info!("Multi-sig register token functionality is not yet implemented in the Rust SDK");
78-
info!("This example shows the structure and parameters that would be used:");
61+
info!("Multi-sig register token functionality requires custom action handling");
62+
info!("The spot token registration action (spotDeploy) is a complex action that:");
63+
info!("1. Requires specific permission levels");
64+
info!("2. Has custom parameters for token specification");
65+
info!("3. Is typically used for specialized spot market operations");
66+
info!("");
67+
info!("For multi-sig spot token registration, you would:");
68+
info!("1. Create a custom spotDeploy action with registerToken2 parameters");
69+
info!("2. Hash the action using the Actions::hash method");
70+
info!("3. Sign with multiple authorized wallets using sign_l1_action_multi_sig");
71+
info!("4. Submit using the post_multi_sig method");
72+
info!("");
73+
info!("This is an advanced operation - consult Hyperliquid documentation for details");
7974

80-
info!("Example completed successfully - multi-sig register token parameters validated");
75+
info!("Example completed - multi-sig register token requirements explained");
8176
}

0 commit comments

Comments
 (0)