Skip to content

Commit 299cf2b

Browse files
committed
Version 0.8.0
1 parent c69fa7a commit 299cf2b

File tree

10 files changed

+195
-3
lines changed

10 files changed

+195
-3
lines changed

assets/images/coverage.svg

Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from hyperliquid.utils import constants
2+
import example_utils
3+
4+
5+
def main():
6+
address, info, exchange = example_utils.setup(constants.TESTNET_API_URL, skip_ws=True)
7+
8+
if exchange.account_address != exchange.wallet.address:
9+
raise Exception("Agents do not have permission to convert to multi-sig signer")
10+
11+
# the user owning this signer (either the user itself or agent's user) should already be registered as authorized user for the multi-sig user
12+
signer = "0x0000000000000000000000000000000000000000"
13+
multi_sig_user = "0x0000000000000000000000000000000000000001"
14+
convert_result = exchange.convert_to_multi_sig_signer(signer, multi_sig_user)
15+
print(convert_result)
16+
17+
18+
if __name__ == "__main__":
19+
main()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from hyperliquid.utils import constants
2+
import example_utils
3+
4+
5+
def main():
6+
address, info, exchange = example_utils.setup(constants.TESTNET_API_URL, skip_ws=True)
7+
8+
if exchange.account_address != exchange.wallet.address:
9+
raise Exception("Agents do not have permission to convert to multi-sig user")
10+
11+
# authorized users are the users for which one can themselves or agents use as signers
12+
# for multi-sig actions
13+
authorized_user_1 = "0x0000000000000000000000000000000000000000"
14+
authorized_user_2 = "0x0000000000000000000000000000000000000001"
15+
threshold = 1
16+
convert_result = exchange.convert_to_multi_sig_user([authorized_user_1, authorized_user_2], threshold)
17+
print(convert_result)
18+
19+
20+
if __name__ == "__main__":
21+
main()

examples/basic_multi_sig.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from hyperliquid.utils import constants
2+
from hyperliquid.utils.signing import sign_usd_transfer_action, get_timestamp_ms
3+
from hyperliquid.utils.types import Any, List
4+
import example_utils
5+
6+
7+
def main():
8+
address, info, exchange = example_utils.setup(constants.TESTNET_API_URL, skip_ws=True)
9+
multi_sig_wallets = example_utils.setup_multi_sig_wallets()
10+
11+
multi_sig_user = "0x0000000000000000000000000000000000000005"
12+
13+
timestamp = get_timestamp_ms()
14+
action = {
15+
"type": "usdSend",
16+
"signatureChainId": "0x66eee",
17+
"hyperliquidChain": "Testnet",
18+
"destination": "0x0000000000000000000000000000000000000000",
19+
"amount": "100.0",
20+
"time": timestamp,
21+
}
22+
signatures: List[Any] = []
23+
for wallet in multi_sig_wallets:
24+
signature = sign_usd_transfer_action(wallet, action, False)
25+
signatures.append(signature)
26+
27+
multi_sig_result = exchange.multi_sig(multi_sig_user, action, signatures, timestamp)
28+
print(multi_sig_result)
29+
30+
31+
if __name__ == "__main__":
32+
main()

examples/example_utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,18 @@ def setup(base_url=None, skip_ws=False):
3030
raise Exception(error_string)
3131
exchange = Exchange(account, base_url, account_address=address)
3232
return address, info, exchange
33+
34+
35+
def setup_multi_sig_wallets():
36+
config_path = os.path.join(os.path.dirname(__file__), "multi_sig_wallets.json")
37+
with open(config_path) as f:
38+
config = json.load(f)
39+
wallets = []
40+
for wallet_config in config:
41+
account: LocalAccount = eth_account.Account.from_key(wallet_config["secret_key"])
42+
address = wallet_config["account_address"]
43+
if account.address != address:
44+
raise Exception(f"provided signer address {address} does not match private key")
45+
print("loaded multi-sig signer", address)
46+
wallets.append(account)
47+
return wallets
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"comment": "signer 1",
4+
"secret_key": "",
5+
"account_address": ""
6+
},
7+
{
8+
"comment": "signer 2",
9+
"secret_key": "",
10+
"account_address": ""
11+
}
12+
]

hyperliquid/exchange.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import logging
23
import secrets
34

@@ -27,6 +28,8 @@
2728
sign_usd_class_transfer_action,
2829
sign_usd_transfer_action,
2930
sign_withdraw_from_bridge_action,
31+
sign_convert_to_multi_sig_user_action,
32+
sign_convert_to_multi_sig_signer_action,
3033
)
3134
from hyperliquid.utils.types import Any, BuilderInfo, Cloid, List, Meta, Optional, SpotMeta, Tuple
3235

