digdigdig3 0.1.12

Unified async Rust API for 44 exchange connectors — crypto, stocks, forex. REST + WebSocket.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
# V5 Trait Design Analysis

# digdigdig3 — Multi-Exchange Connector Library


Generated: 2026-03-12
Based on: TRADING_CAPABILITY_MATRIX.md + matrix_batch1/2/3.md (24 exchanges)

---

## Strict Rule (repeated for clarity)


> Connectors MUST NEVER compose base methods to simulate missing features.
> If an exchange has no native endpoint for something, the connector returns
> `ExchangeError::UnsupportedOperation`. All composition belongs in higher layers.

This rule is the lens through which every design decision below is evaluated.

---

## 1. Trading Trait — Is It Correctly Minimal?


### Current five methods


```
market_order        cancel_order
limit_order         get_order
                    get_open_orders
```

### Verdict on each method


**market_order — 24/24 Y. CORRECT. Keep.**

**limit_order — 24/24 Y. CORRECT. Keep.**

**cancel_order — 24/24 Y. CORRECT. Keep.**

**get_open_orders — 24/24 Y. CORRECT. Keep.**

**get_order — Table C shows 23 Y + 1 P (Lighter returns it only via list
filtering). That is effectively universal. CORRECT. Keep.**

### What is MISSING from Trading that is universal


**`get_order_history` — 24/24 Y. THIS IS MISSING FROM THE BASE TRAIT.**

Table C, column GetHistory: 24/24 exchanges support it, including Upbit (spot
minimal floor), Jupiter (DEX), GMX (via GraphQL). Every single exchange that
can place orders also exposes order history. This is not a composition — it is
a native endpoint on every platform. It MUST be in the base Trading trait.

Its absence from the current trait is an oversight. A consumer of digdigdig3
that calls `get_order_history` would today need to downcast to an exchange-
specific type or use an extension trait, which defeats the purpose of a unified
abstraction.

### What SHOULD be removed from Trading


Nothing should be removed. All five current methods are universal.

However, the signature of `market_order` and `limit_order` deserves scrutiny.
Having two separate methods for order types (instead of one `place_order` with
an `OrderType` parameter) means every new order type variant requires a new
method on the base trait. The matrix proposal in section 4 of
TRADING_CAPABILITY_MATRIX.md already proposes a unified `place_order(req:
OrderRequest)`. This is the correct design — `OrderRequest` carries the order
type enum, and the parser/connector rejects unsupported types with
`UnsupportedOperation`. The current split into `market_order` / `limit_order`
is premature type-level duplication.

### Proposed fix


```rust
pub trait Trading: ExchangeIdentity {
    // Unified placement: OrderRequest.order_type = Market | Limit | ...
    // Connector returns UnsupportedOperation for any type it cannot natively execute.
    async fn place_order(&self, req: OrderRequest) -> ExchangeResult<Order>;

    async fn cancel_order(&self, symbol: &str, order_id: &str,
                          account_type: AccountType) -> ExchangeResult<Order>;

    async fn get_order(&self, symbol: &str, order_id: &str,
                       account_type: AccountType) -> ExchangeResult<Order>;

    async fn get_open_orders(&self, symbol: Option<&str>,
                             account_type: AccountType) -> ExchangeResult<Vec<Order>>;

    // ADDED — universally supported, was missing:
    async fn get_order_history(&self, filter: OrderHistoryFilter,
                               account_type: AccountType) -> ExchangeResult<Vec<Order>>;
}
```

---

## 2. Account Trait — Is It Correctly Minimal?


### Current two methods


```
get_balance(asset: Option<Asset>, account_type) -> Vec<Balance>
get_account_info(account_type) -> AccountInfo
```

### Verdict on get_balance


Table E, column Balances: 23 Y + 1 P (GMX is partial — requires on-chain
`balanceOf` calls). Given that even GMX can produce a balance value (just via
a different mechanism), this is effectively 24/24. CORRECT. Keep.

### Verdict on get_account_info


`get_account_info` is currently in the trait but the matrix does not have a
direct "account info" column. Looking at what account_info would contain:
fee tier, trading limits, account status, VIP level. These are exchange-
specific fields. Returning an opaque `AccountInfo` struct that is mostly empty
for minimal exchanges (Upbit, GMX, Jupiter) is fine, but it begs the question
of what the base contract actually guarantees.

If `AccountInfo` is defined as containing only fields that are universally
available, it can stay. If it tries to carry fee schedules, leverage limits,
and IP whitelist status, those belong in extension traits.

Recommendation: keep `get_account_info` but define `AccountInfo` minimally —
only fields that every exchange can populate (account_type, available balance
summary). Exchange-specific details go in connector-specific structs.

### Should get_fees() be in base Account? — 23/24


Table E, column Fees: 17 Y + 6 P = 23/24 non-N. Only GMX (N/A — fees
embedded in on-chain calculation) is missing. 23/24 is very strong.

**YES, `get_fees()` should be in the base Account trait.**

Reasoning:
- 23 of 24 exchanges have a native fee endpoint
- The 6 "partial" responses all have fees embedded in order/trade responses —
  a connector can surface them
- GMX is the sole exception and GMX's fee data is available via REST market
  info endpoint — it qualifies as Y at the exchange level even if the
  *account-specific* fee tier does not exist (fees are uniform in GMX V2)
