xync-db 0.6.1.dev1__tar.gz → 0.7.0__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.
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/PKG-INFO +1 -1
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/enums.py +11 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/0001_initial.py +77 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/0002_triggers.py +17 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/c_net_seed.py +18 -1
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/models/__init__.py +62 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/.gitignore +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/README.md +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/pyproject.toml +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/__init__.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/config.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/exceptions.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/graph.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/i18n.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/login_store.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/logo.png +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/CLAUDE.md +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/__init__.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/0003_seed.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/__init__.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/__init__.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/a_actor_seed.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/a_bin_seed.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/a_city_seed.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/a_country_seed.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/a_tg_seed.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/b_htx_seed.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/cities.json +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/countries.json +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/ranges.csv +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/migrations/models/seed/xync_files.sql +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/models/CLAUDE.md +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/py.typed +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/shared.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/__init__.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/db/__init__.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/db/actor.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/db/ad.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/db/common.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/db/cred.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/db/order.py +0 -0
- {xync_db-0.6.1.dev1 → xync_db-0.7.0}/src/xync_db/typs/db/pm.py +0 -0
|
@@ -43,6 +43,17 @@ class W3SendStatus(IntEnum):
|
|
|
43
43
|
done = 3
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
class W3SwapStatus(IntEnum):
|
|
47
|
+
"""Своп web3-монеты в фиат Xync (W3Swap, web3.md): pending — заявка, ждём монету с `frm`; received — external
|
|
48
|
+
поймал перевод (tx_hash, amount), api зачисляет фиат; done — зачислено (W3Swap.tx); manual — разбирает админ
|
|
49
|
+
(перевод без заявки или зачисление упало)."""
|
|
50
|
+
|
|
51
|
+
pending = 0
|
|
52
|
+
received = 1
|
|
53
|
+
done = 2
|
|
54
|
+
manual = 3
|
|
55
|
+
|
|
56
|
+
|
|
46
57
|
class LinkType(IntEnum):
|
|
47
58
|
ref = 0 # open or trusted
|
|
48
59
|
request = 1 # open or personalized
|
|
@@ -39,6 +39,7 @@ from xync_db.enums import (
|
|
|
39
39
|
TransactionStatus,
|
|
40
40
|
UserStatus,
|
|
41
41
|
W3SendStatus,
|
|
42
|
+
W3SwapStatus,
|
|
42
43
|
)
|
|
43
44
|
|
|
44
45
|
|
|
@@ -1951,6 +1952,7 @@ class Migration(migrations.Migration):
|
|
|
1951
1952
|
),
|
|
1952
1953
|
("contract", fields.CharField(null=True, max_length=128)),
|
|
1953
1954
|
("decimals", UInt1Field()),
|
|
1955
|
+
("scanned", fields.BigIntField(null=True)),
|
|
1954
1956
|
],
|
|
1955
1957
|
options={
|
|
1956
1958
|
"table": "coin_net",
|
|
@@ -2602,6 +2604,81 @@ class Migration(migrations.Migration):
|
|
|
2602
2604
|
options={"table": "transaction", "app": "models", "pk_attr": "id"},
|
|
2603
2605
|
bases=["Model"],
|
|
2604
2606
|
),
|
|
2607
|
+
ops.CreateModel(
|
|
2608
|
+
name="W3Swap",
|
|
2609
|
+
fields=[
|
|
2610
|
+
("id", fields.IntField(generated=True, primary_key=True, unique=True, db_index=True)),
|
|
2611
|
+
("created_at", DatetimeSecField(db_default=Now(), auto_now=False, auto_now_add=True)),
|
|
2612
|
+
("updated_at", DatetimeSecField(db_default=Now(), auto_now=True, auto_now_add=False)),
|
|
2613
|
+
(
|
|
2614
|
+
"user",
|
|
2615
|
+
fields.ForeignKeyField(
|
|
2616
|
+
"models.User",
|
|
2617
|
+
source_field="user_id",
|
|
2618
|
+
null=True,
|
|
2619
|
+
db_constraint=True,
|
|
2620
|
+
to_field="id",
|
|
2621
|
+
related_name="w3_swaps",
|
|
2622
|
+
on_delete=OnDelete.CASCADE,
|
|
2623
|
+
),
|
|
2624
|
+
),
|
|
2625
|
+
(
|
|
2626
|
+
"coin_net",
|
|
2627
|
+
fields.ForeignKeyField(
|
|
2628
|
+
"models.CoinNet",
|
|
2629
|
+
source_field="coin_net_id",
|
|
2630
|
+
db_constraint=True,
|
|
2631
|
+
to_field="id",
|
|
2632
|
+
related_name="w3_swaps",
|
|
2633
|
+
on_delete=OnDelete.CASCADE,
|
|
2634
|
+
),
|
|
2635
|
+
),
|
|
2636
|
+
(
|
|
2637
|
+
"cur",
|
|
2638
|
+
fields.ForeignKeyField(
|
|
2639
|
+
"models.Cur",
|
|
2640
|
+
source_field="cur_id",
|
|
2641
|
+
null=True,
|
|
2642
|
+
db_constraint=True,
|
|
2643
|
+
to_field="id",
|
|
2644
|
+
related_name="w3_swaps",
|
|
2645
|
+
on_delete=OnDelete.CASCADE,
|
|
2646
|
+
),
|
|
2647
|
+
),
|
|
2648
|
+
("frm", fields.CharField(max_length=128)),
|
|
2649
|
+
("rate", fields.FloatField(null=True)),
|
|
2650
|
+
(
|
|
2651
|
+
"status",
|
|
2652
|
+
fields.IntEnumField(
|
|
2653
|
+
description="pending: 0\nreceived: 1\ndone: 2\nmanual: 3",
|
|
2654
|
+
db_default=W3SwapStatus.pending,
|
|
2655
|
+
enum_type=W3SwapStatus,
|
|
2656
|
+
generated=False,
|
|
2657
|
+
),
|
|
2658
|
+
),
|
|
2659
|
+
("tx_hash", fields.CharField(null=True, unique=True, max_length=128)),
|
|
2660
|
+
("amount", UInt16Field(null=True)),
|
|
2661
|
+
(
|
|
2662
|
+
"tx",
|
|
2663
|
+
fields.ForeignKeyField(
|
|
2664
|
+
"models.Transaction",
|
|
2665
|
+
source_field="tx_id",
|
|
2666
|
+
null=True,
|
|
2667
|
+
db_constraint=True,
|
|
2668
|
+
to_field="id",
|
|
2669
|
+
related_name="w3_swaps",
|
|
2670
|
+
on_delete=OnDelete.CASCADE,
|
|
2671
|
+
),
|
|
2672
|
+
),
|
|
2673
|
+
],
|
|
2674
|
+
options={
|
|
2675
|
+
"table": "w3_swap",
|
|
2676
|
+
"app": "models",
|
|
2677
|
+
"pk_attr": "id",
|
|
2678
|
+
"table_description": "Web3 coin → Xync fiat swap",
|
|
2679
|
+
},
|
|
2680
|
+
bases=["Model", "TsTrait"],
|
|
2681
|
+
),
|
|
2605
2682
|
ops.CreateModel(
|
|
2606
2683
|
name="Sign",
|
|
2607
2684
|
fields=[
|
|
@@ -16,6 +16,7 @@ Channels emitted:
|
|
|
16
16
|
ads_upd / mads_upd -- ad row changed (diff payload)
|
|
17
17
|
creds_new/creds_del/creds_upd, credexs_del, credexs_orphan -- cred/credex lifecycle
|
|
18
18
|
w3addr_wanted / w3addr_filled -- web3 address handshake
|
|
19
|
+
w3swap_received -- w3_swap: монета свопа пришла (received) → api зачисляет фиат
|
|
19
20
|
|
|
20
21
|
Guards (RAISE, без NOTIFY): tx_before (баланс/иммутабельность/монотонность), tx_no_delete,
|
|
21
22
|
ad_exid_ex_uniq (exid уникален per-exchange), ad_cond_side_uniq/cond_txt_side_uniq (unique
|
|
@@ -475,6 +476,21 @@ $net_addr_upd$ LANGUAGE plpgsql;
|
|
|
475
476
|
CREATE OR REPLACE TRIGGER net_addr_upd AFTER UPDATE ON "net_addr" FOR EACH ROW EXECUTE FUNCTION net_addr_upd();
|
|
476
477
|
"""
|
|
477
478
|
|
|
479
|
+
# своп web3→фиат: external пишет пришедший перевод в заявку (status → received) — api зачисляет фиат. Каждый
|
|
480
|
+
# UPDATE received-строки шлёт заново: так external рекикает потерянный NOTIFY (рестарт api), а зачисление
|
|
481
|
+
# идемпотентно (claim received→done).
|
|
482
|
+
W3SWAP_SQL = """
|
|
483
|
+
CREATE OR REPLACE FUNCTION w3_swap_upd() RETURNS trigger AS $w3_swap_upd$
|
|
484
|
+
BEGIN
|
|
485
|
+
IF NEW.status = 1 THEN
|
|
486
|
+
PERFORM pg_notify('w3swap_received', NEW.id::varchar);
|
|
487
|
+
END IF;
|
|
488
|
+
RETURN NULL;
|
|
489
|
+
END
|
|
490
|
+
$w3_swap_upd$ LANGUAGE plpgsql;
|
|
491
|
+
CREATE OR REPLACE TRIGGER w3_swap_upd AFTER UPDATE ON "w3_swap" FOR EACH ROW EXECUTE FUNCTION w3_swap_upd();
|
|
492
|
+
"""
|
|
493
|
+
|
|
478
494
|
|
|
479
495
|
class Migration(BaseMigration):
|
|
480
496
|
dependencies: ClassVar = [("models", "0001_initial")]
|
|
@@ -495,4 +511,5 @@ class Migration(BaseMigration):
|
|
|
495
511
|
RunSQL(COND_SIDE_UNIQ_SQL),
|
|
496
512
|
RunSQL(COND_GC_SQL),
|
|
497
513
|
RunSQL(NET_ADDR_TRIGGERS_SQL),
|
|
514
|
+
RunSQL(W3SWAP_SQL),
|
|
498
515
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from tortoise import BaseDBAsyncClient
|
|
2
2
|
|
|
3
|
-
from xync_db.models import Coin, Net
|
|
3
|
+
from xync_db.models import Coin, CoinNet, Net
|
|
4
4
|
|
|
5
5
|
# Сети web3-модуля фронта (ChainId, см. xync-web/src/web3/types.ts) → тикер их нативной монеты.
|
|
6
6
|
# `Net.name` = идентификатор сети во фронте. id=0 — домашняя сеть Xync (как Coin/Ex id=0).
|
|
@@ -17,6 +17,18 @@ NETS: list[tuple[int, str, str]] = [
|
|
|
17
17
|
(9, "btc", "BTC"),
|
|
18
18
|
]
|
|
19
19
|
|
|
20
|
+
# Токены в сетях (CoinNet, `GET /public/coin-nets`, своп web3→фиат): (тикер, сеть, контракт, decimals) — как
|
|
21
|
+
# в xync-pay/src/web3/tokens.ts.
|
|
22
|
+
COIN_NETS: list[tuple[str, str, str, int]] = [
|
|
23
|
+
("USDT", "eth", "0xdAC17F958D2ee523a2206206994597C13D831ec7", 6),
|
|
24
|
+
("USDT", "bsc", "0x55d398326f99059fF775485246999027B3197955", 18),
|
|
25
|
+
("USDT", "polygon", "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", 6),
|
|
26
|
+
("USDT", "arbitrum", "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", 6),
|
|
27
|
+
("USDT", "gram", "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs", 6),
|
|
28
|
+
("USDT", "sol", "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", 6),
|
|
29
|
+
("USDT", "trx", "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", 6),
|
|
30
|
+
]
|
|
31
|
+
|
|
20
32
|
|
|
21
33
|
async def net_seed(db: BaseDBAsyncClient) -> str:
|
|
22
34
|
# ARB/BASE/POL (нативные токены arbitrum/base/polygon, 18 decimals) не заводятся в
|
|
@@ -31,4 +43,9 @@ async def net_seed(db: BaseDBAsyncClient) -> str:
|
|
|
31
43
|
coins = dict(await Coin.all().values_list("ticker", "id"))
|
|
32
44
|
for nid, name, ticker in NETS:
|
|
33
45
|
await Net.update_or_create({"name": name, "native_coin_id": coins[ticker]}, id=nid)
|
|
46
|
+
nets = dict(await Net.all().values_list("name", "id"))
|
|
47
|
+
for ticker, net, contract, decimals in COIN_NETS:
|
|
48
|
+
await CoinNet.update_or_create(
|
|
49
|
+
{"contract": contract, "decimals": decimals}, coin_id=coins[ticker], net_id=nets[net]
|
|
50
|
+
)
|
|
34
51
|
return ""
|
|
@@ -97,6 +97,7 @@ from xync_db.enums import (
|
|
|
97
97
|
TaskType,
|
|
98
98
|
UserStatus,
|
|
99
99
|
W3SendStatus,
|
|
100
|
+
W3SwapStatus,
|
|
100
101
|
)
|
|
101
102
|
from xync_db.enums import (
|
|
102
103
|
TransactionStatus as TS,
|
|
@@ -201,6 +202,9 @@ class CoinNet(Model):
|
|
|
201
202
|
net_id: int
|
|
202
203
|
contract: str | None = CharField(128, null=True)
|
|
203
204
|
decimals: int = UInt1Field()
|
|
205
|
+
# курсор скана входящих на адрес Xync (W3Swap): блок (EVM) / мс (trx) / unix-время (gram) / слот (sol);
|
|
206
|
+
# None — ещё не сканировали (первый скан ставит «сейчас»)
|
|
207
|
+
scanned: int | None = BigIntField(null=True)
|
|
204
208
|
|
|
205
209
|
class Meta:
|
|
206
210
|
table = "coin_net"
|
|
@@ -373,6 +377,64 @@ class W3Send(Model, TsTrait):
|
|
|
373
377
|
table_description = "Outbound web3 send (NetAddr → NetAddr)"
|
|
374
378
|
|
|
375
379
|
|
|
380
|
+
class W3Swap(Model, TsTrait):
|
|
381
|
+
"""Своп web3-монеты в фиат Xync (web3.md): юзер шлёт токен `coin_net` с адреса `frm` на адрес Xync этой сети
|
|
382
|
+
(NetAddr user#0); external ловит входящий перевод (`tx_hash`, `amount` → received), api зачисляет `cur` от
|
|
383
|
+
user#0 по курсу `W3Swap.rates()` (курса нет — по котировке заявки `rate`) → done, `tx`. Перевод без заявки —
|
|
384
|
+
строка без user/cur со статусом manual (разбирает админ)."""
|
|
385
|
+
|
|
386
|
+
user: ForeignKeyNullableRelation[User] = ForeignKeyField("models.User", "w3_swaps", on_update=CASCADE, null=True)
|
|
387
|
+
user_id: int | None
|
|
388
|
+
coin_net: ForeignKeyRelation[CoinNet] = ForeignKeyField("models.CoinNet", "w3_swaps", on_update=CASCADE)
|
|
389
|
+
coin_net_id: int
|
|
390
|
+
cur: ForeignKeyNullableRelation[Cur] = ForeignKeyField("models.Cur", "w3_swaps", on_update=CASCADE, null=True)
|
|
391
|
+
cur_id: int | None
|
|
392
|
+
frm: str = CharField(128) # адрес отправителя, нормализованный (common.chains.norm)
|
|
393
|
+
rate: float | None = FloatField(null=True) # котировка при заявке: сколько cur за 1 монету
|
|
394
|
+
status: W3SwapStatus = IntEnumField(W3SwapStatus, db_default=W3SwapStatus.pending)
|
|
395
|
+
tx_hash: str | None = CharField(128, unique=True, null=True)
|
|
396
|
+
amount: int | None = UInt16Field(null=True) # пришло on-chain, /10^coin_net.decimals (у BSC-USDT 18 знаков)
|
|
397
|
+
tx: ForeignKeyNullableRelation[Transaction] = ForeignKeyField(
|
|
398
|
+
"models.Transaction", "w3_swaps", on_update=CASCADE, null=True
|
|
399
|
+
)
|
|
400
|
+
tx_id: UUID | None
|
|
401
|
+
|
|
402
|
+
class Meta:
|
|
403
|
+
table = "w3_swap"
|
|
404
|
+
table_description = "Web3 coin → Xync fiat swap"
|
|
405
|
+
|
|
406
|
+
@staticmethod
|
|
407
|
+
async def rates() -> dict[tuple[int, int], float]:
|
|
408
|
+
"""{(coin_id, cur_id): сколько cur за 1 монету} — курс свопа монеты в фиат по активным hot-объявам (как их
|
|
409
|
+
видит тейкер), без оглядки на лимиты и объёмы: есть покупные — самая дорогая; нет — зеркало самой дешёвой
|
|
410
|
+
продажной: цена, которую ads_fresh дал бы покупной объяве её слота (`mid·price_k`). Нет объяв — нет свопа."""
|
|
411
|
+
mads = await MyAd.hot_mads_query([]).exclude(Q(hex__isnull=True, ad__maker__ex_id__in=SHARE_HEX_EX_IDS))
|
|
412
|
+
curexs = {
|
|
413
|
+
(cx.ex_id, cx.cur_id): cx
|
|
414
|
+
for cx in await CurEx.filter(
|
|
415
|
+
ex_id__in={m.ad.maker.ex_id for m in mads}, cur_id__in={m.ad.pair_side.pair.cur_id for m in mads}
|
|
416
|
+
).prefetch_related("cur")
|
|
417
|
+
}
|
|
418
|
+
rates: dict[tuple[int, int], float] = {} # сначала — лучшие покупные
|
|
419
|
+
sells: dict[tuple[int, int], tuple[float, MyAd, tuple[int, int]]] = {} # лучшая продажная + её слот
|
|
420
|
+
for m in mads:
|
|
421
|
+
pair = m.ad.pair_side.pair
|
|
422
|
+
cx = curexs[(m.ad.maker.ex_id, pair.cur_id)]
|
|
423
|
+
key, price = (pair.coin_id, pair.cur_id), m.ad.price * 10**-cx.scale
|
|
424
|
+
if not m.ad.pair_side.is_sell:
|
|
425
|
+
rates[key] = max(rates.get(key, 0), price)
|
|
426
|
+
elif (slot := Ad.slot_of(m.ad.min_fiat, m.ad.filtered, cx.minimum)) and (
|
|
427
|
+
key not in sells or price < sells[key][0]
|
|
428
|
+
):
|
|
429
|
+
sells[key] = price, m, slot
|
|
430
|
+
for key, (_, m, (k_min, fltrd)) in sells.items():
|
|
431
|
+
if key not in rates:
|
|
432
|
+
ticker = curexs[(m.ad.maker.ex_id, key[1])].cur.ticker
|
|
433
|
+
k = m.ad.maker.ex.agent_cls().price_k(k_min, bool(fltrd), False, ticker)
|
|
434
|
+
rates[key] = m.ad.pair_side.pair.real() * k
|
|
435
|
+
return rates
|
|
436
|
+
|
|
437
|
+
|
|
376
438
|
class PairEx(Model, TsTrait):
|
|
377
439
|
pair: ForeignKeyRelation[Pair] = ForeignKeyField("models.Pair", "pairexs", on_update=CASCADE)
|
|
378
440
|
pair_id: int
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|