xync-client 0.0.25.dev11__tar.gz → 0.0.25.dev19__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 (75) hide show
  1. {xync_client-0.0.25.dev11/xync_client.egg-info → xync_client-0.0.25.dev19}/PKG-INFO +1 -1
  2. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/Agent.py +28 -11
  3. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/AuthTrait.py +1 -3
  4. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/Base.py +3 -0
  5. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/Ex.py +9 -9
  6. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/agent.py +167 -119
  7. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/auth.py +9 -3
  8. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/ex.py +60 -40
  9. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/pyd.py +66 -91
  10. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19/xync_client.egg-info}/PKG-INFO +1 -1
  11. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/.env.sample +0 -0
  12. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/.gitignore +0 -0
  13. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/.pre-commit-config.yaml +0 -0
  14. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/README.md +0 -0
  15. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/makefile +0 -0
  16. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/pyproject.toml +0 -0
  17. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/setup.cfg +0 -0
  18. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/TestAgent.py +0 -0
  19. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/TestAsset.py +0 -0
  20. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/TestEx.py +0 -0
  21. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/TestOrder.py +0 -0
  22. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/Binance/test_binance.py +0 -0
  23. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  24. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  25. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/Gate/test_gate.py +0 -0
  26. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
  27. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  28. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  29. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/__init__.py +0 -0
  30. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/tests/_todo_refact/_test_ex.py +0 -0
  31. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/Asset.py +0 -0
  32. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/BaseTest.py +0 -0
  33. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/InAgent.py +0 -0
  34. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Abc/Order.py +0 -0
  35. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Binance/__init__.py +0 -0
  36. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Binance/binance_async.py +0 -0
  37. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Binance/earn_api.py +0 -0
  38. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Binance/ex.py +0 -0
  39. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Binance/exceptions.py +0 -0
  40. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Binance/sapi.py +0 -0
  41. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Binance/web_c2c.py +0 -0
  42. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BingX/__init__.py +0 -0
  43. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BingX/agent.py +0 -0
  44. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BingX/base.py +0 -0
  45. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BingX/ex.py +0 -0
  46. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BingX/req.mjs +0 -0
  47. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BingX/sign.js +0 -0
  48. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BingX/test/main.py +0 -0
  49. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BitGet/__init__.py +0 -0
  50. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BitGet/agent.py +0 -0
  51. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BitGet/ex.py +0 -0
  52. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/BitGet/req.mjs +0 -0
  53. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Bybit/agent.py +0 -0
  54. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Bybit/ex.py +0 -0
  55. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Bybit/web_earn.py +0 -0
  56. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Bybit/web_p2p.py +0 -0
  57. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Gate/ex.py +0 -0
  58. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Gate/premarket.py +0 -0
  59. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Htx/agent.py +0 -0
  60. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Htx/earn.py +0 -0
  61. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Htx/ex.py +0 -0
  62. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/KuCoin/pub.py +0 -0
  63. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/KuCoin/web.py +0 -0
  64. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/Okx/ex.py +0 -0
  65. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/asset.py +0 -0
  66. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/inAgent.py +0 -0
  67. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/order.py +0 -0
  68. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/pyro.py +0 -0
  69. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/TgWallet/web.py +0 -0
  70. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/__init__.py +0 -0
  71. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client/loader.py +0 -0
  72. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client.egg-info/SOURCES.txt +0 -0
  73. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client.egg-info/dependency_links.txt +0 -0
  74. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client.egg-info/requires.txt +0 -0
  75. {xync_client-0.0.25.dev11 → xync_client-0.0.25.dev19}/xync_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: xync-client
3
- Version: 0.0.25.dev11
3
+ Version: 0.0.25.dev19
4
4
  Author-email: Mike Artemiev <mixartemev@gmail.com>
5
5
  Project-URL: Homepage, https://gitlab.com/XyncNet/client
6
6
  Project-URL: Repository, https://gitlab.com/XyncNet/client
@@ -3,20 +3,18 @@ from abc import abstractmethod
3
3
  from pydantic import BaseModel
4
4
  from tortoise.exceptions import IntegrityError
5
5
  from x_model import HTTPException, FailReason
6
- from xync_client.Abc.Ex import BaseExClient
6
+ from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Fiat, Agent, Pmex, Pmcur, Cred, PmexBank
7
+ from xync_schema.pydantic import FiatNew, FiatUpd, BaseAd, FiatPydIn, AdSalePydIn, AdBuyPydIn, CredPydIn, BaseOrder
7
8
 
9
+ from xync_client.Abc.Ex import BaseExClient
8
10
  from xync_client.Abc.Base import BaseClient
9
11
 
10
- from xync_client.Abc.AuthTrait import BaseAuthTrait
11
- from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Fiat, Agent, Pmex, Pmcur
12
- from xync_schema.pydantic import FiatNew, FiatUpd, BaseAd, FiatPydIn
13
12
 
14
-
15
- class BaseAgentClient(BaseClient, BaseAuthTrait):
13
+ class BaseAgentClient(BaseClient):
16
14
  def __init__(self, agent: Agent):
17
15
  self.agent: Agent = agent
18
- self.ex_client: BaseExClient = agent.contragent.ex.client(agent) # todo: really need?
19
- super().__init__(agent.contragent.ex) # , "host_p2p"
16
+ super().__init__(self.agent.contragent.ex) # , "host_p2p"
17
+ self.ex_client: BaseExClient = self.ex.client()
20
18
 
21
19
  @abstractmethod
22
20
  async def start_listen(self) -> bool: ...
@@ -33,7 +31,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait):
33
31
 
34
32
  # 1: [T] Запрос на старт сделки
35
33
  @abstractmethod
36
- async def order_request(self, ad_id: int, amount: float) -> dict: ...
34
+ async def order_request(self, order: BaseOrder) -> dict: ...
37
35
 
38
36
  # async def start_order(self, order: Order) -> OrderOutClient:
39
37
  # return OrderOutClient(self, order)
@@ -79,7 +77,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait):
79
77
 
80
78
  # 25: Список реквизитов моих платежных методов
81
79
  @abstractmethod
82
- async def fiats(self) -> list: ... # {fiat.exid: {fiat}}
80
+ async def creds(self) -> list: ... # {fiat.exid: {fiat}}
83
81
 
84
82
  @staticmethod
85
83
  async def fiat_pyd2db(fiat_pyd: FiatNew | FiatUpd, uid: int, fid: int = None) -> tuple[Fiat, bool]:
@@ -97,7 +95,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait):
97
95
  # 26: Создание реквизита моего платежного метода
98
96
  @abstractmethod
99
97
  async def fiat_f2in(self, fiat_new: FiatNew) -> FiatPydIn:
100
- if not (pmcur := await Pmcur.get_or_none(cur_id=fiat_new.cur_id, pm_id=fiat_new.pm_id)):
98
+ if not (_pmcur := await Pmcur.get_or_none(cur_id=fiat_new.cur_id, pm_id=fiat_new.pm_id)):
101
99
  raise HTTPException(FailReason.body, f"No Pmcur with cur#{fiat_new.cur_id} and pm#{fiat_new.pm_id}", 404)