- Fee information is essential for any order sizing calculation; hiding it
  behind an extension trait forces consumers to feature-detect for something
  nearly every exchange provides

The method signature: `get_fees(symbol: Option<&str>) -> ExchangeResult<FeeInfo>`
where `FeeInfo` contains `{ maker_rate: Decimal, taker_rate: Decimal }` at
minimum. Exchanges that only return symbol-level fees can accept `None` as
"use the default tier".

### Proposed fix


```rust
pub trait Account: ExchangeIdentity {
    async fn get_balance(&self, asset: Option<&str>,
                         account_type: AccountType) -> ExchangeResult<Vec<Balance>>;

    async fn get_account_info(&self, account_type: AccountType)
        -> ExchangeResult<AccountInfo>;

    // ADDED — 23/24 exchanges support this natively:
    async fn get_fees(&self, symbol: Option<&str>)
        -> ExchangeResult<FeeInfo>;
}
```

---

## 3. Positions Trait — Is It Correctly Scoped?


### Context: the denominator is 22, not 24


Bitstamp and Upbit are spot-only; their N/A entries mean positions questions
are structurally inapplicable. Jupiter and GMX are also N/A for set_leverage
and margin_mode. The effective denominator for futures capability is 18–22
depending on the capability.

### get_positions — 18 Y + 2 P / 22


Strong majority. Appropriate in a FuturesPositions trait (not base Trading).
Spot-only exchanges implement Trading and Account but not Positions. This
scoping is correct.

### get_funding_rate — 17 Y / 22


17 of 22 futures-capable exchanges have a native funding rate endpoint.
The 5 missing are: Bitget (N), HTX (public only — no private account funding),
MEXC (N), and a couple of others. "Public only" funding rate data is relevant
here: Table D shows that the endpoint may be public (no auth required) but
still exists as a native call. The 17/22 count uses strict "Y" and several
more are partial. Given that funding rates are central to perpetual futures
trading, 17/22 is sufficient to keep it in the Positions trait rather than
an extension. KEEP in Positions.

However, there is a distinction between:
- **`get_funding_rate(symbol)` — current rate**: available on ~17/22
- **`get_funding_history(symbol)` — historical payments**: a separate, less
  universal capability

The current trait only has `get_funding_rate`. This is fine. The history
variant belongs in an extension.

### set_leverage — 15 Y + 2 P / 22


15 Y + 2 P = 17/22. That is 77% of futures exchanges.

The 5 exchanges that cannot natively set leverage: Deribit (N — leverage is
implicit via margin allocation), dYdX V4 (N — leverage is position size /
equity), Jupiter (N/A), GMX (N — leverage is implicit), Bitfinex (P — per-
order parameter only, no account-level set).

**This is the key design question: does 77% qualify for the base Positions
trait or an extension?**

Analysis:
- Deribit and dYdX are major, widely-used exchanges
- For Deribit, `set_leverage` must return `UnsupportedOperation` because there
  is no Deribit endpoint — leverage is implicit
- For dYdX, same: no explicit leverage endpoint exists
- Putting `set_leverage` in base Positions requires these connectors to always
  return `UnsupportedOperation`, which is a runtime failure on a very common
  operation
- A consumer who needs to set leverage before trading perpetuals must either
  check capabilities or receive runtime errors

**Recommendation: move `set_leverage` to an extension trait `LeverageControl`.**

This makes the capability opt-in. Connectors that have native leverage APIs
implement `LeverageControl`. Connectors without it simply don't implement it.
The consumer uses `connector.as_leverage_control()` and handles None gracefully.

Same logic applies marginally to `get_funding_rate` — but 17/22 is stronger
than 15/22, and funding rate is read-only (not an action that can fail with
misleading behavior). Keep funding rate in base Positions.

### Proposed Positions trait


```rust
pub trait Positions: ExchangeIdentity {
    async fn get_positions(&self, symbol: Option<&str>,
                           account_type: AccountType) -> ExchangeResult<Vec<Position>>;

    async fn get_funding_rate(&self, symbol: &str,
                              account_type: AccountType) -> ExchangeResult<FundingRate>;

    // REMOVED: set_leverage — moved to LeverageControl extension
}

// Extension: 15Y+2P of 22 futures exchanges
pub trait LeverageControl: Positions {
    async fn set_leverage(&self, symbol: &str, leverage: u32,
                          account_type: AccountType) -> ExchangeResult<()>;
}
```

---

## 4. Extension Traits — Validation


### BatchOperations — THE COMPOSITION VIOLATION


**Current design: BatchOperations has a DEFAULT IMPL that loops sequentially.**

This is a direct violation of the strict rule. Here is why:

The strict rule says: if an exchange does not have a native batch API endpoint,
return `UnsupportedOperation`. A default implementation that calls
`cancel_order` N times in a loop is precisely the kind of composition that is
forbidden. It silently "works" for exchanges without batch support, making
callers believe they are getting a real batch operation when they are actually
issuing N sequential HTTP calls with N independent round trips.

This matters because:
1. The caller cannot distinguish "this exchange has a real batch cancel that
   atomically removes 20 orders" from "this is looping your 20 sequential
   cancels through our fake impl"
2. Rate limiting behavior is completely different
3. Atomic guarantees differ — a real batch cancel either succeeds entirely or
   fails; a loop can partially succeed
