hotstuff-python-sdk 0.0.1b2__tar.gz → 0.0.1b3__tar.gz

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.
Files changed (40) hide show
  1. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/PKG-INFO +564 -158
  2. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/README.md +563 -157
  3. hotstuff_python_sdk-0.0.1b3/hotstuff/__init__.py +123 -0
  4. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/apis/exchange.py +4 -7
  5. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/apis/info.py +7 -7
  6. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/apis/subscription.py +9 -22
  7. hotstuff_python_sdk-0.0.1b3/hotstuff/exceptions.py +72 -0
  8. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/exchange/account.py +1 -23
  9. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/exchange/collateral.py +2 -24
  10. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/exchange/trading.py +47 -16
  11. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/exchange/vault.py +2 -24
  12. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/info/account.py +54 -32
  13. hotstuff_python_sdk-0.0.1b3/hotstuff/methods/info/global.py +65 -0
  14. hotstuff_python_sdk-0.0.1b2/hotstuff/methods/info/global.py → hotstuff_python_sdk-0.0.1b3/hotstuff/methods/info/market.py +42 -35
  15. hotstuff_python_sdk-0.0.1b2/hotstuff/methods/subscription/global.py → hotstuff_python_sdk-0.0.1b3/hotstuff/methods/subscription/channels.py +39 -39
  16. hotstuff_python_sdk-0.0.1b3/hotstuff/methods/subscription/global.py +55 -0
  17. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/transports/http.py +36 -6
  18. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/utils/__init__.py +2 -0
  19. hotstuff_python_sdk-0.0.1b3/hotstuff/utils/signing.py +97 -0
  20. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/pyproject.toml +8 -1
  21. hotstuff_python_sdk-0.0.1b2/hotstuff/__init__.py +0 -53
  22. hotstuff_python_sdk-0.0.1b2/hotstuff/utils/signing.py +0 -76
  23. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/LICENSE +0 -0
  24. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/apis/__init__.py +0 -0
  25. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/__init__.py +0 -0
  26. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/exchange/__init__.py +0 -0
  27. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/exchange/op_codes.py +0 -0
  28. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/info/__init__.py +0 -0
  29. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/info/explorer.py +0 -0
  30. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/info/vault.py +0 -0
  31. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/methods/subscription/__init__.py +0 -0
  32. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/transports/__init__.py +0 -0
  33. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/transports/websocket.py +0 -0
  34. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/types/__init__.py +0 -0
  35. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/types/clients.py +0 -0
  36. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/types/exchange.py +0 -0
  37. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/types/transports.py +0 -0
  38. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/utils/address.py +0 -0
  39. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/utils/endpoints.py +0 -0
  40. {hotstuff_python_sdk-0.0.1b2 → hotstuff_python_sdk-0.0.1b3}/hotstuff/utils/nonce.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hotstuff-python-sdk
3
- Version: 0.0.1b2
3
+ Version: 0.0.1b3
4
4
  Summary: Python SDK for interacting with Hotstuff L1
5
5
  License: MIT
6
6
  Keywords: hotstuff,trading,blockchain,defi,exchange
