77from eth_account import Account
88from eth_account .signers .local import LocalAccount
99from hyperliquid .exchange import Exchange
10+ from hyperliquid .utils .signing import get_timestamp_ms , sign_multi_sig_l1_action_payload
1011
1112from pusher .config import Config
1213from pusher .exception import PushError
@@ -29,19 +30,28 @@ def __init__(self, config: Config, price_state: PriceState, metrics: Metrics):
2930
3031 self .kms_signer = None
3132 self .enable_kms = False
32- oracle_account = None
33+ self . oracle_account = None
3334 if not config .kms .enable_kms :
3435 oracle_pusher_key = Path (config .hyperliquid .oracle_pusher_key_path ).read_text ().strip ()
35- oracle_account : LocalAccount = Account .from_key (oracle_pusher_key )
36- logger .info ("oracle pusher local pubkey: {}" , oracle_account .address )
37- self .publisher_exchanges = [Exchange (wallet = oracle_account ,
36+ self . oracle_account : LocalAccount = Account .from_key (oracle_pusher_key )
37+ logger .info ("oracle pusher local pubkey: {}" , self . oracle_account .address )
38+ self .publisher_exchanges = [Exchange (wallet = self . oracle_account ,
3839 base_url = url ,
3940 timeout = config .hyperliquid .publish_timeout )
4041 for url in self .push_urls ]
4142 if config .kms .enable_kms :
43+ # TODO: Add KMS/multisig support
44+ if config .multisig .enable_multisig :
45+ raise Exception ("KMS/multisig not yet supported" )
46+
4247 self .enable_kms = True
4348 self .kms_signer = KMSSigner (config , self .publisher_exchanges )
4449
50+ if config .multisig .enable_multisig :
51+ if not config .multisig .multisig_address :
52+ raise Exception ("Multisig enabled but missing multisig address" )
53+ self .multisig_address = config .multisig .multisig_address
54+
4555 self .market_name = config .hyperliquid .market_name
4656 self .market_symbol = config .hyperliquid .market_symbol
4757 self .enable_publish = config .hyperliquid .enable_publish
@@ -68,12 +78,12 @@ def publish(self):
6878 return
6979 else :
7080 logger .debug ("Current oracle price: {}" , oracle_px )
71- oracle_pxs [self .market_symbol ] = oracle_px
81+ oracle_pxs [f" { self .market_name } : { self . market_symbol } " ] = oracle_px
7282
7383 mark_pxs = []
7484 external_perp_pxs = {}
7585 if self .price_state .hl_mark_price :
76- external_perp_pxs [self .market_symbol ] = self .price_state .hl_mark_price .price
86+ external_perp_pxs [f" { self .market_name } : { self . market_symbol } " ] = self .price_state .hl_mark_price .price
7787
7888 # TODO: "Each update can change oraclePx and markPx by at most 1%."
7989 # TODO: "The markPx cannot be updated such that open interest would be 10x the open interest cap."
@@ -87,6 +97,12 @@ def publish(self):
8797 all_mark_pxs = mark_pxs ,
8898 external_perp_pxs = external_perp_pxs ,
8999 )
100+ elif self .multisig_address :
101+ push_response = self ._send_multisig_update (
102+ oracle_pxs = oracle_pxs ,
103+ all_mark_pxs = mark_pxs ,
104+ external_perp_pxs = external_perp_pxs ,
105+ )
90106 else :
91107 push_response = self ._send_update (
92108 oracle_pxs = oracle_pxs ,
@@ -133,3 +149,43 @@ def _record_push_interval_metric(self):
133149 self .metrics .push_interval_histogram .record (push_interval , self .metrics_labels )
134150 self .last_push_time = now
135151 logger .debug ("Push interval: {}" , push_interval )
152+
153+ def _send_multisig_update (self , oracle_pxs , all_mark_pxs , external_perp_pxs ):
154+ for exchange in self .publisher_exchanges :
155+ try :
156+ return self ._send_single_multisig_update (
157+ exchange = exchange ,
158+ oracle_pxs = oracle_pxs ,
159+ all_mark_pxs = all_mark_pxs ,
160+ external_perp_pxs = external_perp_pxs ,
161+ )
162+ except Exception as e :
163+ logger .exception ("_send_single_multisig_update exception for endpoint: {} error: {}" , exchange .base_url , repr (e ))
164+
165+ raise PushError ("all push endpoints failed for multisig" )
166+
167+ def _send_single_multisig_update (self , exchange , oracle_pxs , all_mark_pxs , external_perp_pxs ):
168+ timestamp = get_timestamp_ms ()
169+ oracle_pxs_wire = sorted (list (oracle_pxs .items ()))
170+ mark_pxs_wire = [sorted (list (mark_pxs .items ())) for mark_pxs in all_mark_pxs ]
171+ external_perp_pxs_wire = sorted (list (external_perp_pxs .items ()))
172+ action = {
173+ "type" : "perpDeploy" ,
174+ "setOracle" : {
175+ "dex" : self .market_name ,
176+ "oraclePxs" : oracle_pxs_wire ,
177+ "markPxs" : mark_pxs_wire ,
178+ "externalPerpPxs" : external_perp_pxs_wire ,
179+ },
180+ }
181+ signatures = [sign_multi_sig_l1_action_payload (
182+ wallet = self .oracle_account ,
183+ action = action ,
184+ is_mainnet = not self .use_testnet ,
185+ vault_address = None ,
186+ timestamp = timestamp ,
187+ expires_after = None ,
188+ payload_multi_sig_user = self .multisig_address ,
189+ outer_signer = self .oracle_account .address ,
190+ )]
191+ return exchange .multi_sig (self .multisig_address , action , signatures , timestamp )
0 commit comments