4. Throughput expectations differ by an order of magnitude

**Fix: Remove all default implementations from BatchOperations. Every method
MUST be explicit. Exchanges without native batch endpoints do not implement
this trait at all, and callers get a compile-time `None` from downcasting.**

Table C, column Batch (max): 14 Y + 3 P = 17/24 have native batch creation.
That is still a strong majority. BatchOperations is worth keeping as an
extension — just without the fake default impl.

```rust
// CORRECT — no default implementations
pub trait BatchOperations: Trading {
    /// Native batch order placement.
    /// Only implement if the exchange has a real /batch-orders endpoint.
    async fn create_orders_batch(&self, orders: Vec<OrderRequest>)
        -> ExchangeResult<Vec<OrderResult>>;

    /// Native batch cancel.
    /// Only implement if the exchange has a real cancel-batch endpoint.
    async fn cancel_orders_batch(&self, cancels: Vec<CancelRequest>)
        -> ExchangeResult<Vec<CancelResult>>;
}
```

Exchanges that implement BatchOperations (native batch create): Binance (F
only, 5), Bybit (20/10), OKX (20), KuCoin (S:5), Gate.io (S:10), Bitfinex
(75 mixed), MEXC (S:20/F:50), HTX (10), Bitget (50), BingX (unspec.),
Crypto.com (10), HyperLiquid (unspec.), Lighter (50), Paradex (10),
dYdX (partial via Cosmos tx batching).

Exchanges that do NOT implement BatchOperations: Bitstamp, Kraken (S:15 for
same-pair is the one partial), Coinbase (batch cancel only, not place),
Gemini, Phemex, Upbit, Deribit, Jupiter, GMX (multicall ≠ batch API),
dYdX (Cosmos tx can batch but not a dedicated batch-place endpoint).

### AdvancedOrders — Method list vs matrix


Current methods: `create_trailing_stop`, `create_stop_limit_order`,
`create_oco_order`. All default to `UnsupportedOperation`.

**Problem 1: `create_stop_limit_order` should NOT be in AdvancedOrders.**

Table A StopLimit: 16 Y + 3 P = 19/24 non-N. That is 79% support. Stop-limit
is one of the most common order types across all exchange categories. It is
more prevalent than trailing stop (10/24), OCO (7/24), or bracket (9/24).
Stop-limit belongs either in the base `place_order` call (via `OrderType`
enum) or in a `ConditionalOrders` trait — not in `AdvancedOrders` alongside
rarities like OCO and trailing stop.

Similarly, StopMarket: 19/24 non-N. TP/SL: 19/24 non-N.

Stop, TP, and SL order types should be covered by `OrderType` enum variants in
`OrderRequest` rather than separate trait methods. The connector rejects
unsupported variants with `UnsupportedOperation`. This keeps `place_order`
universal (it always exists) while allowing the `OrderType` to be exchange-
specific.

**Problem 2: What truly belongs in AdvancedOrders?**

Looking at the matrix, "advanced" should mean capabilities that are rare
enough to warrant their own trait interface AND that have genuinely distinct
call signatures from a regular order:

| Type         | Count      | Distinct signature? | Belongs? |
|--------------|------------|---------------------|----------|
| TrailingStop | 10/24      | Yes (offset/percent) | Yes |
| OCO          | 7/24       | Yes (two linked orders) | Yes |
| Bracket      | 9/24 non-N | Yes (entry+TP+SL one call) | Yes |
| Iceberg      | 8/24       | Yes (visible_qty param) | Yes |
| TWAP         | 7/24       | Yes (duration, slices) | Yes |
| StopLimit    | 19/24      | No — just OrderType::StopLimit | No |
| StopMarket   | 19/24      | No — just OrderType::StopMarket | No |
| TP/SL        | 19/24      | No — just OrderType::TakeProfit/StopLoss | No |

**Proposed AdvancedOrders:**

```rust
pub trait AdvancedOrders: Trading {
    async fn place_trailing_stop(&self, req: TrailingStopRequest)
        -> ExchangeResult<Order>;

    async fn place_oco(&self, req: OcoRequest)
        -> ExchangeResult<OcoResponse>;

    async fn place_bracket(&self, req: BracketRequest)
        -> ExchangeResult<BracketResponse>;

    async fn place_iceberg(&self, req: IcebergRequest)
        -> ExchangeResult<Order>;

    async fn place_twap(&self, req: TwapRequest)
        -> ExchangeResult<AlgoOrderResponse>;
    // No default impls. All UnsupportedOperation if not implemented.
}
```

Note: AdvancedOrders methods may each be implemented independently. A
connector that has TWAP but no OCO still implements the AdvancedOrders trait
but its `place_oco` returns `UnsupportedOperation`.

### MarginTrading and Transfers


**MarginTrading: borrow_margin, repay_margin, get_margin_info, set_margin_type**

Table D MarginMode: 13 Y + 4 P / 22. That is majority but not dominant. Set
margin mode is meaningful on CEX futures.

However `borrow_margin` / `repay_margin` are specifically **spot margin
lending** operations. Looking at the matrix — these are relevant only for
exchanges that support spot margin (Bitfinex, Kraken spot margin, Binance
Margin account). That is roughly 5–8 exchanges. This is too narrow for a
shared extension trait.