@@ -48,6 +48,7 @@ Description-Content-Type: text/markdown
48
48
  - [HttpTransport](#httptransport)
49
49
  - [WebSocketTransport](#websockettransport)
50
50
  - [Advanced Usage](#advanced-usage)
51
+ - [Signing](#signing)
51
52
  - [Error Handling](#error-handling)
52
53
  - [Examples](#examples)
53
54
 
@@ -152,8 +153,16 @@ async def setup():
152
153
  #### Market Data Methods
153
154
 
154
155
  ```python
156
+ import importlib
157
+
158
+ global_methods = importlib.import_module("hotstuff.methods.info.global")
159
+ InstrumentsParams = global_methods.InstrumentsParams
160
+ TickerParams = global_methods.TickerParams
161
+ OrderbookParams = global_methods.OrderbookParams
162
+ TradesParams = global_methods.TradesParams
163
+
155
164
  # Get all instruments (perps, spot, options)
156
- instruments = await info.instruments({"type": "all"})
165
+ instruments = await info.instruments(InstrumentsParams(type="all"))
157
166
 
158
167
  # Get supported collateral
159
168
  collateral = await info.supported_collateral({})
@@ -162,13 +171,13 @@ collateral = await info.supported_collateral({})
162
171
  oracle = await info.oracle({})
163
172
 
164
173
  # Get ticker for a specific symbol
165
- ticker = await info.ticker({"symbol": "BTC-PERP"})
174
+ ticker = await info.ticker(TickerParams(symbol="BTC-PERP"))
166
175
 
167
176
  # Get orderbook with depth
168
- orderbook = await info.orderbook({"symbol": "BTC-PERP", "depth": 20})
177
+ orderbook = await info.orderbook(OrderbookParams(symbol="BTC-PERP", depth=20))
169
178
 
170
179
  # Get recent trades
171
- trades = await info.trades({"symbol": "BTC-PERP", "limit": 50})
180
+ trades = await info.trades(TradesParams(symbol="BTC-PERP", limit=50))
172
181
 
173
182
  # Get mid prices for all instruments
174
183
  mids = await info.mids({})
@@ -187,65 +196,84 @@ chart = await info.chart({
187
196
  #### Account Methods
188
197
 
189
198
  ```python
199
+ from hotstuff.methods.info.account import (
200
+ AccountSummaryParams,
201
+ AccountInfoParams,
202
+ UserBalanceParams,
203
+ OpenOrdersParams,
204
+ PositionsParams,
205
+ OrderHistoryParams,
206
+ TradeHistoryParams,
207
+ FundingHistoryParams,
208
+ TransferHistoryParams,
209
+ AccountHistoryParams,
210
+ UserFeeInfoParams,
211
+ InstrumentLeverageParams,
212
+ ReferralInfoParams,
213
+ ReferralSummaryParams,
214
+ SubAccountsListParams,
215
+ AgentsParams,
216
+ )
217
+
190
218
  user_address = "0x1234..."
191
219
 
192
220
  # Get account summary
193
- summary = await info.account_summary({"user": user_address})
221
+ summary = await info.account_summary(AccountSummaryParams(user=user_address))
194
222
 
195
223
  # Get account info
196
- account_info = await info.account_info({"user": user_address})
224
+ account_info = await info.account_info(AccountInfoParams(user=user_address))
197
225
 
198
226
  # Get user balance
199
- balance = await info.user_balance({"user": user_address})
227
+ balance = await info.user_balance(UserBalanceParams(user=user_address))
200
228
 
201
229
  # Get open orders
202
- open_orders = await info.open_orders({"user": user_address})
230
+ open_orders = await info.open_orders(OpenOrdersParams(user=user_address))
203
231
 
204
232
  # Get current positions
205
- positions = await info.positions({"user": user_address})
233
+ positions = await info.positions(PositionsParams(user=user_address))
206
234
 
207
235
  # Get order history
208
- order_history = await info.order_history({
209
- "user": user_address,
210
- "limit": 100,
211
- })
236
+ order_history = await info.order_history(OrderHistoryParams(
237
+ user=user_address,
238
+ limit=100,
239
+ ))
212
240
 
213
241
  # Get trade history (fills)
214
- trade_history = await info.trade_history({"user": user_address})
242
+ trade_history = await info.trade_history(TradeHistoryParams(user=user_address))
215
243
 
216
244
  # Get funding history
217
- funding_history = await info.funding_history({"user": user_address})
245
+ funding_history = await info.funding_history(FundingHistoryParams(user=user_address))
218
246
 
219
247
  # Get transfer history
220
- transfer_history = await info.transfer_history({"user": user_address})
248
+ transfer_history = await info.transfer_history(TransferHistoryParams(user=user_address))
221
249
 
222
250
  # Get account history with time range
223
- account_history = await info.account_history({
224
- "user": user_address,
225
- "from": int(time.time()) - 86400, # 24h ago
226
- "to": int(time.time()),
227
- })
251
+ account_history = await info.account_history(AccountHistoryParams(
252
+ user=user_address,
253
+ from_time=int(time.time()) - 86400, # 24h ago
254
+ to_time=int(time.time()),
255
+ ))
228
256
 
229
257
  # Get user fee information
230
- fee_info = await info.user_fee_info({"user": user_address})
258
+ fee_info = await info.user_fee_info(UserFeeInfoParams(user=user_address))
231
259
 
232
260
  # Get instrument leverage settings
233
- leverage = await info.instrument_leverage({
234
- "user": user_address,
235
- "instrumentId": 1,
236
- })
261
+ leverage = await info.instrument_leverage(InstrumentLeverageParams(
262
+ user=user_address,
263
+ instrumentId=1,
264
+ ))
237
265
 
238
266
  # Get referral info
239
- referral_info = await info.get_referral_info({"user": user_address})
267
+ referral_info = await info.get_referral_info(ReferralInfoParams(user=user_address))
240
268
 
241
269
  # Get referral summary
242
- referral_summary = await info.referral_summary({"user": user_address})
270
+ referral_summary = await info.referral_summary(ReferralSummaryParams(user=user_address))
243
271
 
244
272
  # Get sub-accounts list
245
- sub_accounts = await info.sub_accounts_list({"user": user_address})
273
+ sub_accounts = await info.sub_accounts_list(SubAccountsListParams(user=user_address))
246
274
 
247
275
  # Get agents
248
- agents = await info.agents({"user": user_address})
276
+ agents = await info.agents(AgentsParams(user=user_address))
249
277
  ```
250
278
 
251
279
  #### Vault Methods
@@ -306,155 +334,222 @@ async def setup():
306
334
 
307
335
  ```python
308
336
  import time
337
+ from hotstuff.methods.exchange.trading import (
338
+ PlaceOrderParams,
339
+ UnitOrder,
340
+ BrokerConfig,
341
+ CancelByOidParams,
342
+ CancelByCloidParams,
343
+ CancelAllParams,
344
+ )
309
345
 
310
346
  # Place order(s)
311
- await exchange.place_order({
312
- "orders": [
313
- {
314
- "instrumentId": 1,
315
- "side": "b", # 'b' for buy, 's' for sell
316
- "positionSide": "LONG", # 'LONG', 'SHORT', or 'BOTH'
317
- "price": "50000.00",
318
- "size": "0.1",
319
- "tif": "GTC", # 'GTC', 'IOC', or 'FOK'
320
- "ro": False, # reduce-only
321
- "po": False, # post-only
322
- "cloid": "my-order-123", # client order ID
323
- "triggerPx": "51000.00", # optional trigger price
324
- "isMarket": False, # optional market order flag
325
- "tpsl": "", # optional: 'tp', 'sl', or ''
326
- "grouping": "normal", # optional: 'position', 'normal', or ''
327
- },
328
- ],
329
- "brokerConfig": { # optional broker configuration
330
- "broker": "0x0000000000000000000000000000000000000000",
331
- "fee": "0.001",
332
- },
333
- "expiresAfter": int(time.time()) + 3600, # 1 hour from now
334
- })
347
+ await exchange.place_order(
348
+ PlaceOrderParams(
349
+ orders=[
350
+ UnitOrder(
351
+ instrument_id=1,
352
+ side="b", # 'b' for buy, 's' for sell
353
+ position_side="BOTH", # 'LONG', 'SHORT', or 'BOTH'
354
+ price="50000.00",
355
+ size="0.1",
356
+ tif="GTC", # 'GTC', 'IOC', or 'FOK'
357
+ ro=False, # reduce-only
358
+ po=False, # post-only
359
+ cloid="my-order-123", # client order ID
360
+ trigger_px="51000.00", # optional trigger price
361
+ is_market=False, # optional market order flag
362
+ tpsl="", # optional: 'tp', 'sl', or ''
363
+ grouping="normal", # optional: 'position', 'normal', or ''
364
+ ),
365
+ ],
366
+ broker_config=BrokerConfig( # optional broker configuration
367
+ broker="0x0000000000000000000000000000000000000000",
368
+ fee="0.001",
369
+ ),
370
+ expires_after=int(time.time() * 1000) + 3600000, # 1 hour from now (in milliseconds)
371
+ )
372
+ )
335
373
 
336
374
  # Cancel order by order ID
337
- await exchange.cancel_by_oid({
338
- "cancels": [
339
- {"oid": 123456, "instrumentId": 1},
340
- {"oid": 123457, "instrumentId": 1},
341
- ],
342
- "expiresAfter": int(time.time()) + 3600,
343
- })
375
+ await exchange.cancel_by_oid(
376
+ CancelByOidParams(
377
+ cancels=[
378
+ {"oid": 123456, "instrumentId": 1},
379
+ {"oid": 123457, "instrumentId": 1},
380
+ ],
381
+ expires_after=int(time.time() * 1000) + 3600000,
382
+ )
383
+ )
344
384
 
345
385
  # Cancel order by client order ID
346
- await exchange.cancel_by_cloid({
347
- "cancels": [{"cloid": "my-order-123", "instrumentId": 1}],
348
- "expiresAfter": int(time.time()) + 3600,
349
- })
386
+ await exchange.cancel_by_cloid(
387
+ CancelByCloidParams(
388
+ cancels=[{"cloid": "my-order-123", "instrumentId": 1}],
389
+ expires_after=int(time.time() * 1000) + 3600000,
390
+ )
391
+ )
350
392
 
351
393
  # Cancel all orders
352
- await exchange.cancel_all({
353
- "expiresAfter": int(time.time()) + 3600,
354
- })
394
+ await exchange.cancel_all(
395
+ CancelAllParams(
396
+ expires_after=int(time.time() * 1000) + 3600000,
397
+ )
398
+ )
355
399
  ```
356
400
 
357
401
  #### Account Management
358
402
 
359
403
  ```python
404
+ from hotstuff import AddAgentParams
405
+ from hotstuff.methods.exchange.account import (
406
+ RevokeAgentParams,
407
+ ApproveBrokerFeeParams,
408
+ UpdatePerpInstrumentLeverageParams,
409
+ CreateReferralCodeParams,
410
+ SetReferrerParams,
411
+ ClaimReferralRewardsParams,
412
+ )
413
+
360
414
  # Add an agent (requires agent private key)
361
- await exchange.add_agent({
362
- "agent_name": "my-trading-bot",
363
- "agent": "0xagent...",
364
- "for_account": "",
365
- "agent_private_key": "0xprivatekey...",
366
- "signer": "0xsigner...",
367
- "valid_until": int(time.time()) + 86400, # 24 hours
368
- })
415
+ await exchange.add_agent(
416
+ AddAgentParams(
417
+ agent_name="my-trading-bot",
418
+ agent="0xagent...",
419
+ for_account="",
420
+ agent_private_key="0xprivatekey...",
421
+ signer="0xsigner...",
422
+ valid_until=int(time.time() * 1000) + 86400000, # 24 hours (in milliseconds)
423
+ )
424
+ )
369
425
 
370
426
  # Revoke an agent
371
- await exchange.revoke_agent({
372
- "agent": "0xagent...",
373
- "for_account": "", # optional: sub-account address
374
- })
427
+ await exchange.revoke_agent(
428
+ RevokeAgentParams(
429
+ agent="0xagent...",
430
+ for_account="", # optional: sub-account address
431
+ )
432
+ )
375
433
 
376
434
  # Update leverage for a perpetual instrument
377
- await exchange.update_perp_instrument_leverage({
378
- "instrument_id": 1,
379
- "leverage": 10, # 10x leverage
380
- })
435
+ await exchange.update_perp_instrument_leverage(
436
+ UpdatePerpInstrumentLeverageParams(
437
+ instrument_id=1,
438
+ leverage=10, # 10x leverage
439
+ )
440
+ )
381
441
 
382
442
  # Approve broker fee
383
- await exchange.approve_broker_fee({
384
- "broker": "0xbroker...",
385
- "max_fee_rate": "0.001", # 0.1% max fee
386
- })
443
+ await exchange.approve_broker_fee(
444
+ ApproveBrokerFeeParams(
445
+ broker="0xbroker...",
446
+ max_fee_rate="0.001", # 0.1% max fee
447
+ )
448
+ )
387
449
 
388
450
  # Create a referral code
389
- await exchange.create_referral_code({
390
- "code": "MY_REFERRAL_CODE",
391
- })
451
+ await exchange.create_referral_code(
452
+ CreateReferralCodeParams(
453
+ code="MY_REFERRAL_CODE",
454
+ )
455
+ )
392
456
 
393
457
  # Set referrer using a referral code
394
- await exchange.set_referrer({
395
- "code": "FRIEND_REFERRAL_CODE",
396
- })
458
+ await exchange.set_referrer(
459
+ SetReferrerParams(
460
+ code="FRIEND_REFERRAL_CODE",
461
+ )
462
+ )
397
463
 
398
464
  # Claim referral rewards
399
- await exchange.claim_referral_rewards({
400
- "collateral_id": 1,
401
- "spot": True, # True for spot account, False for derivatives
402
- })
465
+ await exchange.claim_referral_rewards(
466
+ ClaimReferralRewardsParams(
467
+ collateral_id=1,
468
+ spot=True, # True for spot account, False for derivatives
469
+ )
470
+ )
403
471
  ```
404
472
 
405
473
  #### Collateral Transfer Methods
406
474
 
407
475
  ```python
476
+ from hotstuff.methods.exchange.collateral import (
477
+ AccountSpotWithdrawRequestParams,
478
+ AccountDerivativeWithdrawRequestParams,
479
+ AccountSpotBalanceTransferRequestParams,
480
+ AccountDerivativeBalanceTransferRequestParams,
481
+ AccountInternalBalanceTransferRequestParams,
482
+ )
483
+
408
484
  # Request spot collateral withdrawal to external chain
409
- await exchange.account_spot_withdraw_request({
410
- "collateral_id": 1,
411
- "amount": "100.0",
412
- "chain_id": 1, # Ethereum mainnet
413
- })
485
+ await exchange.account_spot_withdraw_request(
486
+ AccountSpotWithdrawRequestParams(
487
+ collateral_id=1,
488
+ amount="100.0",
489
+ chain_id=1, # Ethereum mainnet
490
+ )
491
+ )
414
492
 
415
493
  # Request derivative collateral withdrawal to external chain
416
- await exchange.account_derivative_withdraw_request({
417
- "collateral_id": 1,
418
- "amount": "100.0",
419
- "chain_id": 1,
420
- })
494
+ await exchange.account_derivative_withdraw_request(
495
+ AccountDerivativeWithdrawRequestParams(
496
+ collateral_id=1,
497
+ amount="100.0",
498
+ chain_id=1,
499
+ )
500
+ )
421
501
 
422
502
  # Transfer spot balance to another address on Hotstuff
423
- await exchange.account_spot_balance_transfer_request({
424
- "collateral_id": 1,
425
- "amount": "50.0",
426
- "destination": "0xrecipient...",
427
- })
503
+ await exchange.account_spot_balance_transfer_request(
504
+ AccountSpotBalanceTransferRequestParams(
505
+ collateral_id=1,
506
+ amount="50.0",
507
+ destination="0xrecipient...",
508
+ )
509
+ )
428
510
 
429
511
  # Transfer derivative balance to another address on Hotstuff
430
- await exchange.account_derivative_balance_transfer_request({
431
- "collateral_id": 1,
432
- "amount": "50.0",
433
- "destination": "0xrecipient...",
434
- })
512
+ await exchange.account_derivative_balance_transfer_request(
513
+ AccountDerivativeBalanceTransferRequestParams(
514
+ collateral_id=1,
515
+ amount="50.0",
516
+ destination="0xrecipient...",
517
+ )
518
+ )
435
519
 
436
520
  # Transfer balance between spot and derivatives accounts
437
- await exchange.account_internal_balance_transfer_request({
438
- "collateral_id": 1,
439
- "amount": "25.0",
440
- "to_derivatives_account": True, # True: spot -> derivatives, False: derivatives -> spot
441
- })
521
+ await exchange.account_internal_balance_transfer_request(
522
+ AccountInternalBalanceTransferRequestParams(
523
+ collateral_id=1,
524
+ amount="25.0",
525
+ to_derivatives_account=True, # True: spot -> derivatives, False: derivatives -> spot
526
+ )
527
+ )
442
528
  ```
443
529
 
444
530
  #### Vault Methods
445
531
 
446
532
  ```python
533
+ from hotstuff.methods.exchange.vault import (
534
+ DepositToVaultParams,
535
+ RedeemFromVaultParams,
536
+ )
537
+
447
538
  # Deposit to a vault
448
- await exchange.deposit_to_vault({
449
- "vault_address": "0xvault...",
450
- "amount": "1000.0",
451
- })
539
+ await exchange.deposit_to_vault(
540
+ DepositToVaultParams(
541
+ vault_address="0xvault...",
542
+ amount="1000.0",
543
+ )
544
+ )
452
545
 
453
546
  # Redeem shares from a vault
454
- await exchange.redeem_from_vault({
455
- "vault_address": "0xvault...",
456
- "shares": "500.0",
457
- })
547
+ await exchange.redeem_from_vault(
548
+ RedeemFromVaultParams(
549
+ vault_address="0xvault...",
550
+ shares="500.0",
551
+ )
552
+ )
458
553
  ```
459
554
 
460
555
  ---
@@ -477,12 +572,18 @@ async def setup():
477
572
  #### Market Subscriptions
478
573
 
479
574
  ```python
575
+ import importlib
576
+
577
+ subscription_methods = importlib.import_module("hotstuff.methods.subscription.global")
578
+ TickerSubscriptionParams = subscription_methods.TickerSubscriptionParams
579
+ TradeSubscriptionParams = subscription_methods.TradeSubscriptionParams
580
+
480
581
  # Subscribe to ticker updates
481
582
  def handle_ticker(data):
482
583
  print(f"Ticker: {data.data}")
483
584
 
484
585
  ticker_sub = await subscriptions.ticker(
485
- {"symbol": "BTC-PERP"},
586
+ TickerSubscriptionParams(symbol="BTC-PERP"),
486
587
  handle_ticker
487
588
  )
488
589
 
@@ -506,7 +607,7 @@ orderbook_sub = await subscriptions.orderbook(
506
607
 
507
608
  # Subscribe to trades
508
609
  trade_sub = await subscriptions.trade(
509
- {"symbol": "BTC-PERP"},
610
+ TradeSubscriptionParams(instrument_id="BTC-PERP"),
510
611
  lambda data: print(f"Trade: {data.data}")
511
612
  )
512
613
 
@@ -773,6 +874,90 @@ ws_transport = WebSocketTransport(
773
874
 
774
875
  ---
775
876
 
877
+ ## Signing
878
+
879
+ ### How Signing Works
880
+
881
+ The SDK uses EIP-712 typed data signing for all exchange actions. Here's what happens under the hood:
882
+
883
+ 1. **Action Encoding**: The action payload is encoded using MessagePack
884
+ 2. **Hashing**: The encoded bytes are hashed with keccak256
885
+ 3. **EIP-712 Signing**: The hash is signed using EIP-712 typed data with the following structure:
886
+
887
+ ```python
888
+ from eth_account import Account
889
+ from eth_account.messages import encode_structured_data
890
+ from eth_utils import keccak
891
+ import msgpack
892
+
893
+ # EIP-712 Domain
894
+ domain = {
895
+ "name": "HotstuffCore",
896
+ "version": "1",
897
+ "chainId": 1,
898
+ "verifyingContract": "0x1234567890123456789012345678901234567890",
899
+ }
900
+
901
+ # EIP-712 Types
902
+ types = {
903
+ "EIP712Domain": [
904
+ {"name": "name", "type": "string"},
905
+ {"name": "version", "type": "string"},
906
+ {"name": "chainId", "type": "uint256"},
907
+ {"name": "verifyingContract", "type": "address"},
908
+ ],
909
+ "Action": [
910
+ {"name": "source", "type": "string"}, # "Testnet" or "Mainnet"
911
+ {"name": "hash", "type": "bytes32"}, # keccak256 of msgpack-encoded action
912
+ {"name": "txType", "type": "uint16"}, # transaction type identifier
913
+ ],
914
+ }
915
+
916
+ # Encode action to msgpack
917
+ action_bytes = msgpack.packb(action)
918
+
919
+ # Hash the payload
920
+ payload_hash = keccak(action_bytes)
921
+
922
+ # Message
923
+ message = {
924
+ "source": "Testnet", # or "Mainnet"
925
+ "hash": payload_hash,
926
+ "txType": tx_type,
927
+ }
928
+
929
+ # Create structured data
930
+ structured_data = {
931
+ "types": types,
932
+ "primaryType": "Action",
933
+ "domain": domain,
934
+ "message": message,
935
+ }
936
+
937
+ # Encode and sign
938
+ encoded_data = encode_structured_data(structured_data)
939
+ signed_message = wallet.sign_message(encoded_data)
940
+ signature = signed_message.signature.hex()
941
+ ```
942
+
943
+ ### Debugging Signature Issues
944
+
945
+ It is recommended to use an existing SDK instead of manually generating signatures. There are many potential ways in which signatures can be wrong. An incorrect signature results in recovering a different signer based on the signature and payload and results in one of the following errors:
946
+
947
+ ```
948
+ "Error: account does not exist."
949
+ ```
950
+
951
+ ```
952
+ "invalid order signer"
953
+ ```
954
+
955
+ where the returned address does not match the public address of the wallet you are signing with. The returned address also changes for different inputs.
956
+
957
+ An incorrect signature does not indicate why it is incorrect which makes debugging more challenging. To debug this it is recommended to read through the SDK carefully and make sure the implementation matches exactly. If that doesn't work, add logging to find where the output diverges.
958
+
959
+ ---
960
+
776
961
  ## Error Handling
777
962
 
778
963
  ### HTTP Errors
@@ -781,9 +966,11 @@ HTTP transport raises exceptions with descriptive messages from the server:
781
966
 
782
967
  ```python
783
968
  try:
784
- await exchange.place_order({
785
- # ... order params
786
- })
969
+ await exchange.place_order(
970
+ PlaceOrderParams(
971
+ # ... order params
972
+ )
973
+ )
787
974
  except Exception as e:
788
975
  print(f"Failed to place order: {e}")
789
976
  ```
@@ -811,6 +998,7 @@ except Exception as e:
811
998
  ```python
812
999
  import asyncio
813
1000
  import time
1001
+ import os
814
1002
  from hotstuff import (
815
1003
  HttpTransport,
816
1004
  WebSocketTransport,
@@ -821,47 +1009,69 @@ from hotstuff import (
821
1009
  WebSocketTransportOptions,
822
1010
  )
823
1011
  from eth_account import Account
1012
+ import importlib
1013
+
1014
+ global_methods = importlib.import_module("hotstuff.methods.info.global")
1015
+ TickerParams = global_methods.TickerParams
1016
+
1017
+ from hotstuff.methods.exchange.trading import (
1018
+ PlaceOrderParams,
1019
+ UnitOrder,
1020
+ BrokerConfig,
1021
+ )
824
1022
 
825
1023
  async def main():
826
1024
  # Setup
827
1025
  http_transport = HttpTransport(HttpTransportOptions(is_testnet=True))
828
1026
  ws_transport = WebSocketTransport(WebSocketTransportOptions(is_testnet=True))
829
1027
 
830
- account = Account.from_key("0xYOUR_PRIVATE_KEY")
1028
+ account = Account.from_key(os.getenv("PRIVATE_KEY"))
831
1029
 
832
1030
  info = InfoClient(transport=http_transport)
833
1031
  exchange = ExchangeClient(transport=http_transport, wallet=account)
834
1032
  subscriptions = SubscriptionClient(transport=ws_transport)
835
1033
 
836
1034
  # Get current market data
837
- ticker = await info.ticker({"symbol": "BTC-PERP"})
1035
+ ticker = await info.ticker(TickerParams(symbol="BTC-PERP"))
838
1036
  print(f"Current price: {ticker}")
839
1037
 
840
1038
  # Subscribe to live updates
841
- async def handle_ticker(data):
1039
+ def handle_ticker(data):
842
1040
  price = data.data.get("last")
843
1041
  print(f"Live price: {price}")
844
1042
 
845
1043
  # Simple trading logic
846
1044
  if price and price < 50000:
847
- try:
848
- await exchange.place_order({
849
- "orders": [{
850
- "instrumentId": 1,
851
- "side": "b",
852
- "positionSide": "LONG",
853
- "price": str(price),
854
- "size": "0.1",
855
- "tif": "GTC",
856
- "ro": False,
857
- "po": False,
858
- "cloid": f"order-{int(time.time())}",
859
- }],
860
- "expiresAfter": int(time.time()) + 3600,
861
- })
862
- print("Order placed!")
863
- except Exception as e:
864
- print(f"Order failed: {e}")
1045
+ asyncio.create_task(place_order(exchange, price))
1046
+
1047
+ async def place_order(exchange, price):
1048
+ try:
1049
+ await exchange.place_order(
1050
+ PlaceOrderParams(
1051
+ orders=[
1052
+ UnitOrder(
1053
+ instrument_id=1,
1054
+ side="b",
1055
+ position_side="BOTH",
1056
+ price=str(price),
1057
+ size="0.1",
1058
+ tif="GTC",
1059
+ ro=False,
1060
+ po=False,
1061
+ cloid=f"order-{int(time.time())}",
1062
+ trigger_px=None,
1063
+ is_market=False,
1064
+ tpsl="",
1065
+ grouping="",
1066
+ )
1067
+ ],
1068
+ broker_config=BrokerConfig(broker="", fee=""),
1069
+ expires_after=int(time.time() * 1000) + 3600000,
1070
+ )
1071
+ )
1072
+ print("Order placed!")
1073
+ except Exception as e:
1074
+ print(f"Order failed: {e}")
865
1075
 
866
1076
  ticker_sub = await subscriptions.ticker(
867
1077
  {"symbol": "BTC-PERP"},
@@ -983,3 +1193,199 @@ if __name__ == "__main__":
983
1193
  asyncio.run(broker_agent_trading_example())
984
1194
  ```
985
1195
 
1196
+ ### WebSocket Subscriptions Example
1197
+
1198
+ ```python
1199
+ import asyncio
1200
+ import importlib
1201
+ from hotstuff import (
1202
+ WebSocketTransport,
1203
+ SubscriptionClient,
1204
+ WebSocketTransportOptions,
1205
+ )
1206
+
1207
+ subscription_methods = importlib.import_module("hotstuff.methods.subscription.global")
1208
+ TickerSubscriptionParams = subscription_methods.TickerSubscriptionParams
1209
+ TradeSubscriptionParams = subscription_methods.TradeSubscriptionParams
1210
+
1211
+
1212
+ async def main():
1213
+ # Create WebSocket transport for testnet
1214
+ transport = WebSocketTransport(
1215
+ WebSocketTransportOptions(is_testnet=True)
1216
+ )
1217
+
1218
+ # Create SubscriptionClient
1219
+ subscriptions = SubscriptionClient(transport=transport)
1220
+
1221
+ try:
1222
+ # Subscribe to ticker updates
1223
+ def handle_ticker(data):
1224
+ print(f"Ticker update: {data.data}")
1225
+
1226
+ print("Subscribing to BTC-PERP ticker...")
1227
+ ticker_sub = await subscriptions.ticker(
1228
+ TickerSubscriptionParams(symbol="BTC-PERP"),
1229
+ handle_ticker
1230
+ )
1231
+
1232
+ # Subscribe to trades
1233
+ def handle_trade(data):
1234
+ print(f"Trade: {data.data}")
1235
+
1236
+ print("Subscribing to BTC-PERP trades...")
1237
+ trade_sub = await subscriptions.trade(
1238
+ TradeSubscriptionParams(instrument_id="BTC-PERP"),
1239
+ handle_trade
1240
+ )
1241
+
1242
+ # Run for 30 seconds
1243
+ print("\nListening to updates for 30 seconds...\n")
1244
+ await asyncio.sleep(30)
1245
+
1246
+ # Unsubscribe
1247
+ print("\nUnsubscribing...")
1248
+ await ticker_sub["unsubscribe"]()
1249
+ await trade_sub["unsubscribe"]()
1250
+
1251
+ finally:
1252
+ # Clean up
1253
+ await transport.disconnect()
1254
+
1255
+
1256
+ if __name__ == "__main__":
1257
+ asyncio.run(main())
1258
+ ```
1259
+
1260
+ ### Collateral Transfer Example
1261
+
1262
+ ```python
1263
+ import asyncio
1264
+ import os
1265
+ from hotstuff import (
1266
+ HttpTransport,
1267
+ ExchangeClient,
1268
+ HttpTransportOptions,
1269
+ )
1270
+ from eth_account import Account
1271
+ from hotstuff.methods.exchange.collateral import (
1272
+ AccountSpotWithdrawRequestParams,
1273
+ AccountDerivativeWithdrawRequestParams,
1274
+ AccountSpotBalanceTransferRequestParams,
1275
+ AccountDerivativeBalanceTransferRequestParams,
1276
+ AccountInternalBalanceTransferRequestParams,
1277
+ )
1278
+
1279
+
1280
+ async def main():
1281
+ transport = HttpTransport(HttpTransportOptions(is_testnet=True))
1282
+ account = Account.from_key(os.getenv("PRIVATE_KEY"))
1283
+ exchange = ExchangeClient(transport=transport, wallet=account)
1284
+
1285
+ try:
1286
+ # Request spot collateral withdrawal to external chain
1287
+ result = await exchange.account_spot_withdraw_request(
1288
+ AccountSpotWithdrawRequestParams(
1289
+ collateral_id=1, # USDC
1290
+ amount="100.0",
1291
+ chain_id=1, # Ethereum mainnet
1292
+ )
1293
+ )
1294
+ print(f"Spot withdraw request result: {result}")
1295
+
1296
+ # Request derivative collateral withdrawal to external chain
1297
+ result = await exchange.account_derivative_withdraw_request(
1298
+ AccountDerivativeWithdrawRequestParams(
1299
+ collateral_id=1, # USDC
1300
+ amount="50.0",
1301
+ chain_id=1, # Ethereum mainnet
1302
+ )
1303
+ )
1304
+ print(f"Derivative withdraw request result: {result}")
1305
+
1306
+ # Transfer spot balance to another address on Hotstuff
1307
+ recipient_address = "0x1234567890123456789012345678901234567890"
1308
+ result = await exchange.account_spot_balance_transfer_request(
1309
+ AccountSpotBalanceTransferRequestParams(
1310
+ collateral_id=1, # USDC
1311
+ amount="25.0",
1312
+ destination=recipient_address,
1313
+ )
1314
+ )
1315
+ print(f"Spot balance transfer result: {result}")
1316
+
1317
+ # Internal transfer between spot and derivatives accounts
1318
+ result = await exchange.account_internal_balance_transfer_request(
1319
+ AccountInternalBalanceTransferRequestParams(
1320
+ collateral_id=1, # USDC
1321
+ amount="10.0",
1322
+ to_derivatives_account=True, # Transfer from spot to derivatives
1323
+ )
1324
+ )
1325
+ print(f"Internal transfer result: {result}")
1326
+
1327
+ except Exception as e:
1328
+ print(f"Error: {e}")
1329
+
1330
+ finally:
1331
+ await transport.close()
1332
+
1333
+
1334
+ if __name__ == "__main__":
1335
+ asyncio.run(main())
1336
+ ```
1337
+
1338
+ ### Vault Operations Example
1339
+
1340
+ ```python
1341
+ import asyncio
1342
+ import os
1343
+ from hotstuff import (
1344
+ HttpTransport,
1345
+ ExchangeClient,
1346
+ HttpTransportOptions,
1347
+ )
1348
+ from eth_account import Account
1349
+ from hotstuff.methods.exchange.vault import (
1350
+ DepositToVaultParams,
1351
+ RedeemFromVaultParams,
1352
+ )
1353
+
1354
+
1355
+ async def main():
1356
+ transport = HttpTransport(HttpTransportOptions(is_testnet=True))
1357
+ account = Account.from_key(os.getenv("PRIVATE_KEY"))
1358
+ exchange = ExchangeClient(transport=transport, wallet=account)
1359
+
1360
+ vault_address = "0x1234567890123456789012345678901234567890"
1361
+
1362
+ try:
1363
+ # Deposit to a vault
1364
+ result = await exchange.deposit_to_vault(
1365
+ DepositToVaultParams(
1366
+ vault_address=vault_address,
1367
+ amount="1000.0",
1368
+ )
1369
+ )
1370
+ print(f"Deposit result: {result}")
1371
+
1372
+ # Redeem shares from a vault
1373
+ result = await exchange.redeem_from_vault(
1374
+ RedeemFromVaultParams(
1375
+ vault_address=vault_address,
1376
+ shares="500.0",
1377
+ )
1378
+ )
1379
+ print(f"Redeem result: {result}")
1380
+
1381
+ except Exception as e:
1382
+ print(f"Error: {e}")
1383
+
1384
+ finally:
1385
+ await transport.close()
1386
+
1387
+
1388
+ if __name__ == "__main__":
1389
+ asyncio.run(main())
1390
+ ```
1391
+