102
100
  # cred = await Cred.create({"exid": }, pmcur=pmcur, contragent=self.agent.contragent)
103
101
  # df = {"detail": fiat_pyd.detail, "name": fiat_pyd.name, "amount": fiat_pyd.amount, "target": fiat_pyd.target}
@@ -182,3 +180,22 @@ class BaseAgentClient(BaseClient, BaseAuthTrait):
182
180
  # 39: Балансы моих монет
183
181
  @abstractmethod
184
182
  async def my_assets(self) -> dict: ...
183
+
184
+ # Сохранение объявления (с Pm/Cred-ами) в бд
185
+ async def ad_pydin2db(self, ad_pydin: AdSalePydIn | AdBuyPydIn) -> Ad:
186
+ ad_db = await self.ex_client.ad_pydin2db(ad_pydin)
187
+ await ad_db.creds.add(*getattr(ad_pydin, "creds_", []))
188
+ return ad_db
189
+
190
+ @staticmethod
191
+ async def cred_pydin2db(cred: CredPydIn) -> Cred:
192
+ df, unq = cred.args()
193
+ cred_db, _ = await Cred.update_or_create(df, **unq)
194
+ if cred.banks:
195
+ await cred_db.banks.add(*[await PmexBank.get(exid=b) for b in cred.banks])
196
+ return cred_db
197
+
198
+ @staticmethod
199
+ async def fiat_cred2db(cred: Cred) -> Fiat:
200
+ fiat, _ = await Fiat.get_or_create(cred=cred)
201
+ return fiat
@@ -6,11 +6,9 @@ from aiohttp.http_exceptions import HttpProcessingError
6
6
  from x_client.aiohttp import Client
7
7
  from xync_schema.models import Agent
8
8
 
9
- # from x_client.aiohttp import Client
10
-
11
9
 
12
10
  class BaseAuthTrait(Client):
13
- agent: Agent
11
+ agent: Agent = None
14
12
 
15
13
  @abstractmethod
16
14
  async def _get_auth_hdrs(self) -> dict[str, str]: ...
@@ -8,5 +8,8 @@ MapOfIdsList = dict[int | str, list[int | str]]
8
8
 
9
9
 
10
10
  class BaseClient(Client):
11
+ ex: Ex
12
+
11
13
  def __init__(self, ex: Ex, attr: str = "host_p2p"):
14
+ self.ex = ex
12
15
  super().__init__(getattr(ex, attr))
@@ -2,8 +2,8 @@ import logging
2
2
  import re
3
3
  from abc import abstractmethod
4
4
 
5
- from xync_schema.pydantic import AdPydIn, PmPyd, BaseAd, CurEpyd, CoinEpyd
6
- from xync_schema.models import Ex, Coin, Cur, Pm, Pmex, Curex, Pmcur, Coinex, PmexBank, Ad, Agent
5
+ from xync_schema.pydantic import BaseAdPydIn, PmPyd, BaseAd, CurEpyd, CoinEpyd
6
+ from xync_schema.models import Ex, Coin, Cur, Pm, Pmex, Curex, Pmcur, Coinex, PmexBank, Ad
7
7
 
8
8
  from xync_client.Abc.Base import BaseClient, DictOfDicts, MapOfIdsList
9
9
 
@@ -21,8 +21,7 @@ class BaseExClient(BaseClient):
21
21
  "GTB Bank (Guarantee Trust Bank)": "GTBank",
22
22
  }
23
23
 
24
- def __init__(self, ex: Ex, _: Agent = None):
25
- self.ex: Ex = ex
24
+ def __init__(self, ex: Ex):
26
25
  self.acronyms: dict[str, str] = {} # for pm norm
27
26
  super().__init__(ex) # , "host_p2p"
28
27
 
@@ -66,7 +65,7 @@ class BaseExClient(BaseClient):
66
65
 
67
66
  # Преобразрование объекта объявления из формата биржи в формат xync
68
67
  @abstractmethod
69
- async def ad_epyd2pydin(self, ad: BaseAd) -> AdPydIn: ... # my_uid: for MyAd
68
+ async def ad_epyd2pydin(self, ad: BaseAd) -> BaseAdPydIn: ... # my_uid: for MyAd
70
69
 
71
70
  def _pmnorm(self, s: str) -> str:
72
71
  def get_and_remove_acro(title: str) -> str:
@@ -196,8 +195,9 @@ class BaseExClient(BaseClient):
196
195
  await Coinex.bulk_create(coinexs, update_fields=["minimum"], on_conflict=["coin_id", "ex_id"])
197
196
 
198
197
  # Сохранение чужого объявления (с Pm-ами) в бд
199
- @staticmethod
200
- async def ad_pydin2db(ad_pydin: AdPydIn) -> Ad:
201
- ad_db, _ = await Ad.update_or_create(ad_pydin.model_dump(exclude_none=True), id=ad_pydin.id)
202
- await ad_db.pms.add(*getattr(ad_pydin, "pms_", ad_pydin.fiats_))
198
+ async def ad_pydin2db(self, ad_pydin: BaseAdPydIn) -> Ad:
199
+ df, unq = ad_pydin.args()
200
+ ad_db, _ = await Ad.update_or_create(df, **unq)
201
+ if getattr(ad_pydin, "pms_", None): # if it ListItem, not Full One # todo: remove?
202
+ await ad_db.pms.add(*ad_pydin.pms_)
203
203
  return ad_db
@@ -1,23 +1,28 @@
1
1
  import logging
2
2
  from asyncio import run
3
3
  from enum import StrEnum
4
-
4
+ from tg_auth import UserStatus
5
5
  from x_model import init_db, HTTPException, FailReason
6
+ from xync_client.TgWallet.ex import ExClient
6
7
  from xync_schema import models
7
8
 
8
9
  from xync_client.TgWallet.pyd import (
9
- Banks,
10
- FiatEpyd,
10
+ CredEpyd,
11
11
  Attrs,
12
12
  AttrsV2,
13
13
  OneAdTakerMakerSale,
14
- OneAdMakerPurchase,
15
- AdMakerPurchase,
14
+ OneAdMakerBuy,
15
+ AdMakerBuy,
16
16
  AdMakerSale,
17
17
  AdMakerNewSale,
18
- _TakerOne,
19
18
  _AdNew,
20
19
  FiatEpydIn,
20
+ _PmsTrait,
21
+ OneAdTakerBuy,
22
+ OrderEpyd,
23
+ AdMakerNewBuy,
24
+ OrderEpydIn,
25
+ AvailableVolume,
21
26
  )
22
27
  from xync_client.loader import PG_DSN
23
28
  from xync_schema.enums import AdStatus
@@ -33,20 +38,25 @@ from xync_schema.models import (
33
38
  Ad,
34
39
  Pmcur,
35
40
  Cred,
36
- PmexBank,
37
41
  Agent,
38
42
  Curex,
39
43
  Coinex,
44
+ Pm,
45
+ Order,
46
+ Contragent,
40
47
  )
41
- from xync_schema.pydantic import FiatNew, FiatPydIn
48
+ from xync_schema.pydantic import FiatNew, BaseAdPydIn, AdBuyPydIn, AdSalePydIn, CredPydIn, OrderPydIn
42
49
 