Recommendation: Split MarginTrading:
- `set_margin_type` (isolated/cross) → move to `LeverageControl` (already
  proposed above, alongside `set_leverage`)
- `borrow_margin` / `repay_margin` → move to a `SpotMarginLending` trait
  (ultra-specialized, 5/24)
- `get_margin_info` → could be part of Account or Positions depending on
  whether it is per-account or per-position

**Transfers: `transfer` (required), `get_transfer_history` (default UnsupportedOperation)**

Table E InternalTransfer: 16 Y + 1 P / 20 applicable. Strong majority. The
current design is reasonable.

However, the `transfer` method being marked as "required" in an extension trait
creates a contradiction: if the trait is optional (extension), all its methods
should either be optional or have sane defaults. Marking one method as required
while others default to UnsupportedOperation within the same extension trait
is logically inconsistent.

Recommendation: Rename to `AccountTransfers`. All methods required (no
defaults). Connectors that don't support it simply don't implement it. This
affects: Jupiter (N/A), GMX (N/A), Paradex (N), Upbit (N — single account
type), Coinbase (limited).

---

## 5. Auth Trait — Is It Flexible Enough?


### Current model


```rust
trait ExchangeAuth: Send + Sync {
    fn sign_request(&self, credentials: &Credentials, req: &mut AuthRequest)
        -> ExchangeResult<()>;
    fn signature_location(&self) -> SignatureLocation; // Headers or QueryParams
}

struct Credentials {
    api_key: String,
    api_secret: String,
    passphrase: Option<String>,
}
```

### Problem: 10 distinct auth mechanisms in the matrix


From Table H:

| Category | Exchanges | Current model handles? |
|----------|-----------|------------------------|
| HMAC-SHA256 (key+secret) | 12 exchanges | YES |
| HMAC-SHA256 + passphrase | OKX, KuCoin, Bitget | YES (passphrase field) |
| HMAC-SHA512 | Gate.io | NO — different algorithm |
| HMAC-SHA384 | Bitfinex | NO — different algorithm |
| JWT (ECDSA P-256) | Coinbase | NO — requires PEM private key, not secret |
| JWT (HMAC signed payload) | Upbit | Partially — but JWT generation logic differs |
| OAuth2 client_credentials | Deribit | NO — requires token refresh flow |
| Ethereum ECDSA (secp256k1) | HyperLiquid, GMX | NO — requires wallet private key |
| Solana Ed25519 | Jupiter | NO — requires Solana keypair |
| STARK EC | Lighter, Paradex | NO — requires STARK key |
| Cosmos secp256k1 | dYdX V4 | NO — requires mnemonic/Cosmos wallet |

The current `Credentials` struct only models HMAC-style exchanges. 8 of 24
exchanges cannot be correctly represented.

### Why the current `sign_request` signature is insufficient


`sign_request` receives `&mut AuthRequest` and must modify it in place. This
works well for HMAC (add headers or query params). But:

- OAuth2 (Deribit): requires an async HTTP call to `public/auth` first to
  obtain a token, then injects the Bearer token. `sign_request` is synchronous
  — it cannot perform network I/O
- On-chain signing (HyperLiquid, Jupiter, GMX, Paradex, dYdX): the "request"
  is not an HTTP request with headers — it is a typed action struct that must
  be serialized and signed with cryptographic primitives. The `AuthRequest`
  abstraction does not model this
- Per-action signing (HyperLiquid): every action carries an ECDSA signature
  in its JSON body, not just the HTTP request headers. The signing is
  interleaved with request construction

### How the auth trait should evolve


The key insight is that auth is NOT uniform across exchange types. There are
two fundamentally different authorization paradigms:

**Paradigm A: HTTP request signing** (CEX norm)
The auth layer decorates HTTP requests with API key + signature headers/params.
The connector builds the request, the auth layer signs it, then it is sent.

**Paradigm B: Transaction signing** (DEX norm)
The auth layer signs typed action payloads (not raw HTTP requests). The
"signed transaction" IS the request body. The connector does not send a
plain HTTP request and attach a signature — it signs a domain object and
submits the signed envelope.

These two paradigms require different abstract interfaces.

**Recommended evolution:**

```rust
/// Marker trait for all auth implementations.
pub trait ExchangeAuth: Send + Sync {}

/// Paradigm A: signs HTTP requests (CEX exchanges).
/// Works for all HMAC variants, JWT-based exchanges, OAuth2 (with caching).
pub trait HttpRequestSigner: ExchangeAuth {
    /// Mutates the request to add auth headers/params.
    /// For OAuth2 (Deribit): implementations hold a cached token and refresh
    /// as needed. The fn signature remains sync but the token refresh must
    /// have been done beforehand via `ensure_token_valid()`.
    fn sign_http_request(&self, req: &mut AuthRequest) -> ExchangeResult<()>;
}

/// Paradigm B: signs typed action payloads (DEX exchanges).
/// Works for HyperLiquid (Ethereum ECDSA), Jupiter (Solana Ed25519),
/// Lighter/Paradex (STARK), dYdX (Cosmos).
pub trait ActionSigner: ExchangeAuth {
    /// Signs a typed action payload and returns the signed envelope.
    /// The `payload` bytes are the canonical serialization of the action
    /// (exchange-specific format). Returns the signature bytes.
    fn sign_action(&self, payload: &[u8]) -> ExchangeResult<Vec<u8>>;

    /// Returns the public key / address associated with this signer.
    fn public_address(&self) -> &str;
}

/// For OAuth2 exchanges (Deribit): token lifecycle management.
pub trait OAuthCredentials: HttpRequestSigner {
    /// Ensures a valid access token is cached. Must be called before sign_http_request.
    async fn ensure_token_valid(&self) -> ExchangeResult<()>;
    fn access_token(&self) -> Option<&str>;
}
```

