From 7dd229000c5e0b98a57df6bf9265627733b236c3 Mon Sep 17 00:00:00 2001 From: Mike Rolish Date: Tue, 21 Oct 2025 10:47:28 -0500 Subject: [PATCH 1/3] fix(hip-3-pusher): Multisig update support (only 1/N, no KMS) --- apps/hip-3-pusher/config/config.toml | 4 ++ apps/hip-3-pusher/pyproject.toml | 2 +- apps/hip-3-pusher/src/pusher/config.py | 6 +++ apps/hip-3-pusher/src/pusher/publisher.py | 64 +++++++++++++++++++++-- apps/hip-3-pusher/uv.lock | 2 +- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/apps/hip-3-pusher/config/config.toml b/apps/hip-3-pusher/config/config.toml index 845e3577d2..20bd0e554b 100644 --- a/apps/hip-3-pusher/config/config.toml +++ b/apps/hip-3-pusher/config/config.toml @@ -11,6 +11,10 @@ publish_interval = 3.0 publish_timeout = 5.0 enable_publish = false +[multisig] +enable_multisig = false +multisig_address = "0x0000000000000000000000000000000000000005" + [kms] enable_kms = false aws_kms_key_id_path = "/path/to/aws_kms_key_id.txt" diff --git a/apps/hip-3-pusher/pyproject.toml b/apps/hip-3-pusher/pyproject.toml index 9fe1638191..715b53c917 100644 --- a/apps/hip-3-pusher/pyproject.toml +++ b/apps/hip-3-pusher/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hip-3-pusher" -version = "0.1.6" +version = "0.1.7" description = "Hyperliquid HIP-3 market oracle pusher" readme = "README.md" requires-python = "==3.13.*" diff --git a/apps/hip-3-pusher/src/pusher/config.py b/apps/hip-3-pusher/src/pusher/config.py index b9e71bb941..9c7a2e600c 100644 --- a/apps/hip-3-pusher/src/pusher/config.py +++ b/apps/hip-3-pusher/src/pusher/config.py @@ -10,6 +10,11 @@ class KMSConfig(BaseModel): aws_kms_key_id_path: Optional[FilePath] = None +class MultisigConfig(BaseModel): + enable_multisig: bool + multisig_address: Optional[str] = None + + class LazerConfig(BaseModel): lazer_urls: list[str] lazer_api_key: str @@ -52,3 +57,4 @@ class Config(BaseModel): kms: KMSConfig lazer: LazerConfig hermes: HermesConfig + multisig: MultisigConfig diff --git a/apps/hip-3-pusher/src/pusher/publisher.py b/apps/hip-3-pusher/src/pusher/publisher.py index 0802b74a7d..f930206295 100644 --- a/apps/hip-3-pusher/src/pusher/publisher.py +++ b/apps/hip-3-pusher/src/pusher/publisher.py @@ -7,6 +7,7 @@ from eth_account import Account from eth_account.signers.local import LocalAccount from hyperliquid.exchange import Exchange +from hyperliquid.utils.signing import get_timestamp_ms, sign_multi_sig_l1_action_payload from pusher.config import Config from pusher.exception import PushError @@ -29,19 +30,28 @@ def __init__(self, config: Config, price_state: PriceState, metrics: Metrics): self.kms_signer = None self.enable_kms = False - oracle_account = None + self.oracle_account = None if not config.kms.enable_kms: oracle_pusher_key = Path(config.hyperliquid.oracle_pusher_key_path).read_text().strip() - oracle_account: LocalAccount = Account.from_key(oracle_pusher_key) - logger.info("oracle pusher local pubkey: {}", oracle_account.address) - self.publisher_exchanges = [Exchange(wallet=oracle_account, + self.oracle_account: LocalAccount = Account.from_key(oracle_pusher_key) + logger.info("oracle pusher local pubkey: {}", self.oracle_account.address) + self.publisher_exchanges = [Exchange(wallet=self.oracle_account, base_url=url, timeout=config.hyperliquid.publish_timeout) for url in self.push_urls] if config.kms.enable_kms: + # TODO: Add KMS/multisig support + if config.multisig.enable_multisig: + raise Exception("KMS/multisig not yet supported") + self.enable_kms = True self.kms_signer = KMSSigner(config, self.publisher_exchanges) + if config.multisig.enable_multisig: + if not config.multisig.multisig_address: + raise Exception("Multisig enabled but missing multisig address") + self.multisig_address = config.multisig.multisig_address + self.market_name = config.hyperliquid.market_name self.market_symbol = config.hyperliquid.market_symbol self.enable_publish = config.hyperliquid.enable_publish @@ -87,6 +97,12 @@ def publish(self): all_mark_pxs=mark_pxs, external_perp_pxs=external_perp_pxs, ) + elif self.multisig_address: + push_response = self._send_multisig_update( + oracle_pxs=oracle_pxs, + all_mark_pxs=mark_pxs, + external_perp_pxs=external_perp_pxs, + ) else: push_response = self._send_update( oracle_pxs=oracle_pxs, @@ -133,3 +149,43 @@ def _record_push_interval_metric(self): self.metrics.push_interval_histogram.record(push_interval, self.metrics_labels) self.last_push_time = now logger.debug("Push interval: {}", push_interval) + + def _send_multisig_update(self, oracle_pxs, all_mark_pxs, external_perp_pxs): + for exchange in self.publisher_exchanges: + try: + return self._send_single_multisig_update( + exchange=exchange, + oracle_pxs=oracle_pxs, + all_mark_pxs=all_mark_pxs, + external_perp_pxs=external_perp_pxs, + ) + except Exception as e: + logger.exception("_send_single_multisig_update exception for endpoint: {} error: {}", exchange.base_url, repr(e)) + + raise PushError("all push endpoints failed for multisig") + + def _send_single_multisig_update(self, exchange, oracle_pxs, all_mark_pxs, external_perp_pxs): + timestamp = get_timestamp_ms() + oracle_pxs_wire = sorted(list(oracle_pxs.items())) + mark_pxs_wire = [sorted(list(mark_pxs.items())) for mark_pxs in all_mark_pxs] + external_perp_pxs_wire = sorted(list(external_perp_pxs.items())) + action = { + "type": "perpDeploy", + "setOracle": { + "dex": self.market_name, + "oraclePxs": oracle_pxs_wire, + "markPxs": mark_pxs_wire, + "externalPerpPxs": external_perp_pxs_wire, + }, + } + signatures = [sign_multi_sig_l1_action_payload( + wallet=self.oracle_account, + action=action, + is_mainnet=not self.use_testnet, + vault_address=None, + timestamp=timestamp, + expires_after=None, + payload_multi_sig_user=self.multisig_address, + outer_signer=self.oracle_account.address, + )] + return exchange.multi_sig(self.multisig_address, action, signatures, timestamp) \ No newline at end of file diff --git a/apps/hip-3-pusher/uv.lock b/apps/hip-3-pusher/uv.lock index 7179e9b4d6..e00ae1b177 100644 --- a/apps/hip-3-pusher/uv.lock +++ b/apps/hip-3-pusher/uv.lock @@ -329,7 +329,7 @@ wheels = [ [[package]] name = "hip-3-pusher" -version = "0.1.6" +version = "0.1.7" source = { editable = "." } dependencies = [ { name = "boto3" }, From 81f92b3d99c911a18667206a0763d6127d19c6dd Mon Sep 17 00:00:00 2001 From: Mike Rolish Date: Wed, 22 Oct 2025 22:37:26 -0500 Subject: [PATCH 2/3] fix market name --- apps/hip-3-pusher/src/pusher/publisher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/hip-3-pusher/src/pusher/publisher.py b/apps/hip-3-pusher/src/pusher/publisher.py index f930206295..892f426814 100644 --- a/apps/hip-3-pusher/src/pusher/publisher.py +++ b/apps/hip-3-pusher/src/pusher/publisher.py @@ -78,12 +78,12 @@ def publish(self): return else: logger.debug("Current oracle price: {}", oracle_px) - oracle_pxs[self.market_symbol] = oracle_px + oracle_pxs[f"{self.market_name}:{self.market_symbol}"] = oracle_px mark_pxs = [] external_perp_pxs = {} if self.price_state.hl_mark_price: - external_perp_pxs[self.market_symbol] = self.price_state.hl_mark_price.price + external_perp_pxs[f"{self.market_name}:{self.market_symbol}"] = self.price_state.hl_mark_price.price # TODO: "Each update can change oraclePx and markPx by at most 1%." # TODO: "The markPx cannot be updated such that open interest would be 10x the open interest cap." From 9ee96401bf82724b8378c19ec63107e91de6c565 Mon Sep 17 00:00:00 2001 From: Mike Rolish Date: Thu, 23 Oct 2025 10:02:56 -0500 Subject: [PATCH 3/3] newline --- apps/hip-3-pusher/src/pusher/publisher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/hip-3-pusher/src/pusher/publisher.py b/apps/hip-3-pusher/src/pusher/publisher.py index 892f426814..a1bb600d0d 100644 --- a/apps/hip-3-pusher/src/pusher/publisher.py +++ b/apps/hip-3-pusher/src/pusher/publisher.py @@ -188,4 +188,4 @@ def _send_single_multisig_update(self, exchange, oracle_pxs, all_mark_pxs, exter payload_multi_sig_user=self.multisig_address, outer_signer=self.oracle_account.address, )] - return exchange.multi_sig(self.multisig_address, action, signatures, timestamp) \ No newline at end of file + return exchange.multi_sig(self.multisig_address, action, signatures, timestamp)