hotstuff-python-sdk 0.0.1b3__py3-none-any.whl → 0.0.1b5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hotstuff/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  A Python SDK for interacting with Hotstuff L1.
4
4
  """
5
5
 
6
- __version__ = "0.0.1-beta.3"
6
+ __version__ = "0.0.1-beta.5"
7
7
 
8
8
  # Transports
9
9
  from hotstuff.transports import HttpTransport, WebSocketTransport
hotstuff/apis/exchange.py CHANGED
@@ -2,7 +2,7 @@
2
2
  from typing import Optional, Any, Dict, Callable, Awaitable
3
3
  from eth_account import Account
4
4
 
5
- from hotstuff.utils import sign_action, NonceManager
5
+ from hotstuff.utils import sign_action, NonceManager, canonicalize_for_signing
6
6
  from hotstuff.methods.exchange import (
7
7
  trading as TM,
8
8
  account as AM,
@@ -480,28 +480,32 @@ class ExchangeClient:
480
480
  if "nonce" not in params or params["nonce"] is None:
481
481
  params["nonce"] = await self.nonce()
482
482
 
483
- # Sign the action
483
+ # Canonicalize key order so msgpack bytes match backend (fixes "invalid order signer"
484
+ # for cancelByOid / cancelByCloid when backend expects deterministic key order)
485
+ params_canonical = canonicalize_for_signing(params)
486
+
487
+ # Sign the action (sign_action also canonicalizes; we pass canonical for consistency)
484
488
  signature = sign_action(
485
489
  wallet=self.wallet,
486
- action=params,
490
+ action=params_canonical,
487
491
  tx_type=EXCHANGE_OP_CODES[action],
488
492
  is_testnet=self.transport.is_testnet,
489
493
  )
490
494
 
491
495
  if execute:
492
- # Send to server
496
+ # Send to server with same canonical payload we signed
493
497
  response = await self.transport.request(
494
498
  "exchange",
495
499
  {
496
500
  "action": {
497
- "data": params,
501
+ "data": params_canonical,
498
502
  "type": str(EXCHANGE_OP_CODES[action]),
499
503
  },
500
504
  "signature": signature,
501
- "nonce": params["nonce"],
505
+ "nonce": params_canonical["nonce"],
502
506
  },
503
507
  signal,
504
508
  )
505
509
  return response
506
510
 
507
- return {"params": params, "signature": signature}
511
+ return {"params": params_canonical, "signature": signature}
@@ -114,9 +114,13 @@ class SpotInstrument(BaseModel):
114
114
 
115
115
 
116
116
  class InstrumentsResponse(BaseModel):
117
- """Instruments response."""
118
- perps: List[PerpInstrument]
119
- spot: List[SpotInstrument]
117
+ """Instruments response.
118
+
119
+ Testnet may return only perps (no spot key); both lists default to empty
120
+ when the key is missing so validation succeeds on testnet and mainnet.
121
+ """
122
+ perps: List[PerpInstrument] = Field(default_factory=list, description="Perpetual instruments")
123
+ spot: List[SpotInstrument] = Field(default_factory=list, description="Spot instruments (may be omitted on testnet)")
120
124
 
121
125
 
122
126
  # Ticker Method
@@ -1,7 +1,7 @@
1
1
  """Utilities package."""
2
2
  from hotstuff.utils.endpoints import ENDPOINTS_URLS
3
3
  from hotstuff.utils.nonce import NonceManager
4
- from hotstuff.utils.signing import sign_action
4
+ from hotstuff.utils.signing import sign_action, canonicalize_for_signing
5
5
  from hotstuff.utils.address import validate_ethereum_address
6
6
  from hotstuff.methods.exchange.op_codes import EXCHANGE_OP_CODES
7
7
 
@@ -9,6 +9,7 @@ __all__ = [
9
9
  "ENDPOINTS_URLS",
10
10
  "NonceManager",
11
11
  "sign_action",
12
+ "canonicalize_for_signing",
12
13
  "validate_ethereum_address",
13
14
  "EXCHANGE_OP_CODES",
14
15
  ]
hotstuff/utils/signing.py CHANGED
@@ -13,6 +13,20 @@ except ImportError:
13
13
  _USE_NEW_API = False
14
14
 
15
15
 
16
+ def canonicalize_for_signing(obj):
17
+ """
18
+ Return a copy of obj with all dict keys sorted alphabetically, recursively.
19
+ Use this before signing and in the request body so msgpack bytes are
20
+ deterministic and match backend verification (fixes "invalid order signer"
21
+ for cancelByOid / cancelByCloid when key order differs from backend).
22
+ """
23
+ if isinstance(obj, dict):
24
+ return {k: canonicalize_for_signing(v) for k, v in sorted(obj.items())}
25
+ if isinstance(obj, list):
26
+ return [canonicalize_for_signing(item) for item in obj]
27
+ return obj
28
+
29
+
16
30
  def sign_action(
17
31
  wallet: Account,
18
32
  action: dict,
@@ -34,8 +48,10 @@ def sign_action(
34
48
  Returns:
35
49
  str: The signature (hex string)
36
50
  """