**Concrete Credentials types** (not a single unified struct):

```rust
// For: Binance, Bybit, MEXC, BingX, Bitstamp, HTX, Phemex, Gemini, Crypto.com
pub struct HmacCredentials {
    pub api_key: String,
    pub api_secret: String,
    pub passphrase: Option<String>,  // OKX, KuCoin, Bitget
    pub algorithm: HmacAlgorithm,    // Sha256, Sha512, Sha384
}

// For: Coinbase (ES256 JWT), Upbit (HMAC-signed JWT)
pub struct JwtCredentials {
    pub key_id: String,
    pub private_key_pem: String,  // PEM for Coinbase ES256; raw secret for Upbit
    pub algorithm: JwtAlgorithm,
}

// For: Deribit
pub struct OAuth2Credentials {
    pub client_id: String,
    pub client_secret: String,
}

// For: HyperLiquid, GMX
pub struct EthereumCredentials {
    pub private_key_hex: String,  // secp256k1 private key
}

// For: Jupiter
pub struct SolanaCredentials {
    pub private_key_bytes: Vec<u8>,  // Ed25519 keypair
    pub api_key: Option<String>,     // x-api-key for REST reads
}

// For: Lighter, Paradex
pub struct StarkCredentials {
    pub private_key: String,  // STARK field element
    pub public_key: Option<String>,
}

// For: dYdX V4
pub struct CosmosCredentials {
    pub mnemonic: String,
    pub chain_id: String,
}
```

The `ExchangeAuth` bound on connector structs changes from accepting a single
`Credentials` to accepting the appropriate concrete type for that exchange.
Each exchange connector is generic over its specific credentials type, not
over a universal opaque `Credentials`.

---

## 6. Missing Traits


### get_order_history — 24/24, completely absent


Already covered in section 1. This is the most significant omission. Must be
added to the base `Trading` trait.

### cancel_all_orders — 20 Y + 2 P / 24


Table C CancelAll: 20 Y + 2 P = 22/24 non-N. Only GMX (N) and dYdX V4 (N —
`MsgBatchCancel` requires explicit order IDs, not a cancel-all primitive) lack
this.

22/24 qualifies this as COMMON, not specialized. It should be in an extension
trait (not base — because 2 exchanges genuinely lack it), but it should be in
a MANDATORY extension rather than an optional specialized one.

Recommendation: create a `OrderManagement` extension trait that sits one level
above `Trading`:

```rust
pub trait OrderManagement: Trading {
    /// Cancel all open orders, optionally filtered by symbol.
    async fn cancel_all_orders(&self, symbol: Option<&str>,
                               account_type: AccountType)
        -> ExchangeResult<CancelAllResponse>;

    /// Modify an existing order's price and/or quantity.
    /// 18/24 exchanges support native amend.
    async fn amend_order(&self, req: AmendRequest)
        -> ExchangeResult<Order>;
}
```

Exchanges implementing `OrderManagement.cancel_all_orders`: 22/24.
Exchanges implementing `OrderManagement.amend_order`: 13 Y + 5 P = 18/24.

GMX and dYdX implement `amend_order` (GMX via `updateOrder` on-chain; dYdX
cannot truly amend but replaces). For strict compliance:
- GMX: `cancel_all_orders``UnsupportedOperation`, `amend_order` → Y
- dYdX: both → `UnsupportedOperation`

These two implementing `amend_order` but not `cancel_all_orders` is fine
because `OrderManagement` is an extension they partially implement.

Actually a cleaner decomposition: separate the two methods into separate traits
given they have different support levels:

```rust
// 22/24 — very high coverage
pub trait CancelAll: Trading {
    async fn cancel_all_orders(&self, symbol: Option<&str>,
                               account_type: AccountType)
        -> ExchangeResult<CancelAllResponse>;
}

// 18/24 — high coverage
pub trait AmendOrder: Trading {
    async fn amend_order(&self, req: AmendRequest,
                         account_type: AccountType)
        -> ExchangeResult<Order>;
}
```

### CancelBySymbol — 16 Y + 1 P / 24


Present in many connectors but not universal. Extension trait. Exchanges like
Kraken (cancel by txid only), Coinbase (no cancel-by-symbol), and several DEXes
don't have it. Not enough coverage for a separate trait — should be a method
in the `CancelAll` trait as an optional second method.

### What the matrix shows that has no trait


Beyond what is already discussed:

**`amend_order` / `modify_order` — 18/24**: Already proposed above as
`AmendOrder` trait. Currently no trait for this common operation.

**`get_position_funding_history` — partial**: Available on many exchanges as
the account funding payment history. Not currently modeled. Should be in
a `FundingHistory` extension under `Positions`.

**`add_remove_margin` — 11 Y + 1 P / 22**: `MarginTrading` extension covers
this with `borrow_margin`/`repay_margin`, but those are spot margin concepts.
For futures, it is `add_margin(symbol, amount)`. These are different operations
with different semantics. Should be a separate `MarginAdjustment` trait:

```rust
pub trait MarginAdjustment: Positions {
    async fn add_margin(&self, symbol: &str, amount: Decimal,
                        account_type: AccountType) -> ExchangeResult<()>;
    async fn remove_margin(&self, symbol: &str, amount: Decimal,
                           account_type: AccountType) -> ExchangeResult<()>;
}
```

---

## 7. Proposed Final Trait Hierarchy


### Design Principles


1. **Core traits** = methods every compliant connector MUST implement. Not
   implementing a core trait method is a compile error.
2. **Extension traits** = optional capabilities. Connectors implement them if
   and only if the exchange has a native endpoint. No default implementations
   except where explicitly noted with strong justification.
3. **UnsupportedOperation** is only used within a trait that a connector HAS
   implemented, for methods within that trait that the exchange partially
   supports (e.g., `AdvancedOrders` implemented but `place_twap` not
   supported by this particular exchange variant).
4. **Never** implement an extension trait with a "fake" implementation that
   composes lower-level primitives.

### Tier 0: Foundation


```rust
/// Identity — every connector must implement.
pub trait ExchangeIdentity: Send + Sync {
    fn exchange_id(&self) -> ExchangeId;
    fn name(&self) -> &str;
    fn account_types(&self) -> &[AccountType];
}
```

### Tier 1: Universal Core Traits (all 24 exchanges)


```rust
/// Core trading — 24/24 exchanges.
/// Market + Limit are always available via OrderType enum in OrderRequest.
/// The connector returns UnsupportedOperation for OrderType variants it
/// cannot natively execute (e.g., StopMarket on Upbit).
pub trait Trading: ExchangeIdentity {
    async fn place_order(&self, req: OrderRequest)
        -> ExchangeResult<Order>;

    async fn cancel_order(&self, symbol: &str, order_id: &str,
                          account_type: AccountType)
        -> ExchangeResult<Order>;

    async fn get_order(&self, symbol: &str, order_id: &str,
                       account_type: AccountType)
        -> ExchangeResult<Order>;

    async fn get_open_orders(&self, symbol: Option<&str>,
                             account_type: AccountType)
        -> ExchangeResult<Vec<Order>>;

    async fn get_order_history(&self, filter: OrderHistoryFilter,
                               account_type: AccountType)
        -> ExchangeResult<Vec<Order>>;
}

/// Core account — 24/24 exchanges (23/24 strict + GMX partial balance).
pub trait Account: ExchangeIdentity {
    async fn get_balance(&self, asset: Option<&str>,
                         account_type: AccountType)
        -> ExchangeResult<Vec<Balance>>;

    async fn get_account_info(&self, account_type: AccountType)
        -> ExchangeResult<AccountInfo>;

    async fn get_fees(&self, symbol: Option<&str>)
        -> ExchangeResult<FeeInfo>;
}
```

### Tier 2: High-Coverage Extension Traits (15–22 / 24)


These are not required but nearly universal. Well-behaved connectors should
implement them. Consumers can safely assume most connectors will have them.

```rust
/// Cancel all open orders — 22/24 support.
/// Missing: GMX (on-chain no cancel-all), dYdX V4 (requires explicit IDs).
pub trait CancelAll: Trading {
    async fn cancel_all_orders(&self, symbol: Option<&str>,
                               account_type: AccountType)
        -> ExchangeResult<CancelAllResponse>;
}

/// Amend existing order — 18/24 support (13Y + 5P).
/// Not supported: Upbit, MEXC (spot), HTX (spot), Bitfinex (cancel+replace),
/// KuCoin (HF alter is cancel+replace), some DEXes.
pub trait AmendOrder: Trading {
    async fn amend_order(&self, req: AmendRequest,
                         account_type: AccountType)
        -> ExchangeResult<Order>;
}

/// Native batch order placement — 17/24 support (14Y + 3P).
/// No default implementation. If exchange has no batch endpoint: don't implement.
pub trait BatchOrders: Trading {
    async fn place_orders_batch(&self, orders: Vec<OrderRequest>)
        -> ExchangeResult<Vec<OrderResult>>;

    async fn cancel_orders_batch(&self, cancels: Vec<CancelRequest>)
        -> ExchangeResult<Vec<CancelResult>>;
}

/// Futures/perpetuals positions — 22/24 applicable (spot-only excluded).
pub trait Positions: ExchangeIdentity {
    async fn get_positions(&self, symbol: Option<&str>,
                           account_type: AccountType)
        -> ExchangeResult<Vec<Position>>;

    async fn get_funding_rate(&self, symbol: &str,
                              account_type: AccountType)
        -> ExchangeResult<FundingRate>;
}

/// Account transfers (spot/futures/margin) — 17/20 applicable exchanges.
/// Not applicable to non-custodial DEXes (Jupiter, GMX, dYdX bridges are not
/// internal transfers).
pub trait AccountTransfers: Account {
    async fn transfer(&self, req: TransferRequest)
        -> ExchangeResult<TransferResponse>;

    async fn get_transfer_history(&self, filter: TransferHistoryFilter)
        -> ExchangeResult<Vec<Transfer>>;
}
```

### Tier 3: Specialized Extension Traits (8–15 / 22 futures, or 7–14 / 24 total)