43
50
  from xync_client.Abc.Agent import BaseAgentClient
44
51
 
45
52
 
46
53
  class Exceptions(StrEnum):
54
+ ORDER_KYC = "FIAT_COUNTRY_NOT_SUPPORTED_BY_USER_KYC_COUNTRY"
47
55
  PM_KYC = "OFFER_FIAT_COUNTRY_NOT_SUPPORTED_BY_USER_KYC_COUNTRY"
48
56
  ROUND = "ROUNDING_IS_NOT_SUPPORTED"
49
57
  CUR = "CURRENCY_IS_NOT_SUPPORTED_BY_PAYMENT_METHOD"
58
+ MAX_ADS = "ACTIVE_OFFER_COUNT_LIMIT_REACHED"
59
+ INACTIVE_AD = "OFFER_ILLEGAL_STATE"
50
60
 
51
61
 
52
62
  # class Status(IntEnum):
@@ -54,6 +64,8 @@ class Exceptions(StrEnum):
54
64
 
55
65
 
56
66
  class AgentClient(BaseAgentClient, AuthClient):
67
+ ex_client: ExClient
68
+
57
69
  # 0: Получение ордеров в статусе status, по монете coin, в валюте coin, в направлении is_sell
58
70
  async def orders(
59
71
  self, status: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
@@ -69,48 +81,64 @@ class AgentClient(BaseAgentClient, AuthClient):
69
81
  orders = await self._post("/p2p/public-api/v2/offer/order/get", {"orderId": oid})
70
82
  return orders["data"]
71
83
 
84
+ @staticmethod
85
+ async def order_ad2epydin(ad: Ad, amount: float, cred_id: int) -> OrderEpydIn:
86
+ await ad.fetch_related("direction__pairex__pair__cur")
87
+ return OrderEpydIn(
88
+ offerId=ad.exid,
89
+ amount=AvailableVolume(currencyCode=ad.direction.pairex.pair.cur.ticker, amount=str(int(amount))),
90
+ type="SALE" if ad.direction.sell else "PURCHASE",
91
+ paymentDetailsId=cred_id,
92
+ )
93
+
72
94
  # 1: [T] Запрос на старт сделки
73
- async def order_request(self, ad_id: int, amount: float = None) -> dict | bool:
74
- await self.agent.fetch_related("ex", "ex__agents")
75
- ad: _TakerOne = await self.ex_client.ad(ad_id=ad_id)
76
- fiats: list[FiatEpyd] = await self.fiats()
77
- fiats_pms: dict[str, int] = {fiat.paymentMethod.code: fiat.id for fiat in fiats}
78
- pmfs: dict[str, int]
79
- if ad.is_sell:
80
- pmfs = {pd.paymentMethod.code: pd.id for pd in ad.paymentDetails if pd.paymentMethod.code in fiats_pms}
81
- fiatex = (
82
- await Cred.filter(
83
- fiat__user_id=self.agent.user_id,
84
- ex=self.ex_client.ex,
85
- fiat__pmcur__pm__pmexs__exid__in=pmfs.keys(),
86
- )
87
- .order_by("-fiat__amount")
88
- .first()
89
- .prefetch_related("fiat__pmcur")
90
- )
91
- pmex = await Pmex.get(ex=self.ex_client.ex, pm_id=fiatex.fiat.pmcur.pm_id)
92
- # else:
93
- # pmfs = {pm.code: fid for pm in ad.paymentMethods if (fid := fiats_pms.get(pm.code))}
94
- # f = await Cred.filter(
95
- # fiat__user_id=self.agent.user_id, ex_id=self.ex_client.ex_id, fiat__pmcur__pm__pmexs__exid__in=pmfs.keys()
96
- # ).order_by("-fiat__amount").first()
97
- # p = max(pmfs, key=lambda x: fiats_pms)
98
- req_body = {
99
- "offerId": ad_id,
100
- "paymentDetailsId": pmfs[pmex.exid], # fiats_pms[pid],
101
- "amount": {"currencyCode": ad.orderAmountLimits.currencyCode, "amount": amount},
102
- "type": ad.type,
103
- }
104
- request = await self._post("/p2p/public-api/v2/offer/order/create-by-amount", req_body)
95
+ async def order_request(self, req: OrderEpydIn) -> OrderEpyd | dict:
96
+ request = await self._post("/p2p/public-api/v2/offer/order/create-by-amount", req.model_dump(exclude_none=True))
105
97
  if r := request.get("data"):
106
- return await self._post("/p2p/public-api/v2/offer/order/confirm", {"orderId": r["id"], "type": ad.type})
98
+ res = await self._post("/p2p/public-api/v2/offer/order/confirm", {"orderId": r["id"], "type": req.type})
99
+ if res.get("status") == "SUCCESS":
100
+ return OrderEpyd(**r)
107
101
  logging.error(request)
108
102
  return request
109
103
 
104
+ async def order_epyd2pydin(self, order: OrderEpyd) -> OrderPydIn:
105
+ ad = await Ad.get(exid=order.offerId, direction__pairex__ex=self.ex)
106
+ cred = await Cred.get(exid=order.paymentDetails.id, contragent__ex=self.ex)
107
+ iam_maker = self.agent.contragent == ad.maker
108
+ taker = (
109
+ (
110
+ await Contragent.get(
111
+ exid=(order.seller if order.is_sell == iam_maker else order.buyer).userId, ex=self.ex
112
+ )
113
+ )
114
+ if iam_maker
115
+ else self.agent.contragent
116
+ )
117
+ return OrderPydIn(
118
+ exid=order.id,
119
+ amount=order.amount.amount,
120
+ maker_topic=None,
121
+ taker_topic=None,
122
+ status=OrderStatus.created,
123
+ created_at=order.createDateTime,
124
+ payed_at=None,
125
+ confirmed_at=None,
126
+ appealed_at=None,
127
+ ad=ad,
128
+ cred=cred,
129
+ taker=taker,
130
+ )
131
+
132
+ @staticmethod
133
+ async def order_pydin2db(order: OrderPydIn) -> Order:
134
+ df, unq = order.args()
135
+ order_db, _ = await Order.update_or_create(df, **unq)
136
+ return order_db
137
+
110
138
  # # # FIATS # # #
111
139
  def fiat_args2ex_pyd(
112
140
  self, exid: int | str, cur: str, detail: str, name: str, fid: int, typ: str, extra=None
113
- ) -> FiatEpyd:
141
+ ) -> CredEpyd:
114
142
  fiat = self.fiat_pyd(
115
143
  paymentMethodCode=exid,
116
144
  currencyCode=cur,
@@ -141,11 +169,11 @@ class AgentClient(BaseAgentClient, AuthClient):
141
169
  }
142
170
 
143
171
  # 25: Список реквизитов моих платежных методов
144
- async def fiats(self) -> list[FiatEpyd]:
172
+ async def creds(self) -> list[CredEpyd]:
145
173
  resp = await self._post("/p2p/public-api/v3/payment-details/get/by-user-id")
146
- return [FiatEpyd(**fiat) for fiat in resp["data"]]
174
+ return [CredEpyd(**fiat) for fiat in resp["data"]]
147
175
 
