|
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
|
from eth_account import Account
|
|
from eth_account import Account
|
|
|
from eth_account.signers.local import LocalAccount
|
|
from eth_account.signers.local import LocalAccount
|
|
|
from hyperliquid.exchange import Exchange
|
|
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.config import Config
|
|
|
from pusher.exception import PushError
|
|
from pusher.exception import PushError
|
|
@@ -29,19 +30,28 @@ class Publisher:
|
|
|
|
|
|
|
|
self.kms_signer = None
|
|
self.kms_signer = None
|
|
|
self.enable_kms = False
|
|
self.enable_kms = False
|
|
|
- oracle_account = None
|
|
|
|
|
|
|
+ self.oracle_account = None
|
|
|
if not config.kms.enable_kms:
|
|
if not config.kms.enable_kms:
|
|
|
oracle_pusher_key = Path(config.hyperliquid.oracle_pusher_key_path).read_text().strip()
|
|
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,
|
|
base_url=url,
|
|
|
timeout=config.hyperliquid.publish_timeout)
|
|
timeout=config.hyperliquid.publish_timeout)
|
|
|
for url in self.push_urls]
|
|
for url in self.push_urls]
|
|
|
if config.kms.enable_kms:
|
|
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.enable_kms = True
|
|
|
self.kms_signer = KMSSigner(config, self.publisher_exchanges)
|
|
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_name = config.hyperliquid.market_name
|
|
|
self.market_symbol = config.hyperliquid.market_symbol
|
|
self.market_symbol = config.hyperliquid.market_symbol
|
|
|
self.enable_publish = config.hyperliquid.enable_publish
|
|
self.enable_publish = config.hyperliquid.enable_publish
|
|
@@ -68,12 +78,12 @@ class Publisher:
|
|
|
return
|
|
return
|
|
|
else:
|
|
else:
|
|
|
logger.debug("Current oracle price: {}", oracle_px)
|
|
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 = []
|
|
mark_pxs = []
|
|
|
external_perp_pxs = {}
|
|
external_perp_pxs = {}
|
|
|
if self.price_state.hl_mark_price:
|
|
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: "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."
|
|
# TODO: "The markPx cannot be updated such that open interest would be 10x the open interest cap."
|
|
@@ -87,6 +97,12 @@ class Publisher:
|
|
|
all_mark_pxs=mark_pxs,
|
|
all_mark_pxs=mark_pxs,
|
|
|
external_perp_pxs=external_perp_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:
|
|
else:
|
|
|
push_response = self._send_update(
|
|
push_response = self._send_update(
|
|
|
oracle_pxs=oracle_pxs,
|
|
oracle_pxs=oracle_pxs,
|
|
@@ -133,3 +149,43 @@ class Publisher:
|
|
|
self.metrics.push_interval_histogram.record(push_interval, self.metrics_labels)
|
|
self.metrics.push_interval_histogram.record(push_interval, self.metrics_labels)
|
|
|
self.last_push_time = now
|
|
self.last_push_time = now
|
|
|
logger.debug("Push interval: {}", push_interval)
|
|
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)
|