```rust
/// Leverage and margin mode control — 15Y+2P / 22 futures exchanges.
/// NOT in base Positions because Deribit (rank 1 exchange) and dYdX lack it.
pub trait LeverageControl: Positions {
    async fn set_leverage(&self, symbol: &str, leverage: u32,
                          account_type: AccountType)
        -> ExchangeResult<()>;

    async fn set_margin_mode(&self, symbol: &str, mode: MarginMode,
                             account_type: AccountType)
        -> ExchangeResult<()>;
}

/// Add or remove collateral from isolated position — 11Y+1P / 22.
pub trait MarginAdjustment: Positions {
    async fn add_margin(&self, symbol: &str, amount: Decimal,
                        account_type: AccountType)
        -> ExchangeResult<()>;

    async fn remove_margin(&self, symbol: &str, amount: Decimal,
                           account_type: AccountType)
        -> ExchangeResult<()>;
}

/// Advanced order types — selectively supported.
/// A connector implementing this trait returns UnsupportedOperation
/// for methods its exchange lacks.
pub trait AdvancedOrders: Trading {
    /// TrailingStop — 10/24
    async fn place_trailing_stop(&self, req: TrailingStopRequest)
        -> ExchangeResult<Order>;

    /// OCO (One-Cancels-Other) — 7/24
    async fn place_oco(&self, req: OcoRequest)
        -> ExchangeResult<OcoResponse>;

    /// Bracket (Entry + TP + SL in one native call) — 9/24
    async fn place_bracket(&self, req: BracketRequest)
        -> ExchangeResult<BracketResponse>;

    /// Iceberg — 8/24
    async fn place_iceberg(&self, req: IcebergRequest)
        -> ExchangeResult<Order>;

    /// TWAP algo — 7/24
    async fn place_twap(&self, req: TwapRequest)
        -> ExchangeResult<AlgoOrderResponse>;
}

/// Custodial deposits and withdrawals — ~18/24 custodial exchanges.
/// DEXes are structurally non-custodial; return UnsupportedOperation is wrong,
/// they simply do not implement this trait.
pub trait CustodialFunds: Account {
    async fn get_deposit_address(&self, asset: &str, network: Option<&str>)
        -> ExchangeResult<DepositAddress>;

    async fn withdraw(&self, req: WithdrawRequest)
        -> ExchangeResult<WithdrawResponse>;

    async fn get_deposit_withdraw_history(&self, filter: FundsHistoryFilter)
        -> ExchangeResult<Vec<FundsRecord>>;
}

/// Sub-account management — ~12/22 non-DEX exchanges.
pub trait SubAccounts: Account {
    async fn create_subaccount(&self, params: SubAccountParams)
        -> ExchangeResult<SubAccount>;

    async fn list_subaccounts(&self)
        -> ExchangeResult<Vec<SubAccount>>;

    async fn transfer_to_subaccount(&self, req: SubAccountTransfer)
        -> ExchangeResult<TransferResponse>;
}
```

### Tier 4: Rare Extension Traits (2–8 / 24)


These are valid traits but should NOT be in the core library. They belong in
an `extensions` module and are considered unstable API surface.

```rust
/// Spot margin lending — ~5 exchanges (Bitfinex, Kraken, Binance Margin,
/// Gate.io margin, HTX margin account).
pub mod extensions {
    pub trait SpotMarginLending: Account {
        async fn borrow_margin(&self, asset: &str, amount: Decimal)
            -> ExchangeResult<BorrowResponse>;
        async fn repay_margin(&self, asset: &str, amount: Decimal)
            -> ExchangeResult<()>;
        async fn get_margin_loan_info(&self, asset: Option<&str>)
            -> ExchangeResult<MarginInfo>;
    }

    /// On-chain DEX specific — not part of HTTP connector abstraction.
    /// GMX, HyperLiquid liquidity provision.
    pub trait LiquidityPool {
        async fn deposit_to_pool(&self, req: PoolDepositRequest)
            -> ExchangeResult<PoolReceipt>;
        async fn withdraw_from_pool(&self, req: PoolWithdrawRequest)
            -> ExchangeResult<PoolReceipt>;
    }
}
```

### Minimum Exchange (Upbit)


Upbit implements ONLY:
- `ExchangeIdentity`
- `Trading` (place_order: Market+Limit only; cancel; get_order; get_open; get_history)
- `Account` (balance; account_info; fees — partial but present in order responses)
- `CancelAll` (cancel_all_orders supported; cancel_by_symbol supported)

Upbit does NOT implement: `Positions`, `AmendOrder`, `BatchOrders`,
`AccountTransfers` (no transfer between account types),
`LeverageControl`, `AdvancedOrders`, `CustodialFunds` (yes it does! Upbit
supports deposit address, withdraw, deposit/withdraw history), `SubAccounts`.

Correction: Upbit DOES implement `CustodialFunds` despite its minimal trading
profile. The matrix shows DepositAddr: Y, Withdraw: Y, Dep/Wdw History: Y.

### Maximum Exchange (Deribit)


Deribit implements ALL of:
- `ExchangeIdentity`
- `Trading` (all order types via OrderType enum)
- `Account`
- `CancelAll`
- `AmendOrder`
- `BatchOrders` — actually NO for Deribit. Table C shows Deribit Batch: N.
  Deribit has `mass_quote` for market makers but no general batch order
  placement. So Deribit does NOT implement `BatchOrders`.