51
+ # Canonicalize key order so msgpack bytes match backend (deterministic signing)
52
+ canonical_action = canonicalize_for_signing(action)
37
53
  # Encode action to msgpack
38
- action_bytes = msgpack.packb(action)
54
+ action_bytes = msgpack.packb(canonical_action)
39
55
 
40
56
  # Hash the payload
41
57
  payload_hash = keccak(action_bytes)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hotstuff-python-sdk
3
- Version: 0.0.1b3
3
+ Version: 0.0.1b5
4
4
  Summary: Python SDK for interacting with Hotstuff L1
5
5
  License: MIT
6
6
  Keywords: hotstuff,trading,blockchain,defi,exchange
@@ -1,6 +1,6 @@
1
- hotstuff/__init__.py,sha256=IRC8Wnob7tQSjyF4HLkae7ZvkBruBrTEYNkotcDZBXA,2810
1
+ hotstuff/__init__.py,sha256=BnEf04tlaT4FE2ed5KxeL_d4_5P_UfXZLauUt6H2l0Y,2810
2
2
  hotstuff/apis/__init__.py,sha256=ijV2ioe7JM2uQTnS3uEzeQCq-4VWkCxAkTmw9Ih9slg,253
3
- hotstuff/apis/exchange.py,sha256=vnR6Yxe-sufhonSsHLAy1M1L3ZmtR_UCi4QVD9omDwM,15277
3
+ hotstuff/apis/exchange.py,sha256=kjGTsT-Avre6L_XKgtn5LXTQSQwaaSXPk9DbmteFIvI,15700
4
4
  hotstuff/apis/info.py,sha256=m6HYolbNKjgi9RArPbHkQPinS1ZvjO78g4nxZg509NA,13441
5
5
  hotstuff/apis/subscription.py,sha256=Seb_-2oGrs0cdXME7ZSI2_YJGqBTuwITtDtbuXYRzIE,7901
6
6
  hotstuff/exceptions.py,sha256=D9KT72qYi3huhVqdVuXrJ50auc0V0Me7rD_z3ZBFam4,1866
@@ -15,7 +15,7 @@ hotstuff/methods/info/__init__.py,sha256=Sa4LfkDAefadl9XiIRM4LRslxAywFmASOddzXZx
15
15
  hotstuff/methods/info/account.py,sha256=ZA3Uk3Ojz3ze6A4rApD8wLjAJfJVYR5DQaq7t7XTOns,12795
16
16
  hotstuff/methods/info/explorer.py,sha256=gAOcdDSvV3p4LoqE8eApvi6VLa0pEd2rb1QlObpj3XM,1685
17
17
  hotstuff/methods/info/global.py,sha256=GXFjPHkZzlj10s_I-d0CFAeOcsQ7UF_CE064zql27eY,1370