148
- async def _fiat_epyd2pydin(self, fiat: FiatEpyd) -> FiatPydIn:
176
+ async def cred_epyd2pydin(self, fiat: CredEpyd) -> CredPydIn:
149
177
  if not (pmex := await Pmex.get_or_none(exid=fiat.paymentMethod.code, ex=self.ex_client.ex)):
150
178
  raise HTTPException(
151
179
  FailReason.body, f"No Pmex {fiat.paymentMethod.code} on ex#{self.ex_client.ex.name}", 404
@@ -154,36 +182,26 @@ class AgentClient(BaseAgentClient, AuthClient):
154
182
  raise HTTPException(
155
183
  FailReason.body, f"No Pmcur with cur#{fiat.currency} and pm#{fiat.paymentMethod.code}", 404
156
184
  )
157
- ftx = FiatPydIn(
158
- id=fiat.id,
159
- detail="",
160
- name=fiat.name,
161
- amount=0,
162
- user_id=self.agent.user_id,
185
+ cred_pin = CredPydIn(
186
+ exid=fiat.id,
163
187
  pmcur=pmcur,
188
+ contragent=self.agent.contragent,
189
+ name=fiat.name,
164
190
  )
165
191
  for val in fiat.attributes.values:
166
- if val.name != "BANKS":
167
- ftx.detail = val.value
192
+ if val.name == "BANKS":
193
+ cred_pin.banks = [b.code for b in val.value]
168
194
  else:
169
- val: Banks
170
- ftx.banks = [b.code for b in val.value]
171
- return ftx
195
+ cred_pin.detail = val.value
196
+ return cred_pin
172
197
 
173
198
  # 25: Список реквизитов моих платежных методов
174
- async def set_fiatexs(self) -> list[Cred]:
175
- fiat_epyds = await self.fiats()
176
- fiat_xpyds: list[FiatPydIn] = [await self._fiat_epyd2pydin(f) for f in fiat_epyds]
177
- fxs: list[Cred] = []
178
- for fx in fiat_xpyds:
179
- df, unq = fx.args()
180
- cred_db, _ = await Cred.update_or_create(
181
- {"name": fx.cred.name, "detail": fx.cred.detail}, exid=fx.id, ex=self.ex_client.ex
182
- )
183
- fiat_db, _ = await Fiat.update_or_create(df, **unq)
184
- banks = [await PmexBank.get(exid=bn) for bn in fx.banks]
185
- await cred_db.banks.add(*banks)
186
- return fxs
199
+ async def set_fiats(self) -> list[Fiat]:
200
+ creds_epyd: list[CredEpyd] = await self.creds()
201
+ creds_pyd_in: list[CredPydIn] = [await self.cred_epyd2pydin(f) for f in creds_epyd]
202
+ creds_db = [await self.cred_pydin2db(crd) for crd in creds_pyd_in]
203
+ fiats_db = [await self.fiat_cred2db(cd) for cd in creds_db]
204
+ return fiats_db
187
205
 
188
206
  # 26: Создание реквизита моего платежного метода
189
207
  async def fiat_new(self, fiat: FiatNew) -> Fiat:
@@ -201,7 +219,7 @@ class AgentClient(BaseAgentClient, AuthClient):
201
219
  attributes={"values": vals},
202
220
  )
203
221
  add_fiat = await self._post("/p2p/public-api/v3/payment-details/create", fiat_in.model_dump())
204
- FiatEpyd(**add_fiat["data"])
222
+ CredEpyd(**add_fiat["data"])
205
223
  cred, _ = await Cred.update_or_create(
206
224
  {"name": fiat.name, "detail": fiat.detail}, ex=self.ex_client.ex, exid=add_fiat["data"]["id"]
207
225
  )
@@ -246,10 +264,22 @@ class AgentClient(BaseAgentClient, AuthClient):
246
264
  del_fiat = await self._post("/p2p/public-api/v3/payment-details/delete", {"id": fiat_id})
247
265
  return del_fiat
248
266
 
267
+ async def ad_epyd2pydin(self, ad_: OneAdTakerMakerSale | OneAdMakerBuy | OneAdTakerBuy) -> AdBuyPydIn | AdSalePydIn:
268
+ ad_in: BaseAdPydIn = await self.ex_client.ad_common_epyd2pydin(ad_)
269
+ ad_in.maker = self.agent.contragent
270
+ if isinstance(ad_, _PmsTrait):
271
+ return AdBuyPydIn(
272
+ **ad_in.model_dump(),
273
+ pms_=await Pm.filter(pmexs__ex=self.ex_client.ex, pmexs__exid__in=[p.code for p in ad_.paymentMethods]),
274
+ )
275
+ creds_pin: list[CredPydIn] = [await self.cred_epyd2pydin(c) for c in ad_.paymentDetails]
276
+ creds_ = [(await Cred.update_or_create((a := cp.args())[0], **a[1]))[0] for cp in creds_pin]
277
+ return AdSalePydIn(**ad_in.model_dump(), creds_=creds_)
278
+
249
279
  # 29: Список моих объявлений
250
- async def my_ads(self, status: AdStatus = None) -> list[AdMakerPurchase | AdMakerSale]:
251
- def model(ad: dict) -> (AdMakerPurchase | AdMakerSale).__class__:
252
- return AdMakerSale if ad["type"] == "SALE" else AdMakerPurchase
280
+ async def my_ads(self, status: AdStatus = None) -> list[AdMakerBuy | AdMakerSale]:
281
+ def model(ad: dict) -> (AdMakerBuy | AdMakerSale).__class__:
282
+ return AdMakerSale if ad["type"] == "SALE" else AdMakerBuy
253
283
 
254
284
  mapping = {AdStatus.defActive: "INACTIVE", AdStatus.active: "ACTIVE"}
255
285
  ads = await self._post(
@@ -259,18 +289,46 @@ class AgentClient(BaseAgentClient, AuthClient):
259
289
  return [model(ad)(**ad) for ad in ads["data"] if not status or (status and ad["status"] == mapping[status])]
260
290
 
261
291
  # 43: Моя объява по id
262
- async def my_ad(self, ad_id: int) -> OneAdMakerPurchase | OneAdTakerMakerSale:
292
+ async def my_ad(self, ad_id: int) -> OneAdMakerBuy | OneAdTakerMakerSale:
263
293
  ad = await self._post("/p2p/public-api/v2/offer/get-user-own", {"offerId": ad_id})
264
294
  ad: dict = ad["data"]
265
295
  assert ad["user"]["userId"] == self.agent.contragent.exid, "Not your ad"
266
- model = OneAdTakerMakerSale if ad["type"] == "SALE" else OneAdMakerPurchase
296
+ model = OneAdTakerMakerSale if ad["type"] == "SALE" else OneAdMakerBuy
267
297
  return model(**ad)
268
298
 
299
+ async def ad_f2ein(
300
+ self, coin: Coin, cur: Cur, is_sell: bool, vol: int = None, cur_min: int = None
301
+ ) -> AdMakerNewSale | AdMakerNewBuy:
302
+ coinex = await Coinex.get(coin=coin, ex=self.ex)
303
+ curex = await Curex.get(ex=self.ex, cur=cur)
304
+ creds = await Cred.filter(contragent__agent__user_id=self.agent.user_id, pmcur__cur=cur).limit(5)
305
+ # todo: ordering and filtering by fiat.amount-target
306
+ ad_ein = _AdNew(
307
+ type="SALE" if is_sell else "PURCHASE",
308
+ initVolume=AvailableVolume(currencyCode=coinex.exid, amount=str(vol or coinex.minimum)),
309
+ orderRoundingRequired=curex.rounding_scale is not None,
310
+ price={
311
+ "type": "FLOATING",
312
+ "baseCurrencyCode": coinex.exid,
313
+ "quoteCurrencyCode": curex.exid,
314
+ "value": "120" if is_sell else "80",
315
+ },
316
+ orderAmountLimits={"min": str(cur_min or curex.minimum)},
317
+ paymentConfirmTimeout="PT15M",
318
+ comment="tst",
319
+ )
320
+ if ad_ein.type == "SALE":
321
+ ad_ein = AdMakerNewSale(**ad_ein.model_dump(exclude_none=True), paymentDetailsIds=[c.exid for c in creds])
322
+ else:
323
+ pmexs = await Pmex.filter(ex=self.agent.contragent.ex, pm__pmcurs__id__in=[c.pmcur_id for c in creds])
324
+ ad_ein = AdMakerNewBuy(**ad_ein.model_dump(exclude_none=True), paymentMethodCodes=[p.exid for p in pmexs])
325
+ return ad_ein
326
+
269
327
  # 30: Создание объявления
270
- async def ad_new(self, ad: _AdNew) -> OneAdMakerPurchase | OneAdTakerMakerSale:
328
+ async def ad_new(self, ad: _AdNew) -> OneAdMakerBuy | OneAdTakerMakerSale:
271
329
  create = await self._post("/p2p/public-api/v2/offer/create", ad.model_dump())
272
330
  if res := create.get("data"):
273
- return OneAdTakerMakerSale(**res) if res["type"] == "SALE" else OneAdMakerPurchase(**res)
331
+ return OneAdTakerMakerSale(**res) if res["type"] == "SALE" else OneAdMakerBuy(**res)
274
332
  raise Exception(create)
275
333
 
276
334
  # 31: Редактирование объявления
@@ -312,7 +370,7 @@ class AgentClient(BaseAgentClient, AuthClient):
312
370
 
313
371
  # 33: Вкл/выкл объявления
314
372
  async def ad_switch(self, ad_id: int, active: bool) -> bool:
315
- ad: OneAdMakerPurchase | OneAdTakerMakerSale = await self.my_ad(ad_id)
373
+ ad: OneAdMakerBuy | OneAdTakerMakerSale = await self.my_ad(ad_id)
316
374
  pre = "" if active else "de"
317
375
  switch = await self._post(f"/p2p/public-api/v2/offer/{pre}activate", {"type": ad.type, "offerId": ad_id})
318
376
  return switch["status"] == "SUCCESS"
@@ -353,50 +411,40 @@ class AgentClient(BaseAgentClient, AuthClient):
353
411
 
354
412
  async def main():
355
413
  await init_db(PG_DSN, models, True)
356
- agents: tuple[Agent, Agent] = (
357
- await Agent.filter(ex_id=34, auth__isnull=False).order_by("user_id").limit(2).prefetch_related("ex")
414
+ maker: Agent
415
+ taker: Agent
416
+ maker, taker = (
417
+ await Agent.filter(contragent__ex_id=34, auth__isnull=False, user__status__gte=UserStatus.MEMBER)
418
+ .order_by("user_id")
419
+ .limit(2)
420
+ .prefetch_related("contragent__ex")
358
421
  )
359
- maker, taker = agents
360
422
  mcl: AgentClient = maker.client()
361
423
  tcl: AgentClient = taker.client()
362
- # msale_ad: list[AdEpyd] = await mcl.ex_client.ad(1941011)
363
- # mbuy_ad: list[AdEpyd] = await mcl.ex_client.ad(1813220)
364
- # fiatexs = await tcl.set_fiatexs()
365
- # fiatexs = await mcl.set_fiatexs()
366
- # await mcl.my_ads()
367
- coinex = await Coinex.get(coin__ticker="USDT")
368
- taker_fiats = await Fiat.filter(user_id=taker.user_id)
369
- maker_fiats = await Fiat.filter(user_id=maker.user_id)
370
- common_mfs: set[Fiat] = {mf for mf in maker_fiats if mf.pmcur_id in {tf.pmcur_id for tf in taker_fiats}}
371
- mfs: list[Fiat] = sorted(common_mfs, key=lambda x: x.amount, reverse=True)[:5]
372
- curex = await Curex.get(ex=maker.ex, cur_id=(await mfs[0].pmcur).cur_id)
373
- ad_ein = AdMakerNewSale(
374
- type="SALE",
375
- initVolume={"currencyCode": coinex.exid, "amount": f"{coinex.minimum}"},
376
- orderRoundingRequired=curex.rounding_scale is not None,
377
- price={
378
- "type": "FLOATING",
379
- "baseCurrencyCode": coinex.exid,
380
- "quoteCurrencyCode": curex.exid,
381
- "value": "120",
382
- },
383
- orderAmountLimits={"min": str(curex.minimum)},
384
- paymentConfirmTimeout="PT15M",
385
- comment="test1",
386
- )
387
- if ad_ein.type == "SALE":
388
- mfiatexids = [(await Cred.get(ex=maker.ex, fiat=mf)).exid for mf in mfs]
389
- ad_ein.paymentDetailsIds = mfiatexids[:1]
390
- else:
391
- pmexsids = [(await Pmex.get(ex=maker.ex, pm__pmcurs__id=mf.pmcur_id)).exid for mf in mfs]
392
- ad_ein.paymentMethodCodes = pmexsids[:1]
393
-
394
- mad = await mcl.ad_new(ad_ein)
395
- await mcl.ad_switch(mad.id, True)
396
- await tcl.order_request(mad.id, float(mad.orderAmountLimits.min))
397
- ad_in = await mcl.ex_client.ad_epyd2pydin(mad)
398
- ad_db = await Ad.create(**ad_in.model_dump(exclude_none=True))
399
- print(ad_db)
424
+ # fiats = await tcl.set_fiats()
425
+ # fiats = await mcl.set_fiats()
426
+ # my_ads = await mcl.my_ads()
427
+ # my_ads_in = [await mcl.ad_epyd2pydin(ma) for ma in my_ads]
428
+ # _my_ads_db = [await mcl.ad_pydin2db(ma) for ma in my_ads_in]
429
+
430
+ coin = await Coin.get(ticker="USDT")
431
+ cur = await Cur.get(ticker="RUB")
432
+ ad_sell_ein = await tcl.ad_f2ein(coin, cur, True)
433
+ ad_buy_ein = await mcl.ad_f2ein(coin, cur, False)
434
+ sad = await tcl.ad_new(ad_sell_ein)
435
+ bad = await mcl.ad_new(ad_buy_ein)
436
+ await tcl.ad_switch(sad.id, True)
437
+ await mcl.ad_switch(bad.id, True)
438
+ sad_in = await tcl.ad_epyd2pydin(sad)
439
+ await Ad.create(**sad_in.model_dump(exclude_none=True))
440
+ bad_in = await mcl.ad_epyd2pydin(bad)
441
+ await Ad.create(**bad_in.model_dump(exclude_none=True))
442
+
443
+ # order_epin: OrderEpydIn = await tcl.order_ad2epydin(ad_db, float(mad.orderAmountLimits.min), cred_ids[0])
444
+ # new_order: OrderEpyd = await tcl.order_request(order_epin)
445
+ # order_pin: OrderPydIn = await tcl.order_epyd2pydin(new_order)
446
+ # _order_db = await tcl.order_pydin2db(order_pin)
447
+
400
448
  await tcl.close(), await mcl.close()
401
449
 
402
450
 
@@ -1,15 +1,21 @@
1
1
  from x_client.http import Client as HttpClient
2
+ from xync_schema.models import Agent
2
3
 
3
4
  from xync_client.Abc.AuthTrait import BaseAuthTrait
5
+ from xync_client.Abc.Base import BaseClient
4
6
  from xync_client.TgWallet.pyro import PyroClient
5
7
 
6
8
 
7
- class AuthClient(BaseAuthTrait):
9
+ class AuthClient(BaseAuthTrait, BaseClient):
8
10
  async def _get_auth_hdrs(self) -> dict[str, str]:
11
+ if not self.agent:
12
+ self.agent = (
13
+ await Agent.filter(contragent__ex=self.ex, auth__isnull=False).prefetch_related("contragent").first()
14
+ )
9
15
  pyro = PyroClient(self.agent)
10
16
  init_data = await pyro.get_init_data()
11
17
  tokens = HttpClient("walletbot.me")._post("/api/v1/users/auth/", init_data)
12
- self.agent.exid = tokens["user_id"]
13
- await self.agent.save()
18
+ self.agent.contragent.exid = tokens["user_id"]
19
+ await self.agent.contragent.save()
14
20
  pref = "" if self.__class__.__name__ == "AssetClient" else "Bearer "
15
21
  return {"Wallet-Authorization": tokens["jwt"], "Authorization": pref + tokens["value"]}
@@ -1,19 +1,19 @@
1
1
  from asyncio import run
2
2
 
3
3
  from x_model import init_db
4
- from xync_schema.pydantic import AdPydIn, PmPyd, PmexBankPyd, CurEpyd, CoinEpyd
4
+ from xync_schema.pydantic import PmPyd, PmexBankPyd, CurEpyd, CoinEpyd, BaseAdPydIn, AdBuyPydIn
5
5
 
6
6
  from xync_schema import models
7
- from xync_schema.models import Ex, Agent, Direction, Pair, Coin, Cur, Pm, Fiat, Contragent
7
+ from xync_schema.models import Ex, Direction, Pair, Coin, Cur, Pm, Contragent, PairEx
8
8
 
9
9
  from xync_client.TgWallet.pyd import (
10
10
  PmEpydRoot,
11
- _BaseAd,
12
11
  OneAdTakerMakerSale,
13
- OneAdTakerPurchase,
14
- AdTakerSalePurchase,
15
- _UserTrait,
12
+ OneAdTakerBuy,
13
+ AdTakerSaleBuy,
16
14
  _TakerOne,
15
+ _PmsTrait,
16
+ _BaseAd,
17
17
  )
18
18
  from xync_client.loader import PG_DSN
19
19
  from xync_client.Abc.Ex import BaseExClient
@@ -22,12 +22,8 @@ from xync_client.TgWallet.auth import AuthClient
22
22
 
23
23
 
24
24
  class ExClient(BaseExClient, AuthClient):
25
- def __init__(self, ex: Ex, agent: Agent = None):
26
- if not agent:
27
- # ex should be with fetched .agents
28
- agent = [ctg for ctg in ex.contragents if ctg.agent and ctg.agent.auth][0].agent
29
- self.agent: Agent = agent # need for AuthTrait
30
- super().__init__(ex) # , "host_p2p"
25
+ def __init__(self, ex: Ex):
26
+ super().__init__(ex) # BaseExClient
31
27
 
32
28
  def pm_type_map(self, pm: Pm) -> str:
33
29
  return "V2" if pm.name.startswith("SBP") else "V1"
@@ -92,13 +88,13 @@ class ExClient(BaseExClient, AuthClient):
92
88
  async def ad(self, ad_id: int) -> _TakerOne:
93
89
  ad = await self._post("/p2p/public-api/v2/offer/get", {"offerId": ad_id})
94
90
  ad: dict = ad["data"]
95
- model = OneAdTakerMakerSale if ad["type"] == "SALE" else OneAdTakerPurchase
91
+ model = OneAdTakerMakerSale if ad["type"] == "SALE" else OneAdTakerBuy
96
92
  return model(**ad)
97
93
 
98
94
  # 24: Список объяв по (buy/sell, cur, coin, pm)
99
95
  async def ads(
100
96
  self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str | int] = None, amount: int = None
101
- ) -> list[AdTakerSalePurchase]:
97
+ ) -> list[AdTakerSaleBuy]:
102
98
  params = {
103
99
  "baseCurrencyCode": coin_exid,
104
100
  "quoteCurrencyCode": cur_exid,
@@ -108,46 +104,70 @@ class ExClient(BaseExClient, AuthClient):
108
104
  # "merchantVerified": "TRUSTED"
109
105
  }
110
106
  ads = await self._post("/p2p/public-api/v2/offer/depth-of-market/", params, "data")
111
- return [AdTakerSalePurchase(**ad) for ad in ads]
107
+ return [AdTakerSaleBuy(**ad) for ad in ads]
112
108
 
113
- async def ad_epyd2pydin(self, ad: _BaseAd) -> AdPydIn:
109
+ async def ad_common_epyd2pydin(self, ad: _BaseAd) -> BaseAdPydIn:
114
110
  coin = await Coin.get_or_create_by_name(ad.price.baseCurrencyCode)
115
111
  cur = await Cur.get_or_create_by_name(ad.price.quoteCurrencyCode)
116
- pair, _ = await Pair.get_or_create(coin=coin, cur=cur, ex=self.ex)
117
- dr, _ = await Direction.get_or_create(pair=pair, sell=ad.is_sell)
118
- maker, _ = await Contragent.update_or_create({"name": ad.user.nickname}, exid=ad.user.userId, ex=self.ex)
119
- adx = AdPydIn(
120
- id=ad.id,
112
+ pair, _ = await Pair.get_or_create(coin=coin, cur=cur)
113
+ pairex, _ = await PairEx.get_or_create(pair=pair, ex=self.ex)
114
+ dr, _ = await Direction.get_or_create(pairex=pairex, sell=ad.is_sell)
115
+ return BaseAdPydIn(
116
+ exid=ad.id,
121
117
  price=ad.price.value,
122
118
  min_fiat=ad.orderAmountLimits.min,
123
119
  max_fiat=ad.orderAmountLimits.max,
124
120
  direction=dr,
125
121
  detail=getattr(ad, "comment", None),
126
- maker=maker if isinstance(ad, _UserTrait) else self.agent.contragent,
127
- # todo: maybe later adpm_banks
128
122
  )
129
- if ad.is_sell and getattr(ad, "paymentDetails", None):
130
- adx.fiats_ = await Fiat.filter(cred__ex=self.ex, cred__exid__in=[p.id for p in ad.paymentDetails])
131
- else:
132
- adx.pms_ = await Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=[p.code for p in ad.paymentMethods])
133
- return adx
123
+
124
+ async def ad_taker_epyd2pydin(self, ad: _TakerOne) -> AdBuyPydIn:
125
+ adx: BaseAdPydIn = await self.ad_common_epyd2pydin(ad)
126
+ adx.maker = (await Contragent.update_or_create({"name": ad.user.nickname}, ex=self.ex, exid=ad.user.userId))[0]
127
+ pms = ad.paymentMethods if isinstance(ad, _PmsTrait) else [pd.paymentMethod for pd in ad.paymentDetails]
128
+ return AdBuyPydIn(
129
+ **adx.model_dump(), pms_=await Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=[p.code for p in pms])
130
+ )
134
131
 
