فقط برای مشاهده. همهی باگها و تسکهای باز که هنوز ready نشدهاند، به ترتیب اولویت. برای تأیید یا درخواست تغییر در تلگرام پیام بدهید، مثلاً «#70 تأیید» یا «#70 تغییر: …».
LBank یک صرافی order-book معمولی است که هیچ کدی در 3abot ندارد. برخلاف صرافیهای ایرانی که فقط دو بازار IRT و USDT دارند، فرصت آربیتراژ در LBank بین چند stablecoin است (USDT / USDC / USD1). یعنی «دو quote ثابت» که الان در کل pipeline هاردکد شده (coinfetcher، CryptoData، موتور Go، trade-execution، ResellService) باید به N quote عمومی شود.
دادهی زندهی ۲۰۲۶-۱۰-۰۲ (حدود ۱۲ دقیقه WS روی ۳۷ pair، فقط endpointهای عمومی، جزئیات در docs/reports/epic-70-lbank-plan.html) نشان میدهد که با کارمزد فرضی taker معادل 0.10%، edge بین quoteها خیلی کم است. روی کوینهای پرحجم اختلاف حداکثر حدود 5 bps است و هزینهی دو leg معادل 20 bps. تنها موارد سودده bookهای کمعمق بودند (مثلاً یک سفارش حدود ۱۰۷ دلاری TRX/USD1). پس قبل از هر سفارش واقعی باید معلوم شود در عمل وضعیتمان چطور میبود.
تصمیم مالک (۲۰۲۶-۱۰-۰۵): فاز ۱ همهچیز را میسازد، جز ثبت سفارش واقعی. wallet هم mock است و هیچ notificationی ارسال نمیشود. سیستم مدتی روی دادهی زندهی LBank کار میکند و بعد از روی نتیجه تصمیم میگیریم که فاز ۲ (سفارش واقعی) را انجام بدهیم یا نه.
دو quote در ریشهی سیستم هاردکد شده است:
bun/domain/contracts/IExchangePlugin.ts فقط irtQuotes/usdtQuotes دارد. groupMarkets() در bun/application/coinfetcher.ts فقط baseای را نگه میدارد که هم بازار IRT و هم USDT داشته باشد.CryptoData در هر دو runtime (bun/domain/entities/CryptoData.ts، go/internal/core/types/model.go) فیلدهای ثابت irt/usdt دارد.go/internal/core/engine.go دو worker، دو تابع process و دو تابع calc دارد. state.go هم فیلدهای scalar مخصوص IRT/USDT دارد.TradeExecutionService در حدود ۱۵ جا روی transactionType == "IRT to USDT" شاخه میزند. ResellService دقیقاً دو بازار را مقایسه میکند (irtBid در برابر usdtBidInIrt).DryRunOrderSimulator (bun/infra/exchange/shared/DryRunOrderSimulator.ts) هر سفارش را کامل پر میکند و به orderbook نگاه نمیکند. برای سنجیدن «اگر واقعی بود چه میشد» این کافی نیست.notifyGotify (bun/infra/notification/gotify.ts) از چند جا مستقیم صدا زده میشود (trade-execution، resellService، dollarCrashGuard). خاموش کردن notification باید در همین تابع مشترک انجام شود، نه در تکتک callerها.یک branch به نام fix/70-lbank از origin/main و یک draft PR با checklist زیر. هر بند یک سری commit است. بندهای ۱ تا ۷ refactorهایی هستند که رفتار Wallex/Tabdil/Nobitex/Bitpin/Ramzinex را عیناً حفظ میکنند. gate آنها این است که تستهای فعلی Go و TS با همان fixtureهای قدیمی پاس شوند.
IExchangePlugin فیلدهای quotes: string[] و valuationQuote اضافه میشود. پیشفرض صرافیهای فعلی ["IRT","USDT"] / "IRT" است. groupMarkets بهصورت marketsByQuote برمیگردد و baseای معتبر است که حداقل ۲ quote داشته باشد. precision برابر کمینهی precision همهی quoteها میشود. coinfetcher کلید quotes را فقط وقتی مینویسد که با پیشفرض فرق داشته باشد. این کلید به هر دو فایل key (RedisKeys.ts، keys.go) اضافه میشود.CryptoData.books (½ روز): پیام به شکل {symbol, books:{Q:{bids,asks}}, at:{Q:ms}, lastUpdate} درمیآید. Go در UnmarshalJSON شکل قدیمی irt/usdt را هم میخواند تا وقتی Bun و Go با چند ثانیه فاصله reload میشوند پیامی گم نشود. MarketDataService هر coin را روی همهی quoteها subscribe میکند و وقتی حداقل ۲ book در cache باشد publish میکند.bun/infra/exchange/lbank/، getMarkets() با currencyPairs.do و accuracy.do پیاده میشود. LBankWs روی depth 10 subscribe میکند، به ping سرور pong میدهد و در هر socket حداکثر ۸ pair نگه میدارد (اندازهگیریشده: هر socket حدود 20 msg/s میکشد). reconnect و resubscribe هم دارد. در LBankPlugin مقادیر valuationQuote:"USDT"، currency:"usdt"، hasFairPrice:false و supportsWalletWs:false تنظیم میشوند. LBank در ExchangeRegistry ثبت میشود و در Go، پکیج go/internal/exchange/lbank ساخته و در main.go import میشود. LBank به environments/*.json و pm2Config.ts هم اضافه میشود.balance:{q} (بههمراه :locked) برای هر quote نوشته میشوند و quoteها از balance:coin:* بیرون میروند. قانون balance:delta_at (#5) روی همهی quoteها اعمال میشود. PriceService.getBalance/getRate/getTakerFee/applyWalletDelta(q) اضافه میشود. role مربوط به usdtprice برای LBank کلید rate:{q} = bid(q_usdt)·(1−taker) را هر حدود ۱ ثانیه با TTL مینویسد، تا اگر writer مُرد موتور آن quote را skip کند. fee:taker_{q} هم نوشته میشود.(data, A, B) با W worker و یک تابع calcProfit عمومی جای دو تابع قبلی را میگیرد: profit = vol·(1−f_A)·bid_B·(1−f_B)·rate(B) − vol·ask_A·rate(A). block به شکل trade:block:{a}_to_{b} درمیآید و adapter.MinNotional(q) اضافه میشود. ArbitrageResult فیلدهای buyQuote/sellQuote/buyRate/sellRate/buyAt/sellAt را میگیرد و transactionType به شکل "{A} to {B}" نوشته میشود، پس رشتههای قدیمی دقیقاً همان میمانند. manage.ts blockTrade/unblockTrade هر a_to_b را قبول میکند.buyQuote/sellQuote یک بار در بالای _processTradeInner تعیین میشود و هر شاخهی IRT/USDT با lookup روی quote جایگزین میشود. calcProfit عمومی در profitRevalidation.ts قرار میگیرد و برابریاش با fixtureهای Go تست میشود. ResellSignal فیلد buyQuote را میگیرد.bid_q·(1−f_q)·rate(q) حساب میشود و بیشترین برنده است. یک map از rateها جای usdtIrtCache را میگیرد. entryهایی که قبلاً با "irt"/"usdt" ذخیره شدهاند همچنان load میشوند.paper:wallet:{ASSET} نگه داشته میشود (فقط Bun از آن استفاده میکند و به هر دو فایل key اضافه میشود). مقدار اولیه با manage.ts paperWallet lbank set USDT=1000 USDC=500 ... تنظیم و با paperWallet lbank show دیده میشود. در فاز ۱، LBankExchange.getBalances() همین wallet را برمیگرداند. به این ترتیب walletFetcher → balance:* → Go → trade-execution بدون تغییر و عیناً همان مسیر واقعی کار میکند.
- PaperOrderSimulator: در placeOrder، depth لحظهای REST (/v2/depth.do) خوانده میشود و سطحها با معنای IOC تا قیمت limit پیموده میشوند. خروجی matchedAmount و fee واقعی است، باقی سفارش لغو میشود، و wallet بهصورت اتمیک (INCRBYFLOAT در MULTI) debit/credit میشود. پس latency واقعی و slippage هم در نتیجه دیده میشوند. sellهای ResellService هم از همین مسیر میروند.
- در فاز ۱ کد ثبت سفارش واقعی (create_order.do) اصلاً وجود ندارد. LBankExchange.placeOrder/getOrderStatus/cancelOrder فقط به simulator وصلاند، پس اشتباه در env هم نمیتواند سفارش واقعی ثبت کند. transactionها با dry_run=true در Postgres ثبت میشوند.
- کارمزد: اگر کلید API ست شده باشد (creds:set lbank)، signer (MD5 روی پارامترهای مرتبشده → HmacSHA256) و فقط endpoint خواندنی customer_trade_fee.do پیاده میشوند تا کارمزد واقعی حساب در fee:taker_{q} نوشته شود. در غیر این صورت مقدار config استفاده میشود (پیشفرض 0.001). این مهمترین عدد برای تصمیم فاز ۲ است.notifyGotify: وقتی NOTIFICATIONS_ENABLED=false باشد، فقط console.log میکند و چیزی به Gotify نمیفرستد. در env مربوط به LBank مقدارش false است. صرافیهای دیگر دست نمیخورند.smoke-test.ts اضافه میشود. دستور manage.ts paperReport lbank (یکبار چاپ میکند) اینها را نشان میدهد: تعداد signal، تعداد اجراشده، fill rate، سود و زیان به USDT به تفکیک جهت و coin (از transactions/orders با exchange='lbank' AND dry_run)، و ارزش فعلی paper wallet با rate:{q} در مقایسه با مقدار اولیه. PR بعد از review از حالت draft بیرون میآید. سپس LBank مدتی (پیشنهاد: ۲ هفته) در paper mode اجرا میشود.create_order.do (IOC)، orders_info.do، cancel، mapping کدهای خطا، lbankRateLimits.ts (order 500/10s، بقیه 200/10s)، و user_info_account.do برای wallet واقعی.currency:"usdt"، plugin.unitLabel بهجای حدود ۲۲ برچسب هاردکد «تومان»، و نمایش جهتهایی مثل «USDT → USDC».خارج از scope: rebalance خودکار stablecoin به USDT (چون rate خالص آن را قیمتگذاری کرده است)، quoteهای غیر stable (BTC/ETH/BRL)، مثلث اتمیک ۳ leg در یک signal، و private WS لبانک.
MinimumProfit().api.lbank.info در دسترس است (Q7).engine_test.go، state_test.go) و TS با همان اعداد مورد انتظار پاس میشوند. هیچ کلید Redis قدیمی تغییر نام نمیدهد.pm2build_local lbank)، Go برای هر coin حداقل ۲ quote دریافت میکند و lag کمتر از ۲ ثانیه است.PaperOrderSimulator و روی depth لحظهای پر میشود و paper:wallet:USDT/paper:wallet:USDC درست تغییر میکنند. transaction با dry_run=true در Postgres ثبت میشود.create_order.do یا cancel_order وجود ندارد (grep).NOTIFICATIONS_ENABLED=false هیچ درخواستی به Gotify نمیرود (تست).manage.ts paperReport lbank سود و زیان، fill rate و ارزش wallet را به USDT چاپ میکند.groupMarkets: حالت قدیمی و حالت ۳ quote.CryptoData در Go: شکل جدید و شکل قدیمی.calcProfit در Go: برابر با calcIRTtoUSDT/calcUSDTtoIRT روی همهی fixtureهای موجود. موارد quote بدون موجودی (job ساخته نمیشود)، rate ناموجود (skip) و block usdc_to_usdt (فقط همان جهت skip میشود).calcProfit در profitRevalidation.test.ts.TradeExecutionService: جریانهای USDT→USDC و USDC→USD1 با MockExchangeRest.ResellService: انتخاب بین ۳ بازار، از جمله حالتی که bid خام بالاتر بعد از fee/rate میبازد، و load شدن entry قدیمی.LBankWs: parser برای depth و ping، و sharding (۱۷ pair → ۳ socket). getMarkets روی JSON ضبطشده.PaperOrderSimulator: پر شدن چندسطحی تا قیمت limit، پر شدن جزئی IOC، صفر fill، کسر fee، و debit/credit اتمیک wallet.customer_trade_fee.do.notifyGotify، و فرمول paperReport روی دادهی ساختگی.Estimate: 1 week+
L. فاز ۱ حدود ۱۱ تا ۱۴ روز کاری است. مسیر بحرانی ۱ → ۲ → ۵ → ۶ → ۷ است. این کار money path (trade-execution، ResellService)، قرارداد Bun↔Go (CryptoData، ArbitrageResult) و کلیدهای Redis (quotes، balance:{q}، fee:*_{q}، rate:{q}، paper:wallet:*، trade:block:{a}_to_{b}) را تغییر میدهد. ریسک اصلی پسرفت در صرافیهای فعلی است که با gate «fixtureهای قدیمی دستنخورده» کنترل میشود. در LBank فاز ۱ ریسک مالی صفر است، چون کد سفارش واقعی وجود ندارد.
P1
A normal order-book exchange; implement the required REST and WebSocket integration for trading. Needs authentication, token/API key handling, pair mapping, balance management, order placement, and order status tracking.
Documentation: https://www.lbank.com/docs/#introduction
برای تأیید یا تغییر، در تلگرام پیام بدهید. · مشاهده در GitHub
Wallex's docs (docs/Wallex.md, changelog line 5) now cap order placement, POST /v1/account/orders, at 20 requests / 10 s (it used to be 10 req/s). WallexPlugin.getRateLimits() (bun/infra/exchange/wallex/WallexPlugin.ts:74) returns {}, so RateLimiter.acquire('order_add', …) is a no-op on Wallex. Trade-execution and resell-service (two processes, one account) place orders with no pacing against that cap.
Prod, last 7 days (exchange_requests, Wallex):
So Wallex isn't visibly enforcing the 20/10 s yet. This is defensive: if they start enforcing it, a burst over 20 would get orders rejected mid-trade, for example a buy that fills followed by a rejected sell leg.
WallexPlugin.getRateLimits() returns {}. Every other exchange with documented caps already has a <exchange>RateLimits.ts (Nobitex, Bitpin, Ramzinex). Trade-execution and ResellService already call acquire('order_add' | 'order_status' | 'order_cancel') on every Wallex order, so only the config is missing.
bun/infra/exchange/wallex/wallexRateLimits.ts, same shape as bitpinRateLimits.ts, with a comment quoting the doc:
ts
export const WALLEX_RATE_LIMITS: Record<string, RateLimitBucketConfig> = {
order_add: { limit: 20, windowMs: 10_000, reservePerPendingCoin: 4 },
}
Why reservePerPendingCoin: 4:order_status / order_cancel: no bucket. The docs only give the global 100 req/s. Prod status+cancel traffic is 135,499 per 7 days (~0.2/s average), nowhere near it. Leaving them out keeps acquire() a no-op there (YAGNI). Add a bucket only if 429s ever appear.
3. WallexPlugin.getRateLimits() returns WALLEX_RATE_LIMITS.
4. Docs: update CLAUDE.md "Order Rate Limiting" (l.371 says "Wallex/Tabdil return {}"), so Tabdil is the only no-op one left, and add the Wallex numbers.
5. Out of scope: Tabdil limits, and changing RateLimiter itself.bun/tests/ts/infra/WallexPlugin.test.ts: getRateLimits() returns order_add { limit: 20, windowMs: 10_000, reservePerPendingCoin: 4 } and no order_status/order_cancel buckets.low acquires within 10 s succeed and the 21st waits; with 1 pending resell coin, low gets 16 and high still gets 20.exchange_requests, and trade-execution's added wait is visible only in those bursts (log [RateLimiter] waiting, if logged).WallexPlugin.test.ts (new cases) and bun/tests/ts/services/RateLimiter.test.ts, or the existing shared limiter test, using the Wallex config. No real Redis or exchange.
Estimate: ≤ 2h
S. Config only, on a path every other exchange already runs. The cost: in burst windows (about 53 a week with ≥ 15, a few above 20) trade-execution waits up to a few seconds instead of sending immediately, which can slightly delay those trades. Today's real risk is low, since Wallex currently accepts 31/10 s. No Redis key or Bun/Go change; the ratelimit:wallex:* keys follow the existing pattern.
هنوز برنامه ندارد. برای refine شدن #31 در تلگرام بگویید. · مشاهده در GitHub
P2
Develop a centralized graphical dashboard for monitoring the operational status, performance, profitability, and key trading controls of each bot.
The dashboard should provide both real-time operational controls and historical performance metrics, allowing the team to monitor the bot without relying on raw production logs.
Dashboard Components
Expected Result
A single dashboard should provide a clear overview of the bot's current operational state, system health, trading activity, and profitability, while exposing the main operational controls required for day-to-day bot management.
The dashboard should prioritize quick visibility of abnormal states and key performance indicators, rather than requiring the operator to inspect production logs or individual services manually.
هنوز برنامه ندارد. برای refine شدن #52 در تلگرام بگویید. · مشاهده در GitHub
P2
Implement support for Wallex Credit Accounts as a separate account from the main account. The system should be able to handle trades on both accounts simultaneously. Coins that are not supported by the credit account should automatically be traded through the main account. If the credit account reaches its rate limit, new trades should be routed through the main account until the credit account is available again. Documentation: https://developers.wallex.ir/docs/trade-credit-intro
هنوز برنامه ندارد. برای refine شدن #73 در تلگرام بگویید. · مشاهده در GitHub
1,022 OverValueOrder responses (61% of 1,336 order-adds on 09-21). This is the known 20×
internal retry. Nobitex has had no trades since 09-23 06:43 and its logs stop at 09-23 10:13.
Check whether nobitex was stopped on purpose.
Priority: P1 · Area: Bitpin / Nobitex · From bugs.md (prod logs + DB, 2026-09-27)
هنوز برنامه ندارد. برای refine شدن #19 در تلگرام بگویید. · مشاهده در GitHub
Wallex REST errors print the whole AxiosError (request config, sockets, [Symbol(...)],
~70 lines each; wallex-resell-service-error.log is 39k lines). No credential values were
found, but auth header names appear, so a future change could leak them. Log only
status, data, method url.
Priority: P3 · Area: Logging hygiene · From bugs.md (prod logs + DB, 2026-09-27)
هنوز برنامه ندارد. برای refine شدن #26 در تلگرام بگویید. · مشاهده در GitHub
...×tamp=N&signature=<hex> appears in error logs. It's low risk (per-request), but strip
the query string.
Priority: P3 · Area: Logging hygiene · From bugs.md (prod logs + DB, 2026-09-27)
هنوز برنامه ندارد. برای refine شدن #27 در تلگرام بگویید. · مشاهده در GitHub
Rows with identical (exchange, created_at, endpoint, request) in the last 2 days: wallex 2,694,
bitpin 2,606, tabdil 1,299. The same request is logged twice. Also Wallex 422 rows store
response_summary.error = generic Axios text, while the real reason is only in raw_payload.data.
Put the exchange's message in the summary.
Priority: P3 · Area: Logging hygiene · From bugs.md (prod logs + DB, 2026-09-27)
هنوز برنامه ندارد. برای refine شدن #28 در تلگرام بگویید. · مشاهده در GitHub
*-arbitrage-check-error*.log is almost entirely [StateManager] PubSub received on
config:X: fees_updated/thresholds_updated, about 430k lines per exchange. So coinfetcher
publishes config:* far more often than every 5 min, or every instance logs each one.
Lower it to debug, and check the publish rate (each one triggers a full syncSlow).
Priority: P3 · Area: Logging hygiene · From bugs.md (prod logs + DB, 2026-09-27)
هنوز برنامه ندارد. برای refine شدن #29 در تلگرام بگویید. · مشاهده در GitHub
Every marketdata/arbitrage-check restart creates a new -<pid>.log file that's never cleaned up.
Tune pm2-logrotate retain and delete orphaned per-pid files.
Priority: P3 · Area: Logging hygiene · From bugs.md (prod logs + DB, 2026-09-27)
هنوز برنامه ندارد. برای refine شدن #30 در تلگرام بگویید. · مشاهده در GitHub
W2 — Remove the undocumented /v1/account/profile fallback in getStreamKey().
/v1/account/profile is gone from the new docs. It's also dead code: walletFetcher only
calls subscribeWallet() when supportsWalletWs is true, which requires
WALLEX_STREAM_KEY to be set, and getStreamKey() returns that env var first, so the
fallback is never reached. Make the env var the only source (throw if unset), drop the
profile call, and update the doc comments in WallexExchange.getStreamKey()/
WallexPlugin.supportsWalletWs plus CLAUDE.md's Wallet Balance table row ("checked every
endpoint, including /v1/account/profile").
Priority: P3 · Area: Wallex · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #32 در تلگرام بگویید. · مشاهده در GitHub
W5 (optional) — new private channel STREAMKEY@tradeDetails pushes per-fill price, qty,
fee and isMaker. Could replace getOrderStatus() polling for fills later. Not needed now.
Priority: P3 · Area: Wallex · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #35 در تلگرام بگویید. · مشاهده در GitHub
A — Abantether (poor fit: OTC broker, not an order book). Decide before starting.
No order book at all: GET /api/v1/manager/otc/ticker (and WS
wss://ws.abantether.com/public, channel coins_list_v2) only give one buy_price /
sell_price + buy_max / sell_max per coin, in IRT and USDT markets.
The Go engine and trade-execution assume real order-book depth (pickAsks/pickBids,
revalidation against the live book). Abantether would need a synthetic 1-level book
(ask = buy_price, bid = sell_price, volume = _max). The spread is the broker's margin, so
arbitrage opportunities inside* Abantether (IRT ↔ USDT) are likely rare. Its value is
mostly as a cross-exchange leg, which this bot doesn't do.
Orders: POST /api/v1/order_handler/orders/otc/market (buy volume is in quote
currency, sell volume in base), .../otc/limit, status GET .../otc/{id}/, cancel
POST .../otc/{id}/cancel. Order ids are strings.
Auth: static Authorization: Token <key> (never expires), so the credential shape is the
simple token, like Nobitex classic. Balances: GET /api/v1/accounting/balances/?type=spot.
Fees/min/max: GET /api/v1/feecalculator/coin-info/?symbol=&side=.
No OHLC → use the public Wallex OHLC fallback like Tabdil. No private WS → REST-only wallet. No rate limits documented.
Recommendation: do Ramzinex first. Only build Abantether if you actually want the OTC-price arbitrage it allows, since that needs a design decision on the synthetic book (Bun-side normalization only; Go stays pass-through).
Docs: docs/abantether.md
Priority: P3 · Area: New exchange · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #38 در تلگرام بگویید. · مشاهده در GitHub
Nobitex's axios client timeout (NobitexExchange.ts, timeout: 2000) is aggressive —
26 ETIMEDOUT errors across just 2 days of logs. Now recoverable (this session's
clientOrderId fix), so no longer money-risking, but still worth raising to reduce needless
recovery-lookup traffic against Nobitex's own rate limits.
Priority: P2 · Area: Nobitex · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #40 در تلگرام بگویید. · مشاهده در GitHub
CPU usage history / RAM usage history / service health graph has no data source at all.
Confirmed by search — no collector, no table, anywhere in this codebase. This is a real gap
in the dashboard requirements handed down for this task, unrelated to retention (nothing to
retain if nothing is ever collected). Flagging for whoever scopes the dashboard build; when
it lands, its table needs a deliberate retention decision, not a silent default either way.
See docs/db-retention.md's dependency-audit table.
Priority: P3 · Area: Observability · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #41 در تلگرام بگویید. · مشاهده در GitHub
ResellConfig.minProfitIrt is a flat threshold that doesn't decay as an entry's deadline
approaches — could miss a brief "good enough" window right before deadline-driven manual
handoff. (See CLAUDE.md's own "Suggestions" section.)
Priority: P3 · Area: Resell · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #42 در تلگرام بگویید. · مشاهده در GitHub
No cool-down/backoff between ResellService.executeSell() attempts on the same entry —
every orderbook tick can trigger a fresh attempt immediately after a failed/partial one.
Priority: P3 · Area: Resell · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #43 در تلگرام بگویید. · مشاهده در GitHub
No priority/fairness ordering across multiple resell entries sharing one symbol
(resell:index:{SYMBOL} is a Redis SET; iteration order isn't deliberate).
Priority: P3 · Area: Resell · Migrated from todo.md (2026-09-27)
هنوز برنامه ندارد. برای refine شدن #44 در تلگرام بگویید. · مشاهده در GitHub