polynode 0.5.5__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.
@@ -0,0 +1,506 @@
1
+ """PolyNodeTrader — place orders on Polymarket with local credential custody."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from typing import Any
8
+
9
+ from .clob_api import (
10
+ cancel_all_orders,
11
+ cancel_order as clob_cancel_order,
12
+ fetch_neg_risk,
13
+ fetch_tick_size,
14
+ get_open_orders as clob_get_open_orders,
15
+ post_order,
16
+ )
17
+ from .constants import DEFAULT_COSIGNER
18
+ from .cosigner import build_l2_headers, send_via_cosigner
19
+ from .eip712 import create_signed_order
20
+ from .onboarding import (
21
+ check_approvals as check_approvals_onchain,
22
+ check_balance as check_balance_onchain,
23
+ create_clob_credentials,
24
+ derive_funder_address,
25
+ detect_wallet_type,
26
+ is_safe_deployed,
27
+ )
28
+ from .signer import NormalizedSigner, normalize_signer
29
+ from .sqlite_backend import TradingSqliteBackend
30
+ from .types import (
31
+ ApprovalStatus,
32
+ BalanceInfo,
33
+ CancelResult,
34
+ GeneratedWallet,
35
+ HistoryParams,
36
+ LinkResult,
37
+ MarketMeta,
38
+ OpenOrder,
39
+ OrderHistoryRow,
40
+ OrderParams,
41
+ OrderResult,
42
+ ReadyStatus,
43
+ RouterSigner,
44
+ SignatureType,
45
+ StoredCredentials,
46
+ TraderConfig,
47
+ WalletExport,
48
+ WalletInfo,
49
+ )
50
+
51
+
52
+ class PolyNodeTrader:
53
+ """Place orders on Polymarket with local credential custody and builder attribution."""
54
+
55
+ def __init__(self, config: TraderConfig | None = None) -> None:
56
+ c = config or TraderConfig()
57
+ self._polynode_key = c.polynode_key
58
+ self._db_path = c.db_path
59
+ self._cosigner_url = c.cosigner_url
60
+ self._fallback_direct = c.fallback_direct
61
+ self._default_sig_type = c.default_signature_type
62
+ self._rpc_url = c.rpc_url
63
+
64
+ self._db: TradingSqliteBackend | None = None
65
+ self._active_signer: NormalizedSigner | None = None
66
+ self._active_wallet: str | None = None
67
+
68
+ @staticmethod
69
+ async def generate_wallet() -> GeneratedWallet:
70
+ """Generate a fresh EOA wallet. User MUST back up the private key."""
71
+ try:
72
+ from eth_account import Account
73
+ except ImportError:
74
+ raise ImportError("eth-account required. Install with: pip install polynode[trading]")
75
+ account = Account.create()
76
+ return GeneratedWallet(private_key=account.key.hex(), address=account.address)
77
+
78
+ def _get_db(self) -> TradingSqliteBackend:
79
+ if not self._db:
80
+ self._db = TradingSqliteBackend(self._db_path)
81
+ return self._db
82
+
83
+ # ── Onboarding ──
84
+
85
+ async def ensure_ready(
86
+ self,
87
+ signer: str | RouterSigner,
88
+ *,
89
+ type: SignatureType | None = None,
90
+ ) -> ReadyStatus:
91
+ """One-call onboarding: detect wallet type, deploy Safe, set approvals, create CLOB creds."""
92
+ temp = await normalize_signer(signer, type or SignatureType.POLY_GNOSIS_SAFE)
93
+ actions: list[str] = []
94
+
95
+ if type is not None:
96
+ sig_type = type
97
+ funder = temp.funder_address or await derive_funder_address(temp.address, sig_type)
98
+ else:
99
+ detected = await detect_wallet_type(temp.address)
100
+ sig_type = detected["signature_type"]
101
+ funder = detected["funder_address"]
102
+ actions.append(f"auto_detected_type_{int(sig_type)}")
103
+
104
+ normalized = await normalize_signer(signer, sig_type)
105
+ normalized.funder_address = funder
106
+ normalized.signature_type = sig_type
107
+
108
+ db = self._get_db()
109
+ stored = db.get_credentials(normalized.address)
110
+ safe_deployed = stored.safe_deployed if stored else False
111
+ approvals_set = stored.approvals_set if stored else False
112
+
113
+ # Deploy Safe if needed
114
+ if sig_type == SignatureType.POLY_GNOSIS_SAFE and not safe_deployed:
115
+ deployed = await is_safe_deployed(funder)
116
+ if deployed:
117
+ safe_deployed = True
118
+ actions.append("safe_already_deployed")
119
+ else:
120
+ actions.append("safe_deploy_needed")
121
+
122
+ # Check approvals
123
+ if sig_type in (SignatureType.POLY_GNOSIS_SAFE, SignatureType.EOA) and not approvals_set:
124
+ try:
125
+ approval_status = await check_approvals_onchain(funder, self._rpc_url)
126
+ if approval_status.all_approved:
127
+ approvals_set = True
128
+ actions.append("approvals_already_set")
129
+ else:
130
+ actions.append("approvals_needed")
131
+ except Exception:
132
+ actions.append("approval_check_failed")
133
+
134
+ # Create CLOB credentials
135
+ if not stored:
136
+ creds = await create_clob_credentials(normalized, funder)
137
+ stored = StoredCredentials(
138
+ wallet_address=normalized.address,
139
+ funder_address=funder,
140
+ api_key=creds["apiKey"],
141
+ api_secret=creds["apiSecret"],
142
+ api_passphrase=creds["apiPassphrase"],
143
+ signature_type=sig_type,
144
+ safe_deployed=safe_deployed,
145
+ approvals_set=approvals_set,
146
+ created_at=time.time(),
147
+ updated_at=time.time(),
148
+ )
149
+ db.upsert_credentials(stored)
150
+ actions.append("credentials_created")
151
+ else:
152
+ stored.safe_deployed = safe_deployed
153
+ stored.approvals_set = approvals_set
154
+ stored.updated_at = time.time()
155
+ db.upsert_credentials(stored)
156
+ actions.append("credentials_loaded")
157
+
158
+ self._active_signer = normalized
159
+ self._active_wallet = normalized.address
160
+
161
+ return ReadyStatus(
162
+ wallet=normalized.address,
163
+ funder_address=funder,
164
+ signature_type=sig_type,
165
+ safe_deployed=safe_deployed,
166
+ approvals_set=approvals_set,
167
+ credentials_stored=True,
168
+ credentials={
169
+ "apiKey": stored.api_key,
170
+ "apiSecret": stored.api_secret,
171
+ "apiPassphrase": stored.api_passphrase,
172
+ },
173
+ actions=actions,
174
+ )
175
+
176
+ async def link_wallet(
177
+ self,
178
+ signer: str | RouterSigner,
179
+ *,
180
+ type: SignatureType | None = None,
181
+ ) -> LinkResult:
182
+ """Link a wallet manually (derive credentials, store locally)."""
183
+ sig_type = type or self._default_sig_type
184
+ normalized = await normalize_signer(signer, sig_type)
185
+ funder = normalized.funder_address or await derive_funder_address(normalized.address, sig_type)
186
+
187
+ db = self._get_db()
188
+ existing = db.get_credentials(normalized.address)
189
+
190
+ if existing:
191
+ creds_dict = {
192
+ "apiKey": existing.api_key,
193
+ "apiSecret": existing.api_secret,
194
+ "apiPassphrase": existing.api_passphrase,
195
+ }
196
+ else:
197
+ creds_dict = await create_clob_credentials(normalized, funder)
198
+
199
+ db.upsert_credentials(StoredCredentials(
200
+ wallet_address=normalized.address,
201
+ funder_address=funder,
202
+ api_key=creds_dict["apiKey"],
203
+ api_secret=creds_dict["apiSecret"],
204
+ api_passphrase=creds_dict["apiPassphrase"],
205
+ signature_type=sig_type,
206
+ safe_deployed=existing.safe_deployed if existing else False,
207
+ approvals_set=existing.approvals_set if existing else False,
208
+ created_at=existing.created_at if existing else time.time(),
209
+ updated_at=time.time(),
210
+ ))
211
+
212
+ self._active_signer = normalized
213
+ self._active_wallet = normalized.address
214
+
215
+ return LinkResult(
216
+ wallet=normalized.address,
217
+ funder_address=funder,
218
+ signature_type=sig_type,
219
+ credentials=creds_dict,
220
+ )
221
+
222
+ def link_credentials(
223
+ self,
224
+ *,
225
+ wallet: str,
226
+ api_key: str,
227
+ api_secret: str,
228
+ api_passphrase: str,
229
+ signature_type: SignatureType = SignatureType.EOA,
230
+ funder_address: str | None = None,
231
+ ) -> None:
232
+ """Import existing CLOB credentials directly."""
233
+ db = self._get_db()
234
+ db.upsert_credentials(StoredCredentials(
235
+ wallet_address=wallet,
236
+ funder_address=funder_address,
237
+ api_key=api_key,
238
+ api_secret=api_secret,
239
+ api_passphrase=api_passphrase,
240
+ signature_type=signature_type,
241
+ safe_deployed=True,
242
+ approvals_set=True,
243
+ created_at=time.time(),
244
+ updated_at=time.time(),
245
+ ))
246
+ self._active_wallet = wallet
247
+
248
+ def unlink_wallet(self, address: str | None = None) -> None:
249
+ wallet = address or self._active_wallet
250
+ if not wallet:
251
+ return
252
+ self._get_db().delete_credentials(wallet)
253
+ if self._active_wallet == wallet:
254
+ self._active_wallet = None
255
+ self._active_signer = None
256
+
257
+ def get_linked_wallets(self) -> list[WalletInfo]:
258
+ return [
259
+ WalletInfo(
260
+ wallet=c.wallet_address,
261
+ funder_address=c.funder_address or c.wallet_address,
262
+ signature_type=c.signature_type,
263
+ credentials={
264
+ "apiKey": c.api_key,
265
+ "apiSecret": c.api_secret,
266
+ "apiPassphrase": c.api_passphrase,
267
+ },
268
+ created_at=c.created_at,
269
+ )
270
+ for c in self._get_db().get_all_credentials()
271
+ ]
272
+
273
+ # ── Export / Backup ──
274
+
275
+ def export_wallet(self, wallet: str | None = None) -> WalletExport | None:
276
+ addr = wallet or self._active_wallet
277
+ if not addr:
278
+ return None
279
+ creds = self._get_db().get_credentials(addr)
280
+ if not creds:
281
+ return None
282
+ return WalletExport(
283
+ wallet=creds.wallet_address,
284
+ funder_address=creds.funder_address or creds.wallet_address,
285
+ signature_type=creds.signature_type,
286
+ credentials={
287
+ "apiKey": creds.api_key,
288
+ "apiSecret": creds.api_secret,
289
+ "apiPassphrase": creds.api_passphrase,
290
+ },
291
+ safe_deployed=creds.safe_deployed,
292
+ approvals_set=creds.approvals_set,
293
+ created_at=creds.created_at,
294
+ )
295
+
296
+ def export_all(self) -> list[WalletExport]:
297
+ return [
298
+ WalletExport(
299
+ wallet=c.wallet_address,
300
+ funder_address=c.funder_address or c.wallet_address,
301
+ signature_type=c.signature_type,
302
+ credentials={"apiKey": c.api_key, "apiSecret": c.api_secret, "apiPassphrase": c.api_passphrase},
303
+ safe_deployed=c.safe_deployed,
304
+ approvals_set=c.approvals_set,
305
+ created_at=c.created_at,
306
+ )
307
+ for c in self._get_db().get_all_credentials()
308
+ ]
309
+
310
+ def import_wallet(self, exported: WalletExport) -> None:
311
+ self._get_db().upsert_credentials(StoredCredentials(
312
+ wallet_address=exported.wallet,
313
+ funder_address=exported.funder_address,
314
+ api_key=exported.credentials["apiKey"],
315
+ api_secret=exported.credentials["apiSecret"],
316
+ api_passphrase=exported.credentials["apiPassphrase"],
317
+ signature_type=exported.signature_type,
318
+ safe_deployed=exported.safe_deployed,
319
+ approvals_set=exported.approvals_set,
320
+ created_at=exported.created_at,
321
+ updated_at=time.time(),
322
+ ))
323
+
324
+ # ── Pre-Trade Checks ──
325
+
326
+ async def check_approvals(self, wallet: str | None = None) -> ApprovalStatus:
327
+ creds = self._get_stored_creds(wallet)
328
+ return await check_approvals_onchain(creds.funder_address or creds.wallet_address, self._rpc_url)
329
+
330
+ async def check_balance(self, wallet: str | None = None) -> BalanceInfo:
331
+ creds = self._get_stored_creds(wallet)
332
+ return await check_balance_onchain(creds.funder_address or creds.wallet_address, self._rpc_url)
333
+
334
+ # ── Trading ──
335
+
336
+ async def order(self, params: OrderParams) -> OrderResult:
337
+ """Place an order on Polymarket."""
338
+ creds = self._get_stored_creds()
339
+ signer = self._require_signer()
340
+ meta = await self._fetch_meta(params.token_id)
341
+
342
+ signed_order = await create_signed_order(
343
+ signer.sign_typed_data,
344
+ signer_address=signer.address,
345
+ funder_address=creds.funder_address or creds.wallet_address,
346
+ token_id=params.token_id,
347
+ price=params.price,
348
+ size=params.size,
349
+ side=params.side,
350
+ signature_type=creds.signature_type,
351
+ tick_size=meta.tick_size,
352
+ neg_risk=meta.neg_risk,
353
+ fee_rate_bps=meta.fee_rate_bps,
354
+ expiration=params.expiration or 0,
355
+ )
356
+
357
+ payload = {
358
+ "order": signed_order,
359
+ "owner": creds.funder_address or creds.wallet_address,
360
+ "orderType": params.type,
361
+ }
362
+ if params.post_only:
363
+ payload["postOnly"] = True
364
+
365
+ body_str = json.dumps(payload)
366
+ headers = build_l2_headers(
367
+ creds.api_key, creds.api_secret, creds.api_passphrase,
368
+ creds.wallet_address, "POST", "/order", body_str,
369
+ )
370
+
371
+ db = self._get_db()
372
+ local_id = db.insert_order({
373
+ "wallet_address": creds.wallet_address,
374
+ "token_id": params.token_id,
375
+ "side": params.side,
376
+ "price": params.price,
377
+ "size": params.size,
378
+ "order_type": params.type,
379
+ "status": "submitting",
380
+ })
381
+
382
+ result = await send_via_cosigner(
383
+ self._cosigner_url, self._polynode_key, self._fallback_direct,
384
+ {"method": "POST", "path": "/order", "body": body_str, "headers": headers},
385
+ )
386
+
387
+ order_id = result.get("orderID") or result.get("orderId")
388
+ success = bool(result.get("success") or order_id)
389
+ error = result.get("error") or result.get("errorMsg")
390
+
391
+ db.update_order_status(
392
+ local_id,
393
+ "submitted" if success else "failed",
394
+ order_id, error, json.dumps(result),
395
+ )
396
+
397
+ return OrderResult(
398
+ success=success,
399
+ order_id=order_id,
400
+ status=result.get("status"),
401
+ error=error,
402
+ making_amount=result.get("makingAmount"),
403
+ taking_amount=result.get("takingAmount"),
404
+ )
405
+
406
+ async def cancel_order(self, order_id: str) -> CancelResult:
407
+ creds = self._get_stored_creds()
408
+ result = await clob_cancel_order(
409
+ self._cosigner_url, self._polynode_key, self._fallback_direct,
410
+ {"apiKey": creds.api_key, "apiSecret": creds.api_secret, "apiPassphrase": creds.api_passphrase},
411
+ creds.wallet_address, order_id,
412
+ )
413
+ return CancelResult(
414
+ canceled=result.get("canceled", []),
415
+ not_canceled=result.get("not_canceled", {}),
416
+ )
417
+
418
+ async def cancel_all(self, market: str | None = None) -> CancelResult:
419
+ creds = self._get_stored_creds()
420
+ result = await cancel_all_orders(
421
+ self._cosigner_url, self._polynode_key, self._fallback_direct,
422
+ {"apiKey": creds.api_key, "apiSecret": creds.api_secret, "apiPassphrase": creds.api_passphrase},
423
+ creds.wallet_address, market,
424
+ )
425
+ return CancelResult(
426
+ canceled=result.get("canceled", []),
427
+ not_canceled=result.get("not_canceled", {}),
428
+ )
429
+
430
+ async def get_open_orders(
431
+ self, *, market: str | None = None, asset_id: str | None = None
432
+ ) -> list[OpenOrder]:
433
+ creds = self._get_stored_creds()
434
+ result = await clob_get_open_orders(
435
+ self._cosigner_url, self._polynode_key, self._fallback_direct,
436
+ {"apiKey": creds.api_key, "apiSecret": creds.api_secret, "apiPassphrase": creds.api_passphrase},
437
+ creds.wallet_address, market, asset_id,
438
+ )
439
+ return [
440
+ OpenOrder(
441
+ id=o.get("id", ""), market=o.get("market", ""),
442
+ asset_id=o.get("assetId") or o.get("asset_id", ""),
443
+ side=o.get("side", ""), price=o.get("price", ""),
444
+ original_size=o.get("originalSize") or o.get("original_size", ""),
445
+ size_matched=o.get("sizeMatched") or o.get("size_matched", ""),
446
+ status=o.get("status", ""), created_at=o.get("createdAt") or o.get("created_at", ""),
447
+ order_type=o.get("orderType") or o.get("order_type", ""),
448
+ )
449
+ for o in result
450
+ ]
451
+
452
+ # ── Metadata ──
453
+
454
+ async def prefetch_meta(self, token_ids: list[str]) -> None:
455
+ import asyncio
456
+ await asyncio.gather(*(self._fetch_meta(tid) for tid in token_ids))
457
+
458
+ async def _fetch_meta(self, token_id: str) -> MarketMeta:
459
+ db = self._get_db()
460
+ cached = db.get_market_meta(token_id)
461
+ if cached:
462
+ return cached
463
+
464
+ import asyncio
465
+ tick, neg = await asyncio.gather(fetch_tick_size(token_id), fetch_neg_risk(token_id))
466
+ meta = MarketMeta(
467
+ token_id=token_id,
468
+ tick_size=tick,
469
+ fee_rate_bps=0,
470
+ neg_risk=neg,
471
+ fetched_at=time.time(),
472
+ )
473
+ db.upsert_market_meta(meta)
474
+ return meta
475
+
476
+ # ── History ──
477
+
478
+ def get_order_history(self, params: HistoryParams | None = None) -> list[OrderHistoryRow]:
479
+ if not self._active_wallet:
480
+ return []
481
+ return self._get_db().get_order_history(self._active_wallet, params)
482
+
483
+ # ── Lifecycle ──
484
+
485
+ def close(self) -> None:
486
+ if self._db:
487
+ self._db.close()
488
+ self._db = None
489
+ self._active_signer = None
490
+ self._active_wallet = None
491
+
492
+ # ── Internal ──
493
+
494
+ def _get_stored_creds(self, wallet: str | None = None) -> StoredCredentials:
495
+ addr = wallet or self._active_wallet
496
+ if not addr:
497
+ raise RuntimeError("No active wallet. Call ensure_ready() or link_wallet() first.")
498
+ creds = self._get_db().get_credentials(addr)
499
+ if not creds:
500
+ raise RuntimeError(f"No credentials found for {addr}. Call ensure_ready() first.")
501
+ return creds
502
+
503
+ def _require_signer(self) -> NormalizedSigner:
504
+ if not self._active_signer:
505
+ raise RuntimeError("No active signer. Call ensure_ready() or link_wallet() first.")
506
+ return self._active_signer
@@ -0,0 +1,191 @@
1
+ """Trading module type definitions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import IntEnum
7
+ from typing import Any, Literal, Protocol, runtime_checkable
8
+
9
+
10
+ class SignatureType(IntEnum):
11
+ EOA = 0
12
+ POLY_PROXY = 1
13
+ POLY_GNOSIS_SAFE = 2
14
+
15
+
16
+ @runtime_checkable
17
+ class RouterSigner(Protocol):
18
+ async def get_address(self) -> str: ...
19
+ async def sign_typed_data(self, payload: Eip712Payload) -> str: ...
20
+
21
+
22
+ @dataclass
23
+ class Eip712Payload:
24
+ domain: dict[str, Any]
25
+ types: dict[str, Any]
26
+ primary_type: str
27
+ message: dict[str, Any]
28
+
29
+
30
+ @dataclass
31
+ class GeneratedWallet:
32
+ private_key: str
33
+ address: str
34
+
35
+
36
+ @dataclass
37
+ class TraderConfig:
38
+ polynode_key: str = ""
39
+ db_path: str = "./polynode-trading.db"
40
+ cosigner_url: str = "https://trade.polynode.dev"
41
+ fallback_direct: bool = True
42
+ default_signature_type: SignatureType = SignatureType.POLY_GNOSIS_SAFE
43
+ rpc_url: str = "https://polygon-bor-rpc.publicnode.com"
44
+
45
+
46
+ @dataclass
47
+ class ReadyStatus:
48
+ wallet: str
49
+ funder_address: str
50
+ signature_type: SignatureType
51
+ safe_deployed: bool
52
+ approvals_set: bool
53
+ credentials_stored: bool
54
+ credentials: dict[str, str]
55
+ actions: list[str]
56
+
57
+
58
+ @dataclass
59
+ class LinkResult:
60
+ wallet: str
61
+ funder_address: str
62
+ signature_type: SignatureType
63
+ credentials: dict[str, str]
64
+
65
+
66
+ @dataclass
67
+ class WalletInfo:
68
+ wallet: str
69
+ funder_address: str
70
+ signature_type: SignatureType
71
+ credentials: dict[str, str]
72
+ created_at: float
73
+
74
+
75
+ @dataclass
76
+ class WalletExport:
77
+ wallet: str
78
+ funder_address: str
79
+ signature_type: SignatureType
80
+ credentials: dict[str, str]
81
+ safe_deployed: bool
82
+ approvals_set: bool
83
+ created_at: float
84
+
85
+
86
+ @dataclass
87
+ class ApprovalStatus:
88
+ funder_address: str
89
+ usdc: dict[str, bool]
90
+ ctf: dict[str, bool]
91
+ all_approved: bool
92
+
93
+
94
+ @dataclass
95
+ class BalanceInfo:
96
+ funder_address: str
97
+ usdc: str
98
+ usdc_raw: str
99
+ matic: str
100
+
101
+
102
+ OrderSide = Literal["BUY", "SELL"]
103
+ OrderType = Literal["GTC", "GTD", "FOK", "FAK"]
104
+
105
+
106
+ @dataclass
107
+ class OrderParams:
108
+ token_id: str
109
+ side: OrderSide
110
+ price: float
111
+ size: float
112
+ type: OrderType = "GTC"
113
+ expiration: int | None = None
114
+ post_only: bool = False
115
+
116
+
117
+ @dataclass
118
+ class OrderResult:
119
+ success: bool
120
+ order_id: str | None = None
121
+ status: str | None = None
122
+ error: str | None = None
123
+ making_amount: str | None = None
124
+ taking_amount: str | None = None
125
+
126
+
127
+ @dataclass
128
+ class CancelResult:
129
+ canceled: list[str] = field(default_factory=list)
130
+ not_canceled: dict[str, str] = field(default_factory=dict)
131
+
132
+
133
+ @dataclass
134
+ class OpenOrder:
135
+ id: str
136
+ market: str
137
+ asset_id: str
138
+ side: str
139
+ price: str
140
+ original_size: str
141
+ size_matched: str
142
+ status: str
143
+ created_at: str
144
+ order_type: str
145
+
146
+
147
+ @dataclass
148
+ class MarketMeta:
149
+ token_id: str
150
+ tick_size: str
151
+ fee_rate_bps: int
152
+ neg_risk: bool
153
+ fetched_at: float
154
+
155
+
156
+ @dataclass
157
+ class OrderHistoryRow:
158
+ id: int
159
+ wallet_address: str
160
+ order_id: str | None
161
+ token_id: str
162
+ side: str
163
+ price: float
164
+ size: float
165
+ order_type: str
166
+ status: str
167
+ error_msg: str | None
168
+ response_json: str | None
169
+ created_at: float
170
+
171
+
172
+ @dataclass
173
+ class HistoryParams:
174
+ limit: int | None = None
175
+ offset: int | None = None
176
+ token_id: str | None = None
177
+ side: OrderSide | None = None
178
+
179
+
180
+ @dataclass
181
+ class StoredCredentials:
182
+ wallet_address: str
183
+ funder_address: str | None
184
+ api_key: str
185
+ api_secret: str
186
+ api_passphrase: str
187
+ signature_type: SignatureType
188
+ safe_deployed: bool
189
+ approvals_set: bool
190
+ created_at: float
191
+ updated_at: float
@@ -0,0 +1,8 @@
1
+ """Re-export all types."""
2
+
3
+ from .enums import * # noqa: F401, F403
4
+ from .events import * # noqa: F401, F403
5
+ from .orderbook import * # noqa: F401, F403
6
+ from .rest import * # noqa: F401, F403
7
+ from .short_form import * # noqa: F401, F403
8
+ from .ws import * # noqa: F401, F403