135
132
 
136
133
  async def _test():
137
134
  await init_db(PG_DSN, models, True)
138
- tgex = await Ex.get(name="TgWallet").prefetch_related("contragents__agent", "contragents__ex")
135
+ tgex = await Ex.get(name="TgWallet")
139
136
  cl: ExClient = tgex.client()
140
- # await cl.pms("RUB")
141
- await cl.set_pmcurexs()
142
- await cl.set_coinexs()
143
- ads: list[AdTakerSalePurchase] = await cl.ads("USDT", "RUB", False)
144
- ad: _TakerOne = await cl.ad(ads[0].id)
145
- ad_pydin: AdPydIn = await cl.ad_epyd2pydin(ad)
146
- await cl.ad_pydin2db(ad_pydin)
147
- ads: list[AdTakerSalePurchase] = await cl.ads("USDT", "RUB", True)
148
- ad: AdTakerSalePurchase = ads[1]
149
- ad_pydin: AdPydIn = await cl.ad_epyd2pydin(ad)
150
- await cl.ad_pydin2db(ad_pydin)
137
+
138
+ # await cl.set_pmcurexs()
139
+ # await cl.set_coinexs()
140
+
141
+ # # # SALE # # #
142
+ # get ads list
143
+ ads: list[AdTakerSaleBuy] = await cl.ads("TON", "RUB", True)
144
+ # prepare ad list items for saving
145
+ ads_pydin: list[BaseAdPydIn] = [await cl.ad_taker_epyd2pydin(adp) for adp in ads]
146
+ # list items save
147
+ _ads_db = [await cl.ad_pydin2db(adi) for adi in ads_pydin]
148
+
149
+ # # get ad fulls
150
+ # ads_pyd: list[_TakerOne] = [await cl.ad(ad.id) for ad in ads]
151
+ # # prepare ad fulls for saving
152
+ # ads_pydin: list[AdBuyPydIn] = [await cl.ad_taker_epyd2pydin(adp) for adp in ads_pyd]
153
+ # # full ones save
154
+ # _ads_db = [await cl.ad_pydin2db(adi) for adi in ads_pydin]
155
+
156
+ # # # BUY # # #
157
+ # get ads list
158
+ ads: list[AdTakerSaleBuy] = await cl.ads("TON", "RUB", False)
159
+ # prepare ad list items for saving
160
+ ads_pydin: list[BaseAdPydIn] = [await cl.ad_taker_epyd2pydin(adp) for adp in ads]
161
+ # list items save
162
+ _ads_db = [await cl.ad_pydin2db(adi) for adi in ads_pydin]
163
+
164
+ # # get ad fulls
165
+ # ads_pyd = [await cl.ad(ad.id) for ad in ads]
166
+ # # prepare ad fulls for saving
167
+ # ads_pydin: list[AdBuyPydIn] = [await cl.ad_taker_epyd2pydin(adp) for adp in ads_pyd]
168
+ # # full ones save
169
+ # _ads_db = [await cl.ad_pydin2db(adi) for adi in ads_pydin]
170
+
151
171
  await cl.close()