- `Positions`
- `AccountTransfers`
- `LeverageControl` — actually NO for Deribit. SetLev: N in Table D.
  Deribit uses implicit leverage via margin allocation. So Deribit does NOT
  implement `LeverageControl`.
- `MarginAdjustment` — NO for Deribit. AddRemMargin: N in Table D.
- `AdvancedOrders` (trailing, OCO, bracket, iceberg; NOT TWAP)
- `CustodialFunds`
- `SubAccounts`

The fact that the "maximum exchange" (Deribit) does not implement `BatchOrders`
or `LeverageControl` confirms these traits are correctly placed as optional
extensions rather than universal requirements.

---

## Summary: Changes Required to Current Implementation


| Area | Issue | Action |
|------|-------|--------|
| `Trading` | Missing `get_order_history` (24/24) | ADD to base trait |
| `Trading` | Split `market_order`/`limit_order` → unified `place_order(OrderRequest)` | REFACTOR |
| `Account` | Missing `get_fees` (23/24) | ADD to base trait |
| `Positions` | `set_leverage` not universal (15/22) | MOVE to `LeverageControl` extension |
| `BatchOperations` | Default impl loops sequentially (violates strict rule) | REMOVE all default impls |
| `AdvancedOrders` | Contains `create_stop_limit_order` which is not advanced | REMOVE; StopLimit → `OrderType` enum |
| `ExchangeAuth` | Models HMAC only; 10 auth mechanisms unsupported | REDESIGN with `HttpRequestSigner` + `ActionSigner` |
| `Transfers` | "Required" method in optional extension is contradictory | Rename to `AccountTransfers`, all methods required, no defaults |
| Missing | No `CancelAll` trait (22/24) | CREATE |
| Missing | No `AmendOrder` trait (18/24) | CREATE |
| Missing | No `MarginAdjustment` trait (11/22) | CREATE |
| Missing | No `CustodialFunds` trait | CREATE |
| Missing | `AdvancedOrders` missing Iceberg (8/24) and TWAP (7/24) | ADD methods |

---

## Appendix: Per-Exchange Trait Implementation Table


| Exchange | Trading | Account | CancelAll | AmendOrder | BatchOrders | Positions | LeverageControl | AdvancedOrders | AccountTransfers | CustodialFunds | SubAccounts |
|----------|---------|---------|-----------|------------|-------------|-----------|-----------------|----------------|------------------|----------------|-------------|
| Binance | Y | Y | Y | Y | Y(F) | Y | Y | Trailing,Iceberg | Y | Y | Y(broker) |
| Bybit | Y | Y | Y | Y | Y | Y | Y | Trailing,Iceberg | Y | Y | N(no create/list) |
| OKX | Y | Y | Y | Y | Y | Y | Y | All | Y | Y | Y(partial) |
| KuCoin | Y | Y | Y | Y(HF) | Y | Y | P(risk-level) | Iceberg | Y | Y | N |
| Kraken | Y | Y | Y | P | Y(S:15) | Y | Y | Trailing,Iceberg | Y | Y | N |
| Coinbase | Y | Y | P(batch) | P | N | Y(INTX) | P | Bracket,TWAP | Y | N | N |
| Gate.io | Y | Y | Y | Y | Y | Y | Y | Iceberg | Y | Y | N(partial) |
| Bitfinex | Y | Y | Y | Y | Y | Y | P | Trailing,Iceberg,OCO | Y | N | N(partial) |
| Bitstamp | Y | Y | Y | P | N | N | N | N | P | Y | N(institutional) |
| MEXC | Y | Y | Y | N | Y | Y | Y | N | Y | N | N(partial) |
| HTX | Y | Y | Y | N | Y | Y | Y | Trailing | Y | N | N |
| Bitget | Y | Y | Y | Y(F) | Y | Y | Y | Trailing,Bracket | Y | N | N |
| Gemini | Y | Y | Y | N | N | P | N | N | Y | Y | Y |
| BingX | Y | Y | Y | Y(F) | Y | Y | Y | Trailing,TWAP | Y | Y | Y |
| Phemex | Y | Y | Y | Y | N | Y | Y | Trailing,Bracket,Iceberg | Y | Y | N(partial) |
| Crypto.com | Y | Y | Y | Y | Y | Y | Y | OCO,Bracket | Y | Y | N(UI only) |
| Upbit | Y | Y | Y | P | N | N | N | N | N | Y | N |
| Deribit | Y | Y | Y | Y | N | Y | N | Trailing,OCO,Bracket,Iceberg | Y | Y | Y |
| HyperLiquid | Y | Y | P | Y | Y | Y | Y | TWAP | Y | N | P |
| Lighter | Y | Y | Y | Y | Y | Y | Y | TWAP,OCO(P) | Y | Y | Y |
| Jupiter | Y | Y | Y | N | N | P | N/A | N | N/A | N/A | N/A |
| GMX | Y | P | N | Y | Y(multicall) | Y | N | N | N/A | N/A | N/A |
| Paradex | Y | Y | Y | Y | Y | P | P | TWAP | N | N/A | N(partial) |
| dYdX V4 | Y | Y | N | N | P | Y | N | TWAP | Y | N/A | Y |