18
- hotstuff/methods/info/market.py,sha256=GAjHeDd8UuuKHLalFxJ3ZuYhX08nqS0bwpzApa8WLFE,7063
18
+ hotstuff/methods/info/market.py,sha256=_84HwpTd3kuQMdSuF808CRw4WxMiTLH4HHKdIaMMg1I,7382
19
19
  hotstuff/methods/info/vault.py,sha256=x7aCMz8ETPxmU4CH0GJJXZ-x3XJmvxhbjJ69L_lNHIM,2356
20
20
  hotstuff/methods/subscription/__init__.py,sha256=keqk-_KG9KrX5OOhYGXj6UUkVrE5xKRXNGxOX7Kos98,191
21
21
  hotstuff/methods/subscription/channels.py,sha256=ZywTGMx7vuRwNAcjeJTzjBvyLXUYJMjqR5ovJ0s8fzE,5443
@@ -27,12 +27,12 @@ hotstuff/types/__init__.py,sha256=e8otSSMYmSf-pWFQ5Dyk12OBssmAyM1fo0U0Rf4dKyc,13
27
27
  hotstuff/types/clients.py,sha256=jzsuKN5eSa7aKVBM15AlcXksv1eYHa-enZi3UbuUk44,712
28
28
  hotstuff/types/exchange.py,sha256=QhqEunW_kHs6lsEZEVaiLVU7ZnhcUWyuf-kuUCB58zQ,1937
29
29
  hotstuff/types/transports.py,sha256=MG6weTHsBMAxk3buhkHpih9Ao_j0AJYWTT8DWvUIBnI,2704
30
- hotstuff/utils/__init__.py,sha256=431dP__wBMBC3r-wQ_QwD6Dmgq6rG4a61sLFMc5j5gg,431
30
+ hotstuff/utils/__init__.py,sha256=8hJmRgPY4Y9QD6HgcuIdsrWaX1QtdkjBCm3ew4_aOV0,489
31
31
  hotstuff/utils/address.py,sha256=vUVT4MAR5QM5zJquliYHEbSJXcrQ7XwBkSeNuvq2FE8,996
32
32
  hotstuff/utils/endpoints.py,sha256=l_YZzN0FgfDWVIVDz3Cocx8Mpnait-UbZ9fyfqtI0RI,422
33
33
  hotstuff/utils/nonce.py,sha256=higBLzP0PwI7De9WUEuA7tOTbMcyc-RHc-4C04pRmNg,581
34
- hotstuff/utils/signing.py,sha256=-7XyexQgIGJZdXJ2Hd1kMHILoCRp81OFLtGsFbo0l9I,2783
35
- hotstuff_python_sdk-0.0.1b3.dist-info/LICENSE,sha256=v18SoYCD8lebxaYQWXtlqlwDLs6i_RAqXCHW4VGq6xg,1070
36
- hotstuff_python_sdk-0.0.1b3.dist-info/METADATA,sha256=BtxZfxWZLfg80RC8YHEK1UMVxpNRha2JDAytYKmHK74,36346
37
- hotstuff_python_sdk-0.0.1b3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
38
- hotstuff_python_sdk-0.0.1b3.dist-info/RECORD,,
34
+ hotstuff/utils/signing.py,sha256=njT6R1iSj-x716WICA2nFDYMsFIbzfIjQZ1kV5fxPoQ,3511
35
+ hotstuff_python_sdk-0.0.1b5.dist-info/LICENSE,sha256=v18SoYCD8lebxaYQWXtlqlwDLs6i_RAqXCHW4VGq6xg,1070
36
+ hotstuff_python_sdk-0.0.1b5.dist-info/METADATA,sha256=6sLgpq0s5nyBYfdDpHm7vuDYelm6bAF5ZLc9u2hDlGg,36346
37
+ hotstuff_python_sdk-0.0.1b5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
38
+ hotstuff_python_sdk-0.0.1b5.dist-info/RECORD,,