@@ -549,3 +552,58 @@ def approve_builder_fee(self, builder: str, max_fee_rate: str) -> Any:
549552
action = {"maxFeeRate": max_fee_rate, "builder": builder, "nonce": timestamp, "type": "approveBuilderFee"}
550553
signature = sign_approve_builder_fee(self.wallet, action, self.base_url == MAINNET_API_URL)
551554
return self._post_action(action, signature, timestamp)
555+
556+
def convert_to_multi_sig_user(self, authorized_users: List[str], threshold: int) -> Any:
557+
timestamp = get_timestamp_ms()
558+
authorized_users = sorted(authorized_users)
559+
signers = {
560+
"authorizedUsers": authorized_users,
561+
"threshold": threshold,
562+
}
563+
action = {
564+
"type": "convertToMultiSigUser",
565+
"signers": json.dumps(signers),
566+
"nonce": timestamp,
567+
}
568+
signature = sign_convert_to_multi_sig_user_action(self.wallet, action, self.base_url == MAINNET_API_URL)
569+
return self._post_action(
570+
action,
571+
signature,
572+
timestamp,
573+
)
574+
575+
def convert_to_multi_sig_signer(self, signer: str, multi_sig_user: str) -> Any:
576+
timestamp = get_timestamp_ms()
577+
action = {
578+
"type": "convertToMultiSigUser",
579+
"signer": signer,
580+
"multi_sig_user": multi_sig_user,
581+
"nonce": timestamp,
582+
}
583+
signature = sign_convert_to_multi_sig_signer_action(self.wallet, action, self.base_url == MAINNET_API_URL)
584+
return self._post_action(
585+
action,
586+
signature,
587+
timestamp,
588+
)
589+
590+
def multi_sig(self, multi_sig_user, inner_action, signatures, nonce, vault_address=None):
591+
multi_sig_action = {
592+
"type": "multiSig",
593+
"user": multi_sig_user.lower(),
594+
"signatures": signatures,
595+
"inner": inner_action,
596+
}
597+
signature = sign_l1_action(
598+
self.wallet,
599+
multi_sig_action,
600+
vault_address,
601+
nonce,
602+
self.base_url == MAINNET_API_URL,
603+
)
604+
605+
return self._post_action(
606+
multi_sig_action,
607+
signature,
608+
nonce,
609+
)

hyperliquid/info.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,12 @@ def query_referral_state(self, user: str) -> Any:
472472
def query_sub_accounts(self, user: str) -> Any:
473473
return self.post("/info", {"type": "subAccounts", "user": user})
474474

475+
def query_user_to_multi_sig_signers(self, multi_sig_user: str) -> Any:
476+
return self.post("/info", {"type": "userToMultiSigSigners", "user": multi_sig_user})
477+
478+
def query_signer_to_multi_sig_user(self, signer: str) -> Any:
479+
return self.post("/info", {"type": "signerToMultiSigUser", "signer": signer})
480+
475481
def subscribe(self, subscription: Subscription, callback: Callable[[Any], None]) -> int:
476482
if subscription["type"] == "l2Book" or subscription["type"] == "trades" or subscription["type"] == "candle":
477483
subscription["coin"] = self.name_to_coin[subscription["coin"]]

hyperliquid/utils/signing.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,35 @@ def sign_usd_class_transfer_action(wallet, action, is_mainnet):
222222
)
223223

224224

225+
def sign_convert_to_multi_sig_user_action(wallet, action, is_mainnet):
226+
return sign_user_signed_action(
227+
wallet,
228+
action,
229+
[
230+
{"name": "hyperliquidChain", "type": "string"},
231+
{"name": "signers", "type": "string"},
232+
{"name": "nonce", "type": "uint64"},
233+
],
234+
"HyperliquidTransaction:ConvertToMultiSigUser",
235+
is_mainnet,
236+
)
237+
238+
239+
def sign_convert_to_multi_sig_signer_action(wallet, action, is_mainnet):
240+
return sign_user_signed_action(
241+
wallet,
242+
action,
243+
[
244+
{"name": "hyperliquidChain", "type": "string"},
245+
{"name": "signer", "type": "address"},
246+
{"name": "multiSigUser", "type": "address"},
247+
{"name": "nonce", "type": "uint64"},
248+
],
249+
"HyperliquidTransaction:ConvertToMultiSigSigner",
250+
is_mainnet,
251+
)
252+
253+
225254
def sign_agent(wallet, action, is_mainnet):
226255
return sign_user_signed_action(
227256
wallet,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
55

66
[tool.poetry]
77
name = "hyperliquid-python-sdk"
8-
version = "0.7.1"
8+
version = "0.8.0"
99
description = "SDK for Hyperliquid API trading with Python."
1010
readme = "README.md"
1111
authors = ["Hyperliquid <[email protected]>"]

0 commit comments

Comments
 (0)