152
172
 
153
173
 
@@ -1,6 +1,6 @@
1
1
  from typing import Literal
2
2
  from pydantic import BaseModel, computed_field
3
- from xync_schema.pydantic import BaseAd
3
+ from xync_schema.pydantic import BaseAd, BaseOrder
4
4
 
5
5
 
6
6
  # Модели для вложенных структур
@@ -44,7 +44,7 @@ class OrderLimits(OrderLimitsIn):
44
44
  approximate: bool
45
45
 
46
46
 
47
- Status = Literal["ACTIVE", "INACTIVE", "ACTIVATING", "DEACTIVATING"]
47
+ Status = Literal["ACTIVE", "INACTIVE", "ACTIVATING", "DEACTIVATING", "UPDATING"]
48
48
 
49
49
 
50
50
  class ChangeLogItem(BaseModel):
@@ -120,7 +120,7 @@ class FiatEpydIn(BaseModel):
120
120
  attributes: Attrs | AttrsV2In
121
121
 
122
122
 
123
- class FiatEpyd(BaseModel):
123
+ class CredEpyd(BaseModel):
124
124
  id: int
125
125
  userId: int
126
126
  paymentMethod: PmEpydRoot
@@ -134,16 +134,19 @@ class InitVolume(BaseModel):
134
134
  amount: str # of asset
135
135
 
136
136
 
137
+ AdType = Literal["PURCHASE", "SALE"]
138
+
139
+
137
140
  # Основные модели
138
141
  class _BaseInOutAd(BaseAd):
139
- type: Literal["PURCHASE", "SALE"]
142
+ type: AdType
140
143
  price: Price
141
144
 
142
145
 
143
146
  class _BaseAd(_BaseInOutAd):
144
147
  number: str
145
- orderAmountLimits: OrderLimits
146
- orderVolumeLimits: OrderLimits
148
+ orderAmountLimits: OrderLimits # fiat
149
+ orderVolumeLimits: OrderLimits # asset
147
150
  availableVolume: AvailableVolume
148
151
 
149
152
  @computed_field
@@ -165,38 +168,41 @@ class _UserTrait:
165
168
  user: User # taker | One
166
169
 
167
170
 
171
+ PaymentConfirmTimeout = Literal["PT3M", "PT15M", "PT30M", "PT45M", "PT1H", "PT2H", "PT3H"]
172
+
173
+
168
174
  class _OneTrait(_StatusTrait, _UserTrait):
169
175
  comment: str
170
176
  changeLog: ChangeLog
171
177
  createDateTime: str
172
- paymentConfirmTimeout: Literal["PT3M", "PT15M", "PT30M"]
178
+ paymentConfirmTimeout: PaymentConfirmTimeout
173
179
 
174
180
 
175
181
  class _TakerOne(_BaseAd, _OneTrait):
176
- orderConfirmationTimeout: Literal["PT3M", "PT15M"]
182
+ orderConfirmationTimeout: Literal["PT1M30S", "PT3M", "PT15M"]
177
183
  orderAcceptTimeout: Literal["PT10M"]
178
184
 
179
185
 
180
- class AdTakerSalePurchase(_BaseAd, _PmsTrait, _UserTrait):
186
+ class AdTakerSaleBuy(_BaseAd, _PmsTrait, _UserTrait):
181
187
  availableVolume: str
182
188
 
183
189
 
184
190
  class OneAdTakerMakerSale(_TakerOne):
185
- paymentDetails: list[FiatEpyd]
191
+ paymentDetails: list[CredEpyd]
186
192
  fee: Fee
187
193
 
188
194
 
189
- class OneAdTakerPurchase(_TakerOne, _PmsTrait): ...
195
+ class OneAdTakerBuy(_TakerOne, _PmsTrait): ...
190
196
 
191
197
 
192
- class AdMakerPurchase(_BaseAd, _PmsTrait, _StatusTrait): ...
198
+ class AdMakerBuy(_BaseAd, _PmsTrait, _StatusTrait): ...
193
199
 
194
200
 
195
- class OneAdMakerPurchase(_BaseAd, _OneTrait, _PmsTrait): ...
201
+ class OneAdMakerBuy(_BaseAd, _OneTrait, _PmsTrait): ...
196
202
 
197
203
 
198
204
  class AdMakerSale(_BaseAd, _StatusTrait):
199
- paymentDetails: list[FiatEpyd]
205
+ paymentDetails: list[CredEpyd]
200
206
 
201
207
 
202
208
  # # #
@@ -206,9 +212,9 @@ class AdMakerSale(_BaseAd, _StatusTrait):
206
212
  class _BaseIn(_BaseInOutAd):
207
213
  orderRoundingRequired: bool
208
214
  orderAmountLimits: OrderLimitsIn
209
- paymentConfirmTimeout: str
215
+ paymentConfirmTimeout: PaymentConfirmTimeout
210
216
  comment: str
211
- takerFilter: TakerFilter
217
+ takerFilter: TakerFilter | None = None
212
218
 
213
219
 
214
220
  class _AdNew(_BaseIn):
@@ -224,7 +230,7 @@ class _SaleInTrait:
224
230
  paymentDetailsIds: list[int]
225
231
 
226
232
 
227
- class _PurchaseInTrait:
233
+ class _BuyInTrait:
228
234
  paymentMethodCodes: list[str]
229
235
 
230
236
 
@@ -234,77 +240,46 @@ class AdMakerNewSale(_AdNew, _SaleInTrait): ...
234
240
  class AdMakerUpdSale(_AdUpd, _SaleInTrait): ...
235
241
 
236
242
 
237
- class AdMakerNewPurchase(_AdNew, _PurchaseInTrait): ...
238
-
239
-
240
- class AdMakerUpdPurchase(_AdUpd, _PurchaseInTrait): ...
241
-
242
-
243
- # class _BaseInOutAd(BaseAd):
244
- # type: Literal["PURCHASE", "SALE"]
245
- # price: Price
246
- # orderAmountLimits: OrderLimits # cur/fiat
247
- # paymentConfirmTimeout: Literal["PT3M", "PT15M", "PT30M"] | None = None # sale?
248
- #
249
- # @computed_field
250
- # @property
251
- # def is_sell(self) -> bool:
252
- # return self.type == "SALE"
253
- #
254
- #
255
- # class MyAdEpydIn(_BaseInOutAd):
256
- # comment: str = ""
257
- # initVolume: InitVolume # of asset
258
- # orderRoundingRequired: bool
259
- # paymentMethodCodes: list[str] | None = None # purchase
260
- # paymentDetailsIds: list[int] | None = None # sale
261
- #
262
- # @classmethod
263
- # @model_validator(mode="before")
264
- # def check_at_least_one_field(cls, values):
265
- # if values.get("paymentMethodCodes") or values.get("paymentDetailsIds"):
266
- # return values
267
- # raise ValueError("paymentMethodCodes or paymentDetailsIds is required")
268
- #
269
- #
270
- # class _BaseAd(_BaseInOutAd):
271
- # availableVolume: str # of asset
272
- # number: str
273
- # orderVolumeLimits: OrderLimits # coin/asset
274
- # paymentDetails: list[FiatEpyd] | None = None # SALE
275
- # paymentMethods: list[PmEpydRoot] | None = None # PURCHASE
276
- #
277
- #
278
- # class AdEpyd(_BaseAd):
279
- # takerFilter: TakerFilter
280
- # user: User
281
- # orderConfirmationTimeout: Literal["PT3M", "PT15M"] | None = None # purchase?
282
- # orderAcceptTimeout: Literal["PT10M"] | None = None # TAKER
283
- #
284
- # @classmethod
285
- # @model_validator(mode="before")
286
- # def check_at_least_one_field(cls, values):
287
- # if values.get("paymentMethods") or values.get("paymentDetails"):
288
- # return values
289
- # raise ValueError("paymentMethods or paymentDetails is required")
290
- #
291
- #
292
- # class MyAdEpyd(_BaseAd):
293
- # availableVolume: AvailableVolume # of asset
294
- # status: Status
295
- #
296
- #
297
- # class _AllAdFullTrait:
298
- # createDateTime: str
299
- # changeLog: ChangeLog
300
- # takerFilter: TakerFilter
301
- # fee: Fee | None = None
302
- # comment: str
303
- #
304
- #
305
- # class MyAdFullEpyd(MyAdEpyd, _AllAdFullTrait): ...
306
- #
307
- #
308
- # class AdFullEpyd(AdEpyd, _AllAdFullTrait):
309
- # availableVolume: AvailableVolume # of asset
310
- # status: Status
243
+ class AdMakerNewBuy(_AdNew, _BuyInTrait): ...
244
+
245
+
246
+ class AdMakerUpdBuy(_AdUpd, _BuyInTrait): ...
247
+
248
+
249
+ OrderStatus = Literal["ACTIVE", "DRAFT"]
250
+
251
+
252
+ class OrderEpydIn(BaseOrder):
253
+ offerId: int
254
+ type: AdType
255
+ paymentDetailsId: int
256
+ amount: AvailableVolume
257
+
258
+
259
+ class OrderEpyd(BaseModel):
260
+ id: int
261
+ number: str
262
+ seller: User
263
+ buyer: User
264
+ offerId: int
265
+ offerType: AdType
266
+ offerComment: str = ""
267
+ isExpress: bool
268
+ price: Price
269
+ paymentDetails: CredEpyd
270
+ volume: AvailableVolume
271
+ amount: AvailableVolume
272
+ feeVolume: AvailableVolume
273
+ paymentConfirmTimeout: PaymentConfirmTimeout
274
+ buyerSendingPaymentConfirmationTimeout: Literal["PT15M"]
275
+ createDateTime: str
276
+ statusUpdateDateTime: str
277
+ holdRestrictionsWillBeApplied: bool
278
+ status: OrderStatus
279
+ changeLog: ChangeLog
280
+ isAutoAccept: bool
281
+
282
+ @computed_field
283
+ @property
284
+ def is_sell(self) -> bool:
285
+ return self.offerType == "SALE"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: xync-client
3
- Version: 0.0.25.dev11
3
+ Version: 0.0.25.dev19
4
4
  Author-email: Mike Artemiev <mixartemev@gmail.com>
5
5
  Project-URL: Homepage, https://gitlab.com/XyncNet/client
6
6
  Project-URL: Repository, https://gitlab.com/XyncNet/client