معماری حسابداری بروکر A-Book با TigerBeetle
نام سند: Broker Accounting Architecture
زبان مرجع: فارسی
نسخه سند:0.021
وضعیت:Architecture Baseline / local ops console + named commands
آخرین بروزرسانی:2026-09-12
دامنه: A-Book Derivatives + Real Spot + On-Chain Deposit/Withdrawal + Custody + Venue/LP Reconciliation
Financial Source of Truth: TigerBeetle
Operational Source of Truth: PostgreSQL
0. قرارداد اجباری برای Agent توسعهدهنده
این فایل فقط یک توضیح معماری نیست. این فایل Specification زنده پروژه و مرجع تصمیمهای معماری و حسابداری است.
توضیح کوتاه اصطلاحات کنسول و مرحلهها (custody، lock/broadcast/confirm، …) در BROKER_ACCOUNTING_GLOSSARY_FA.md است. آن فایل سیاست تازه نمیسازد؛ اگر تعارض بود همین سند مقدم است.
Agent توسعهدهنده موظف است قبل از هر تغییر مرتبط با حسابداری، معاملات، پوزیشن، واریز/برداشت، کیف پول، LP/Venue، reconciliation یا مدل داده، این فایل را بخواند.
0.1. قانون Document-First
ترتیب هر تغییر باید دقیقاً به شکل زیر باشد:
- نیاز یا تغییر مورد نظر مشخص شود.
- بخش یا بخشهای متاثر در همین سند مشخص شوند.
- قبل از تغییر کد، سند به وضعیت جدید بروزرسانی شود.
- نسخه سند افزایش پیدا کند.
- در Change Log سراسری، تغییر ثبت شود.
- در Change History محلی بخش مربوطه نیز تغییر ثبت شود.
- سپس کد نوشته یا تغییر داده شود.
- تستهای مرتبط نوشته یا بروزرسانی شوند.
- در پایان بررسی شود که implementation دقیقاً با سند مطابقت دارد.
- اگر implementation با طراحی مستندشده متفاوت شد، ابتدا سند اصلاح شود و بعد task نهایی شود.
تغییر رفتار سیستم بدون بروزرسانی این سند مجاز نیست.
0.2. Versioning
نسخه اولیه:
0.001
نسخهها در فاز اولیه به صورت افزایشی نگهداری میشوند:
0.001
0.002
0.003
...
0.010
...
هر تغییر معماری، رفتار مالی، schema، state-machine، invariant، settlement rule یا integration contract باید نسخه سند را افزایش دهد.
تغییرات صرفاً cosmetic که هیچ اثری روی رفتار سیستم ندارند الزامی به افزایش نسخه ندارند.
0.3. Versioning هر بخش
هر بخش اصلی باید metadata زیر را داشته باشد:
Introduced: 0.001
Last Changed: 0.001
Status: Designed | Implementing | Implemented | Deprecated
نمونه:
Position Accounting
Introduced: 0.001
Last Changed: 0.003
Status: Implemented
و پایین همان بخش:
### Change History — Position Accounting
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف اولیه مدل Position و Margin |
| 0.003 | 2026-09-20 | Unrealized PnL از Ledger حذف و فقط Realized PnL settle شد |
Agent حق حذف history قبلی را ندارد. History باید append-only باشد.
0.4. زبان Documentation
تمام توضیحات معماری، reasoning، flowها، changelog و تصمیمات باید به فارسی نوشته شوند.
نامهای فنی، identifierها، enumها، table nameها، API nameها، class nameها و code snippetها باید انگلیسی باقی بمانند.
مثال:
موجودی قابل استفاده کاربر در Account Type با نام USER_AVAILABLE نگهداری میشود.
0.5. ثبت دلیل تغییر
هر تغییر غیر trivial باید مشخص کند:
- چه چیزی تغییر کرد؟
- چرا تغییر کرد؟
- چه بخشهایی متاثر شدند؟
- migration لازم است یا خیر؟
- backward compatibility دارد یا خیر؟
- چه تستهایی اضافه یا اصلاح شدند؟
- آیا reconciliation rule تغییر کرده است؟
- آیا رفتار مالی کاربر تغییر کرده است؟
0.6. ممنوعیت تغییر تاریخچه مالی
در TigerBeetle:
- Account مالی موجود حذف نمیشود.
- Transfer مالی موجود حذف نمیشود.
- Transfer مالی موجود edit نمیشود.
- برای اصلاح اشتباه، Correction/Reversal Transfer جدید ثبت میشود.
- هر correction باید
reason,operator/service,ticket/referenceوoriginal_event_idداشته باشد.
1. هدف سیستم
Introduced: 0.001
Last Changed: 0.001
Status: Designed
هدف، ساخت لایه حسابداری یک Broker است که موارد زیر را پشتیبانی کند:
- حساب چنددارایی برای هر User
- معاملات Spot واقعی
- معاملات Leveraged / Derivatives به صورت A-Book
- Margin و Collateral
- Realized PnL
- Funding
- Trading Fee
- Spread
- Execution Fee
- Deposit روی Blockchain
- Withdrawal روی Blockchain
- Internal Transfer
- Hot / Warm / Cold Wallet
- انتقال سرمایه بین Broker و Venue/LP
- چند Venue/LP
- چند Network
- Reconciliation با Blockchain
- Reconciliation با Venue/LP
- Idempotent financial operations
- Audit trail کامل
1.1. اصل کلیدی
TigerBeetle جواب این سؤال را میدهد:
چه کسی مالک/طلبکار چه مقدار ارزش است و این ارزش چگونه جابهجا شده است؟
PostgreSQL جواب این سؤالها را میدهد:
Order چیست؟
Position چیست؟
Fill چیست؟
TxHash چیست؟
Hedge روی کدام Venue انجام شد؟
Position با چه Entry Price باز شده است؟
بنابراین:
Money / Asset / Financial Settlement
↓
TigerBeetle
Order / Position / Fill / Blockchain Tx / Venue Execution
↓
PostgreSQL
2. مفروضات نسخه 0.001
Introduced: 0.001
Last Changed: 0.001
Status: Designed
نسخه اولیه بر اساس این assumptions ساخته میشود:
Trading
- Derivatives به صورت A-Book هستند.
- Broker exposure کاربر را روی یک یا چند LP/Venue hedge میکند.
- معماری نباید فرض کند هر Client Fill دقیقاً یک Hedge Fill دارد.
- سیستم باید در آینده Net Hedging و Aggregate Hedging را نیز پشتیبانی کند.
- Spot واقعی است؛ کاربر بعد از خرید Spot واقعاً entitlement آن Asset را در Ledger دریافت میکند.
Custody
- دارایی کاربران میتواند به صورت pooled custody نگهداری شود.
- User الزاماً wallet اختصاصی on-chain با موجودی فیزیکی جداگانه ندارد.
- Ledger مالکیت اقتصادی User را نگهداری میکند.
- Blockchain محل custody واقعی Asset را نگهداری میکند.
Database
- TigerBeetle = Financial Source of Truth
- PostgreSQL = Business/Operational State
- PostgreSQL balance نباید مرجع نهایی حسابداری باشد.
- Cached balance در PostgreSQL فقط projection/cache است.
Amount
- هیچ مقدار مالی با
floatیاdoubleذخیره نمیشود. - تمام amountها integer هستند.
- Scale هر Asset در Asset Registry تعریف میشود.
مثال:
BTC:
scale = 8
1 BTC = 100_000_000 units
USDT:
scale = 6
1 USDT = 1_000_000 units
3. معماری سطح بالا
Introduced: 0.001
Last Changed: 0.001
Status: Designed
┌──────────────────┐
│ User │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Broker API │
└────────┬─────────┘
│
┌──────────────────────────────┼──────────────────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Order / OMS │ │ Wallet Service │ │ Accounting Svc │
└───────┬────────┘ └───────┬────────┘ └───────┬────────┘
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Position/Risk │ │ Blockchain │ │ TigerBeetle │
└───────┬────────┘ │ Integration │ │ Ledger │
│ └────────────────┘ └────────────────┘
▼
┌────────────────┐
│ Execution / │
│ Hedge Service │
└───────┬────────┘
│
▼
┌────────────────┐
│ LP / Exchange │
│ Binance/... │
└────────────────┘
┌────────────────────────────┐
│ PostgreSQL │
│ │
│ orders │
│ fills │
│ positions │
│ hedge_orders │
│ hedge_fills │
│ hedge_allocations │
│ deposits │
│ withdrawals │
│ blockchain_transactions │
│ accounting_events │
│ reconciliation_runs │
└────────────────────────────┘
4. تفکیک مسئولیت سرویسها
Introduced: 0.001
Last Changed: 0.001
Status: Designed
4.1. Accounting Service
تنها سرویس مجاز برای ایجاد Financial Transfer در TigerBeetle.
وظایف:
- Account provisioning
- Balance query
- Spot lock/release
- Spot settlement
- Margin lock/release
- Client Realized PnL
- Hedge Realized PnL
- Funding
- Trading Fee
- Deposit credit
- Withdrawal lock/settlement
- Venue funding
- Custody movement
- Correction
- Reconciliation support
هیچ سرویس دیگری نباید مستقیم TigerBeetle Transfer ایجاد کند.
4.2. Order Management System — OMS
مسئول:
- Order lifecycle
- Limit / Market order
- Cancel
- Partial Fill
- Fill aggregation
OMS مالک balance نیست.
4.3. Position / Risk Service
مسئول:
- Open Position
- Close Position
- Position quantity
- Entry Price
- Mark Price
- Leverage
- Initial Margin
- Maintenance Margin
- Unrealized PnL
- Liquidation eligibility
- Exposure
Unrealized PnL در TigerBeetle ثبت نمیشود.
4.4. Execution / Hedge Service
مسئول:
- ارسال Order به LP/Venue
- دریافت Venue Fill
- Hedge execution
- Fee/Commission دریافتشده از Venue
- Hedge Position tracking
- Retry / deduplication
4.5. Wallet Service
مسئول:
- Address allocation
- Deposit detection
- Confirmation tracking
- Withdrawal lifecycle
- Transaction construction
- Broadcast
- Replacement transaction / RBF
- Network fee
- Wallet sweeping
Private Key نباید در Accounting Service یا API Service نگهداری شود.
5. مدل Ledger در TigerBeetle
Introduced: 0.001
Last Changed: 0.002
Status: Designed / Pre-Provisioning Baseline
هر Asset یک ledger مجزا دارد. برای جلوگیری از اشتباه انسانی بین ledger و account code، فضای عددی ledger از Account Codeها جدا نگه داشته میشود.
Registry اولیه:
10_001 = USDT
10_002 = USDC
20_001 = BTC
20_002 = ETH
مقادیر واقعی از assets.tigerbeetle_ledger_id خوانده میشوند و نباید در business code به شکل پراکنده hard-code شوند.
5.1. قانون Ledger
Transfer معمولی TigerBeetle فقط بین دو Account روی یک ledger یکسان انجام میشود.
برای Exchange بین دو Asset، چند Transfer روی ledgerهای متفاوت ساخته میشود. اگر این legها یک accounting operation واحد هستند، باید در همان create_transfers request به صورت یک linked chain پیوسته ارسال شوند.
5.2. Asset Scale یک تصمیم immutable است
Scale هر Asset باید قبل از اولین Account/Transfer آن ledger نهایی شود.
Baseline اولیه:
USDT scale = 6
USDC scale = 6
BTC scale = 8
ETH scale = 18
قواعد:
- amount مالی همیشه integer است.
- محاسبات مالی با float/double ممنوع است.
- تغییر scale یک ledger موجود مجاز نیست.
- اگر scale در آینده تغییر کند، باید
ledgerجدید ساخته و Account/Transferهای لازم migrate شوند. assets.scaleبخشی از contract مالی immutable پروژه محسوب میشود.
Change History — Ledger & Scale
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف اولیه ledger-per-asset |
| 0.002 | 2026-09-12 | جداسازی فضای Ledger ID از Account Code و تثبیت scaleهای اولیه |
6. Asset Registry
Introduced: 0.001
Last Changed: 0.002
Status: Designed / Pre-Provisioning Baseline
PostgreSQL باید Asset Registry داشته باشد.
نمونه:
assets
id
symbol
tigerbeetle_ledger_id
scale
status
economic_asset_group
created_at
updated_at
نمونه داده:
BTC ledger=20_001 scale=8
USDT ledger=10_001 scale=6
USDC ledger=10_002 scale=6
ETH ledger=20_002 scale=18
6.1. Network با Asset یکی نیست
مثلاً:
USDT ERC20
USDT TRC20
اگر از نظر business balance داخلی fungible باشد:
Ledger = USDT
ولی custody accountهای جدا دارند:
CUSTODY_USDT_ETHEREUM
CUSTODY_USDT_TRON
اما:
BTC != WBTC
WBTC باید Asset/Ledger جدا داشته باشد مگر تصمیم معماری مستند دیگری گرفته شود.
7. طبقهبندی Accountها
Introduced: 0.001
Last Changed: 0.003
Status: Designed / Pre-Provisioning Baseline
7.1. Liability Accounts — بدهی Broker به User
برای هر User و Asset:
USER_AVAILABLE
USER_SPOT_LOCKED
USER_MARGIN
USER_WITHDRAWAL_LOCKED
Balance اقتصادی Liability:
balance = credits_posted - debits_posted
Constraint:
flags.debits_must_not_exceed_credits = true
7.2. Broker Asset Accounts
CUSTODY_HOT
CUSTODY_WARM
CUSTODY_COLD
CUSTODY_DEPOSIT
ONCHAIN_OUTBOUND_IN_FLIGHT
VENUE_ASSET
VENUE_IN_TRANSIT
CLIENT_BAD_DEBT_RECEIVABLE
Balance اقتصادی Asset:
balance = debits_posted - credits_posted
برای Asset Accountهایی که نباید منفی شوند:
flags.credits_must_not_exceed_debits = true
CLIENT_BAD_DEBT_RECEIVABLE یک Asset/Receivable است و فقط از مسیر Liquidation/Bad Debt ایجاد میشود.
7.3. Revenue Accounts
TRADING_FEE_REVENUE
SPREAD_REVENUE
WITHDRAWAL_FEE_REVENUE
CLIENT_FUNDING_REVENUE
VENUE_FUNDING_REVENUE
CLIENT_LOSS_REVENUE
HEDGE_PNL_REVENUE
LIQUIDATION_FEE_REVENUE
Revenue به صورت credit-balance مدل میشود.
7.4. Expense Accounts
NETWORK_FEE_EXPENSE
VENUE_EXECUTION_FEE_EXPENSE
CLIENT_PROFIT_EXPENSE
HEDGE_PNL_EXPENSE
CLIENT_FUNDING_EXPENSE
VENUE_FUNDING_EXPENSE
BAD_DEBT_EXPENSE
SPOT_PRICE_IMPROVEMENT_EXPENSE
Expense به صورت debit-balance مدل میشود.
7.5. Clearing / Exception Accounts
SPOT_SETTLEMENT_CLEARING
DEPOSIT_SUSPENSE
WITHDRAWAL_SUSPENSE
RECONCILIATION_SUSPENSE
CHAIN_REORG_RECEIVABLE
MANUAL_ADJUSTMENT_CLEARING
SPOT_SETTLEMENT_CLEARING برای هر Asset ساخته میشود و client settlement را از venue execution جدا میکند.
این حساب عمداً میتواند موقتاً debit یا credit balance داشته باشد و نباید flag محدودکنندهی debits_must_not_exceed_credits یا credits_must_not_exceed_debits داشته باشد.
Target steady-state:
SPOT_SETTLEMENT_CLEARING balance = 0
هر balance غیر صفر خارج از settlement SLA باید alert ایجاد کند.
این alert یک کنترل عملیاتی سخت است، نه صرفاً observability. در Production، عبور clearing unexplained از hard threshold زمانی یا مقداری باید circuit breaker مربوط به Spot را فعال کند؛ settlement/reconciliation/correction ادامه پیدا میکنند ولی ایجاد Client Spot Settlement جدید برای دامنه متاثر متوقف میشود تا exposure توضیح داده یا با override حسابرسیشده رفع شود.
7.6. Broker Equity / Reserve Accounts
INSURANCE_FUND_RESERVE
BROKER_SPOT_INVENTORY_RESERVE
هر دو credit-balance reserve/equity هستند.
INSURANCE_FUND_RESERVE برای جذب loss طبق Risk Specification استفاده میشود. مدل مصرف و replenishment آن قبل از فعالشدن liquidation production باید نهایی شود.
BROKER_SPOT_INVENTORY_RESERVE برای هر Asset، مالکیت اقتصادی خود Broker روی inventory آن Asset را نمایش میدهد و location custody نیست. محل فیزیکی Asset همچنان در CUSTODY_* یا VENUE_ASSET نگهداری میشود.
Seed کردن inventory متعلق به Broker به صورت مفهومی:
Debit: CUSTODY_HOT_BTC
Credit: BROKER_SPOT_INVENTORY_RESERVE_BTC
در نتیجه Asset فیزیکی و Equity/Inventory اقتصادی Broker هر دو قابل ردیابیاند. Internal Spot Fill میتواند این reserve را مصرف یا افزایش دهد بدون اینکه Venue جعلی ساخته شود.
Change History — Account Classification
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف اولیه User/Broker/Revenue/Expense/Clearing |
| 0.002 | 2026-09-12 | افزودن Spot Clearing، Funding accounts، Liquidation Fee، Bad Debt و یکسانسازی HEDGE_PNL naming |
| 0.003 | 2026-09-12 | افزودن Broker Spot Inventory Reserve و تبدیل Spot Clearing alert به hard operational control |
8. Account Code Registry
Introduced: 0.001
Last Changed: 0.003
Status: Frozen for Initial Provisioning
Account Codeها از v0.002 به بعد append-only هستند. تغییر معنای code موجود ممنوع است.
# User liabilities
1001 USER_AVAILABLE
1002 USER_SPOT_LOCKED
1003 USER_MARGIN
1004 USER_WITHDRAWAL_LOCKED
# Broker assets
2001 CUSTODY_HOT
2002 CUSTODY_WARM
2003 CUSTODY_COLD
2004 CUSTODY_DEPOSIT
2005 ONCHAIN_OUTBOUND_IN_FLIGHT
2006 CLIENT_BAD_DEBT_RECEIVABLE
2101 VENUE_ASSET
2102 VENUE_IN_TRANSIT
# Revenue / income
3001 TRADING_FEE_REVENUE
3002 SPREAD_REVENUE
3003 WITHDRAWAL_FEE_REVENUE
3004 CLIENT_FUNDING_REVENUE
3005 CLIENT_LOSS_REVENUE
3006 HEDGE_PNL_REVENUE
3007 VENUE_FUNDING_REVENUE
3008 LIQUIDATION_FEE_REVENUE
# Expenses
4001 NETWORK_FEE_EXPENSE
4002 VENUE_EXECUTION_FEE_EXPENSE
4003 CLIENT_PROFIT_EXPENSE
4004 HEDGE_PNL_EXPENSE
4005 CLIENT_FUNDING_EXPENSE
4006 VENUE_FUNDING_EXPENSE
4007 BAD_DEBT_EXPENSE
4008 SPOT_PRICE_IMPROVEMENT_EXPENSE
# Clearing / exception
5001 DEPOSIT_SUSPENSE
5002 WITHDRAWAL_SUSPENSE
5003 RECONCILIATION_SUSPENSE
5004 CHAIN_REORG_RECEIVABLE
5005 MANUAL_ADJUSTMENT_CLEARING
5006 SPOT_SETTLEMENT_CLEARING
# Equity / reserve
6001 INSURANCE_FUND_RESERVE
6002 BROKER_SPOT_INVENTORY_RESERVE
Naming Rule
نام canonical این پروژه:
HEDGE_PNL_REVENUE
HEDGE_PNL_EXPENSE
نام HEDGE_LOSS_EXPENSE از v0.002 deprecated است و نباید در code جدید استفاده شود.
Change History — Account Codes
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Registry اولیه |
| 0.002 | 2026-09-12 | تکمیل Funding/Liquidation/Bad-Debt/Spot-Clearing accounts و رفع naming inconsistency |
| 0.003 | 2026-09-12 | افزودن BROKER_SPOT_INVENTORY_RESERVE برای internal inventory spot fills |
9. TigerBeetle Account Provisioning & Immutable Mapping
Introduced: 0.001
Last Changed: 0.006
Status: Frozen for Initial Provisioning
Accountهای TigerBeetle بعد از creation از نظر schema fields immutable هستند؛ بنابراین موارد این بخش قبل از اولین create_accounts قرارداد محسوب میشوند.
9.1. Account Fields
id
ledger
code
flags
user_data_128
user_data_64
user_data_32
Mapping:
id
= TigerBeetle time-based u128 ID
= generated once and persisted in accounting_accounts before create_accounts
ledger
= immutable asset ledger from Asset Registry
code
= immutable Account Code Registry
user_data_128
= canonical owning/related entity ID
User Account -> user_id_u128
Venue Account -> venue_id_u128
Custody Account -> custody_wallet_group_id_u128
System Account -> stable system_entity_id_u128
user_data_64
= 0 in schema v0.002
user_data_32
= 0 in schema v0.002
استفاده از user_data_64 یا user_data_32 در آینده نیازمند account migration برای Accountهای قدیمی است؛ Agent حق ندارد معنای پنهانی به این فیلدها اضافه کند.
9.2. flags.history اجباری است
تمام Accountهای production این پروژه باید در زمان creation دارای:
flags.history = true
باشند.
دلیل:
- Historical balance برای audit و reconciliation لازم است.
get_account_balancesفقط برای Accountهایی نتیجه میدهد که از ابتدا باhistoryساخته شده باشند.- این flag بعد از creation قابل اضافهکردن نیست.
Constraint flags بر اساس نوع Account علاوه بر history تنظیم میشوند:
User Liability:
history
debits_must_not_exceed_credits
Non-negative Broker Asset:
history
credits_must_not_exceed_debits
Clearing / Revenue / Expense / Reserve:
history
+ constraint only if explicitly defined by account type
9.3. flags.closed و Freeze
flags.closed در TigerBeetle انتقالهای جدید را رد میکند. با این حال، User Freeze عمومی نباید صرفاً با closed مدل شود زیرا freeze عملیاتی ممکن است همچنان نیازمند correction، deposit handling یا settlement کنترلشده باشد.
Baseline:
users.financial_status:
ACTIVE
FROZEN
CLOSED
FROZEN: application-level gate برای جلوگیری از عملیات جدید User.CLOSED: برای account termination دائمی/کنترلشده میتواند از TigerBeetle closing semantics نیز استفاده کند.- استفاده از
flags.closedیا closing transfer باید در یک Accounting Event مستقل و auditشده انجام شود. - Deep Reorg فوراً User را در application layer به
FROZENمیبرد؛ بعد Risk/Operations درباره hard-close تصمیم میگیرد.
9.4. Historical Import قبل از Go-Live
اگر پروژه historical financial data دارد، migration باید قبل از live traffic انجام شود.
قواعد baseline:
migration_mode =
FRESH_START
| HISTORICAL_IMPORT
برای HISTORICAL_IMPORT:
flags.importedاستفاده میشود.- imported و non-imported event در یک batch مخلوط نمیشوند.
- timestampها unique، گذشته و strictly increasing هستند.
- import روی fresh cluster ترجیح داده میشود؛ در غیر این صورت maintenance window الزامی است.
- batchهای import تا حد ممکن linked و قابل reconciliation هستند.
- live writers قبل از پایان migration و reconciliation فعال نمیشوند.
انتخاب migration_mode یک Go-Live blocker است و نمیتواند به Phaseهای پایانی موکول شود.
9.5. Flag Matrix قطعی برای Provisioning
این جدول قرارداد immutable provisioning است. Agent حق ندارد بر اساس «نوع کلی Account» flag را حدس بزند؛ برای هر code دقیقاً مجموعه زیر ساخته میشود.
Legend:
H = history
D<=C = debits_must_not_exceed_credits
C<=D = credits_must_not_exceed_debits
| Code | Account | Required Flags |
|---|---|---|
| 1001 | USER_AVAILABLE | H + D<=C |
| 1002 | USER_SPOT_LOCKED | H + D<=C |
| 1003 | USER_MARGIN | H + D<=C |
| 1004 | USER_WITHDRAWAL_LOCKED | H + D<=C |
| 2001 | CUSTODY_HOT | H + C<=D |
| 2002 | CUSTODY_WARM | H + C<=D |
| 2003 | CUSTODY_COLD | H + C<=D |
| 2004 | CUSTODY_DEPOSIT | H + C<=D |
| 2005 | ONCHAIN_OUTBOUND_IN_FLIGHT | H + C<=D |
| 2006 | CLIENT_BAD_DEBT_RECEIVABLE | H + C<=D |
| 2101 | VENUE_ASSET | H + C<=D |
| 2102 | VENUE_IN_TRANSIT | H + C<=D |
| 3001 | TRADING_FEE_REVENUE | H only |
| 3002 | SPREAD_REVENUE | H only |
| 3003 | WITHDRAWAL_FEE_REVENUE | H only |
| 3004 | CLIENT_FUNDING_REVENUE | H only |
| 3005 | CLIENT_LOSS_REVENUE | H only |
| 3006 | HEDGE_PNL_REVENUE | H only |
| 3007 | VENUE_FUNDING_REVENUE | H only |
| 3008 | LIQUIDATION_FEE_REVENUE | H only |
| 4001 | NETWORK_FEE_EXPENSE | H only |
| 4002 | VENUE_EXECUTION_FEE_EXPENSE | H only |
| 4003 | CLIENT_PROFIT_EXPENSE | H only |
| 4004 | HEDGE_PNL_EXPENSE | H only |
| 4005 | CLIENT_FUNDING_EXPENSE | H only |
| 4006 | VENUE_FUNDING_EXPENSE | H only |
| 4007 | BAD_DEBT_EXPENSE | H only |
| 4008 | SPOT_PRICE_IMPROVEMENT_EXPENSE | H only |
| 5001 | DEPOSIT_SUSPENSE | H only |
| 5002 | WITHDRAWAL_SUSPENSE | H only |
| 5003 | RECONCILIATION_SUSPENSE | H only |
| 5004 | CHAIN_REORG_RECEIVABLE | H only |
| 5005 | MANUAL_ADJUSTMENT_CLEARING | H only |
| 5006 | SPOT_SETTLEMENT_CLEARING | H only |
| 6001 | INSURANCE_FUND_RESERVE | H + D<=C |
| 6002 | BROKER_SPOT_INVENTORY_RESERVE | H + D<=C |
نکات الزامآور:
SPOT_SETTLEMENT_CLEARINGعمداً هیچmust_not_exceedندارد و میتواند موقتاً debit یا credit balance داشته باشد؛ جبران این آزادی با hard clearing guard در §20.4 انجام میشود.ONCHAIN_OUTBOUND_IN_FLIGHTوVENUE_IN_TRANSITnon-negative asset هستند؛ producer باید prerequisite eventها را به ترتیب مستندشده ثبت کند تا state-dependent rejection ایجاد نشود.- Revenue و Expense accountها فقط
historyدارند تا correction/contra-entry حسابرسیشده به constraint مصنوعی برخورد نکند. CLIENT_BAD_DEBT_RECEIVABLEیک debit-balance receivable واقعی است و write-off نمیتواند از receivable موجود بیشتر شود.BROKER_SPOT_INVENTORY_RESERVEوINSURANCE_FUND_RESERVEcredit-balance reserve هستند و debit آنها نمیتواند از credit موجود بیشتر شود.
هر تغییر در این جدول به دلیل immutable بودن Account flagها، برای Accountهای ساختهشده نیازمند migration/reprovisioning و Document-First version bump است.
9.6. هزینه history و Sharding Accountهای داغ
تصمیم history=true آگاهانه پذیرفته شده است، حتی برای Accountهای پرترافیک مانند TRADING_FEE_REVENUE.
اگر حجم history یک system account به مشکل عملیاتی تبدیل شود، راهحل baseline خاموشکردن history نیست. میتوان چند Account با همان Account Code و ownership/partition metadata روشن ایجاد کرد، مثلاً:
TRADING_FEE_REVENUE:BINANCE:BTCUSDT
TRADING_FEE_REVENUE:BYBIT:BTCUSDT
Partitioning باید قبل از provisioning آن accountها مستند شود و aggregation گزارش مالی بر اساس code + external metadata انجام شود.
مکانیزم فیزیکی این partition label از 0.005 صریح است: ستون accounting_accounts.partition_key طبق §26.2. مقدار پیشفرض '' است و accountهای shardشده label پایدار میگیرند.
این metadata فقط در PostgreSQL نگهداری میشود. user_data_64 و user_data_32 طبق §9.1 صفر میمانند و partitioning اجازه ندارد از آن فیلدها استفاده کند.
از دید TigerBeetle چند shard صرفاً چند Account با code یکسان و id متفاوت هستند؛ هیچ تغییری در flag matrix §9.5 لازم نیست.
Change History — Account Provisioning
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Mapping اولیه |
| 0.002 | 2026-09-12 | تثبیت history flag، user_data mapping، freeze semantics و historical import strategy |
| 0.003 | 2026-09-12 | افزودن flag matrix قطعی per-code و ثبت آگاهانه هزینه history/sharding policy |
| 0.005 | 2026-09-12 | تثبیت مکانیزم sharding روی accounting_accounts.partition_key و تاکید بر صفر ماندن user_data_64/32 |
10. Transfer ID و Idempotency
Introduced: 0.001
Last Changed: 0.003
Status: Frozen for Initial Implementation
هر Financial Operation قبل از ارسال به TigerBeetle یک accounting_event_id پایدار و persisted دارد.
accounting_event_id نیز یک u128 time-sortable مستقل است و قبل از ساخت legها persist میشود. این ID برای grouping/audit است و با Transfer.id یکی نیست.
10.1. ID Scheme
برای Account و Transfer از TigerBeetle time-based identifier scheme استفاده میشود:
high 48 bits = millisecond timestamp
low 80 bits = random / monotonic-within-ms component
Random UUID/hash به عنوان ID اصلی TigerBeetle استفاده نمیشود.
Transfer Leg ID
هر leg یک TigerBeetle time-based ID مستقل دارد و قبل از submit در accounting_event_legs persist میشود.
Accounting Event E
Leg 1 -> transfer_id = TB_TIME_ID_A
Leg 2 -> transfer_id = TB_TIME_ID_B
Leg 3 -> transfer_id = TB_TIME_ID_C
Retry همان persisted ID را استفاده میکند.
تصمیم v0.002: از schemeهایی مانند
hash(E,n)یا mask کردن بیتهای ID رسمی برای ساختbase_id | leg_indexاستفاده نمیشود. Grouping توسطuser_data_128انجام میشود، نه با دستکاری ID.
10.2. Transfer user_data_*
Mapping Transferها:
user_data_128
= accounting_event_id_u128
user_data_64
= real_world_occurred_at_ms
= 0 only if no meaningful external occurrence time exists
user_data_32
= source_system_code
source_system_code یک enum append-only است، مثلاً:
1 API
2 OMS
3 WALLET
4 EXECUTION
5 RISK
6 RECONCILIATION
7 ADMIN_CORRECTION
10.3. Result Handling
موفقیت semantic:
created / ok
exists
exists در retry همانند success تلقی میشود فقط وقتی object با همان محتوا وجود دارد.
تمام resultهای زیر Critical Data Integrity Error هستند:
exists_with_different_flags
exists_with_different_pending_id
exists_with_different_timeout
exists_with_different_debit_account_id
exists_with_different_credit_account_id
exists_with_different_amount
exists_with_different_user_data_128
exists_with_different_user_data_64
exists_with_different_user_data_32
exists_with_different_ledger
exists_with_different_code
رفتار:
event -> REQUIRES_REVIEW
automatic retry -> STOP
critical alert -> FIRE
این وضعیت یعنی یک ID یکسان با payload متفاوت تولید شده و یک bug منطقی/دادهای است.
10.4. Retry Rule و Burn شدن Transfer ID
حالت A — Outcome نامعلوم است
اگر application response را دریافت نکرده است و نمیداند request commit شده یا نه:
retry same request semantics
reuse exactly the same persisted transfer IDs
در این حالت ساخت ID جدید ممنوع است، چون ممکن است request قبلی در cluster commit شده باشد.
حالت B — TigerBeetle صریحاً state-dependent failure برگردانده است
از client release 0.16.4 به بعد، هفت result زیر transient/state-dependent هستند ولی ID همان attempt برای همیشه سوخته است:
debit_account_not_found
credit_account_not_found
pending_transfer_not_found
exceeds_credits
exceeds_debits
debit_account_already_closed
credit_account_already_closed
Retry همان ID بعداً به id_already_failed میرسد، حتی اگر state اصلاح شده باشد. دریافت خودِ id_already_failed نیز terminal محسوب میشود: worker نباید همان ID را دوباره retry کند و در صورت نیاز business باید از successor event استفاده کند.
این نکته برای business rejection عادی مثل insufficient funds نیز اعمال میشود. مثال:
USER_AVAILABLE insufficient
↓
exceeds_credits
↓
that transfer_id is permanently unusable
اگر business بعداً بعد از تامین موجودی دوباره همان intent را submit کند، باید successor Accounting Event با Transfer IDهای کاملاً جدید بسازد.
Linked Chain Rule
اگر یکی از legهای یک linked chain یکی از هفت خطای بالا را بگیرد، سایر legها معمولاً میتوانند linked_event_failed بگیرند. linked_event_failed خودش در لیست ID-burning بالا نیست، اما در این معماری هیچ ID از آن Accounting Event برای business re-attempt مجدد استفاده نمیشود.
قاعده اجباری:
one explicit ID-burning failure in event
↓
current accounting_event = PERMANENT_FAILED
failure_class = TB_TRANSIENT_ID_BURN
↓
NO leg ID from this event may be reused
↓
create successor accounting_event
supersedes_event_id = failed_event.id
↓
generate/persist ALL fresh leg IDs
↓
submit the entire new event
مخلوطکردن IDهای سالم chain قبلی با ID جدید leg سوخته ممنوع است. این قاعده audit lineage را ساده نگه میدارد و مانع eventهایی با ancestry مبهم میشود.
PERMANENT_FAILED در اینجا یعنی این attempt و IDهای آن terminal هستند؛ business intent در صورت مجاز بودن میتواند با successor event جدید دوباره اجرا شود.
هدف:
durable intent
+
persisted idempotency IDs
+
explicit burn semantics
+
superseding event lineage
=
safe financial retry
10.5. TigerBeetle Client Compatibility Floor
Implementation language این پروژه برای Accounting Service و TigerBeetle integration در baseline 0.004:
.NET / C#
Client رسمی:
NuGet package: tigerbeetle
namespace: TigerBeetle
حداقل prerequisite فعلی client رسمی برای runtime:
.NET >= 8.0
پروژه میتواند .NET جدیدتری pin کند و باید TFM دقیق را در CI ثبت کند.
حداقل semantic version مجاز:
TigerBeetle client >= 0.16.4
استفاده از client پایینتر از 0.16.4 ممنوع است، چون semantics مربوط به permanently-failed idempotency IDs در نسخههای قدیمی متفاوت است. این floor مستقل از زبان است و مبنای §10.4 و D-017 محسوب میشود.
الزامات build:
- نسخه واقعی package باید exact و متمرکز pin شود؛ baseline پیشنهادی Central Package Management با
Directory.Packages.props. PackageReferenceنباید version range یا floating version داشته باشد.- source رسمی
nuget.orgباید درNuGet.configپروژه ثبت شود. - CI باید downgrade به نسخه کمتر از
0.16.4را fail کند. - upgrade client/server باید result-code compatibility tests §35 را اجرا کند.
- actual client/server pair باید با compatibility requirements نسخه انتخابشده TigerBeetle سازگار باشد؛
0.16.4فقط semantic floor این معماری است. - تغییر semantics مربوط به create results/retry نیازمند Document-First update است.
- سطح دقیق API مورد استفاده (synchronous یا asynchronous) باید در ADR محیط بر اساس نسخه package pin شده ثبت شود. معماری فقط shared instance و batching را الزام میکند، نه یک surface خاص.
10.5.1. Client Lifetime در .NET
TigerBeetle.Client نوع IDisposable است و thread-safe؛ یک instance باید بین taskها به اشتراک گذاشته شود تا automatic batching کار کند.
Host / Process
│
└── singleton TigerBeetle.Client per cluster/config
قواعد:
- ثبت به صورت singleton در DI container، نه scoped و نه transient.
Disposeفقط در shutdown برنامه انجام میشود.usingدر scope یک request/handler ممنوع است؛ این عملاً همان client-per-request ممنوع در D-013 است.- بستن client تمام requestهای in-flight را cancel میکند، ولی طبق documentation ممکن است آن request همچنان روی server پردازش شود. بنابراین cancel/dispose هرگز به معنای «ثبت نشد» نیست و §10.4 حالت A اعمال میشود.
- client هیچ per-request timeout ندارد و بینهایت retry میکند؛ مدیریت timeout و cancellation وظیفه لایه application با
CancellationTokenاست.
10.5.2. Type Mapping و ذخیرهسازی u128
TigerBeetle u128 -> System.UInt128
Account flags -> TigerBeetle.AccountFlags
Transfer flags -> TigerBeetle.TransferFlags
time-based ID -> TigerBeetle.ID.Create()
Flagهای §9.5 در .NET با bitwise-or ترکیب میشوند، مثلاً:
H + D<=C -> AccountFlags.History | AccountFlags.DebitsMustNotExceedCredits
H + C<=D -> AccountFlags.History | AccountFlags.CreditsMustNotExceedDebits
H only -> AccountFlags.History
Baseline ذخیرهسازی u128 در PostgreSQL:
tigerbeetle_account_id -> uuid
tigerbeetle_transfer_id -> uuid
accounting_event_id -> uuid
uuid در PostgreSQL دقیقاً 128 بیت و index-friendly است و client رسمی .NET نیز تبدیل UInt128 به Guid را فراهم میکند.
الزام: تبدیل UInt128 ↔ Guid ↔ uuid باید در یک helper مرکزی با endianness ثابت و تستشده انجام شود. تکرار پراکنده این تبدیل در سرویسها ممنوع است، چون layout باینری Guid در .NET mixed-endian است و یک تبدیل ناهمگون میتواند ID مالی را به شکل غیرقابلبازگشت خراب کند.
مقادیر رزروشده: id = 0 و id = 2^128 - 1 مجاز نیستند و generator باید آنها را قبل از submit reject کند.
10.5.3. Deployment Note
محیط production پشتیبانیشده TigerBeetle فقط Linux >= 5.6 است. macOS و Windows فقط برای توسعه پشتیبانی میشوند.
بنابراین محیط توسعه باید TigerBeetle را در container اجرا کند تا با production divergence نداشته باشد.
Change History — IDs & Idempotency
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف Accounting Event و retry با ID ثابت |
| 0.002 | 2026-09-12 | جایگزینی deterministic hash/base-leg scheme با persisted TigerBeetle time IDs و تعریف result handling |
| 0.003 | 2026-09-12 | تعریف کامل ID-burning از 0.16.4، linked-chain superseding rule و انتخاب Go/client floor |
| 0.004 | 2026-09-12 | زبان پیادهسازی از Go به .NET تغییر کرد؛ افزودن client lifetime، type mapping، u128↔uuid storage و deployment note |
11. Accounting Event و Leg Model
Introduced: 0.001
Last Changed: 0.003
Status: Designed
PostgreSQL باید قبل از هر Financial Operation یک intent پایدار ایجاد کند.
accounting_events
id u128-compatible
event_type
aggregate_type
aggregate_id
user_id nullable
status
payload
created_at
occurred_at
submitted_at
completed_at
failed_at
attempt_count
next_retry_at
locked_by
locked_until
last_error_code
last_error_message
failure_class
supersedes_event_id nullable
supersedes_event_id یک self-reference به event قبلی است. Reverse relation یعنی superseded_by از query روی successor به دست میآید و به صورت ستون تکراری ذخیره نمیشود.
Status:
CREATED
READY
SUBMITTING
COMPLETED
RETRYABLE_FAILED
PERMANENT_FAILED
REQUIRES_REVIEW
Transfer IDها به شکل array داخل accounting_events نگهداری نمیشوند.
هر leg row مستقل دارد:
accounting_event_legs
id
accounting_event_id
leg_index
tigerbeetle_transfer_id
ledger_id
debit_account_id
credit_account_id
amount
transfer_code
flags
status
tigerbeetle_result_code
submitted_at
completed_at
created_at
Constraints:
UNIQUE(accounting_event_id, leg_index)
UNIQUE(tigerbeetle_transfer_id)
برای accounting_events:
FOREIGN KEY (supersedes_event_id) REFERENCES accounting_events(id)
UNIQUE (supersedes_event_id) WHERE supersedes_event_id IS NOT NULL
بنابراین یک failed attempt حداکثر یک successor مستقیم canonical دارد. Successor یک accounting_event_id جدید دارد و همه legهایش Transfer ID جدید میگیرند؛ در نتیجه constraint (accounting_event_id, leg_index) بدون تغییر باقی میماند.
مزایا:
- recovery per-leg
- debug سادهتر
- result code مستقل
- audit واضح
- امکان verify کردن linked chain قبل از submit
Change History — Accounting Event
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Event record اولیه |
| 0.002 | 2026-09-12 | افزودن worker lease/retry fields و جدول accounting_event_legs |
| 0.003 | 2026-09-12 | افزودن failure_class و supersedes_event_id برای lineage تلاشهای دارای ID سوخته |
12. Transfer Code Registry
Introduced: 0.001
Last Changed: 0.003
Status: Frozen for Initial Implementation
Transfer Codeها append-only هستند و معنای code موجود تغییر نمیکند.
100 DEPOSIT_CONFIRMED
110 WITHDRAWAL_LOCK
111 WITHDRAWAL_RELEASE
112 WITHDRAWAL_BROADCAST
113 WITHDRAWAL_CONFIRMED
114 WITHDRAWAL_FEE
120 INTERNAL_TRANSFER
200 SPOT_ORDER_LOCK
201 SPOT_ORDER_RELEASE
202 SPOT_CLIENT_SETTLEMENT
203 SPOT_VENUE_SETTLEMENT
204 SPOT_TRADING_FEE
205 SPOT_SPREAD
206 SPOT_PRICE_IMPROVEMENT
207 SPOT_INVENTORY_SETTLEMENT
300 MARGIN_LOCK
301 MARGIN_RELEASE
310 CLIENT_REALIZED_PROFIT
311 CLIENT_REALIZED_LOSS
320 HEDGE_REALIZED_PROFIT
321 HEDGE_REALIZED_LOSS
330 CLIENT_FUNDING_PAYMENT
331 CLIENT_FUNDING_RECEIPT
332 VENUE_FUNDING_PAYMENT
333 VENUE_FUNDING_RECEIPT
340 LIQUIDATION_COLLECTIBLE_LOSS
341 LIQUIDATION_FEE
342 BAD_DEBT_RECOGNITION
343 BAD_DEBT_WRITEOFF
344 INSURANCE_FUND_ADJUSTMENT
400 CUSTODY_MOVE
410 VENUE_DEPOSIT
411 VENUE_WITHDRAWAL
420 NETWORK_FEE
421 VENUE_EXECUTION_FEE
900 CORRECTION
901 MANUAL_ADJUSTMENT
12.1. Zero-Amount Transfer Policy
TigerBeetle از release 0.16.0 مقدار صفر را برای Transfer میپذیرد، اما baseline این پروژه برای posted business legs معمولی چنین است:
computed amount == 0
=> do not emit the transfer leg
در این حالت row اضافی در accounting_event_legs ساخته نمیشود.
استثنا فقط زمانی مجاز است که مقدار صفر در یک API operation معنای خاص و مستند TigerBeetle داشته باشد، مانند برخی post_pending_transfer / void_pending_transfer flowها. چنین استثنایی باید در همان feature صریح مستند شود.
هدف: جلوگیری از history noise و تفاوت رفتاری بین producerها.
Change History — Transfer Codes
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Registry اولیه |
| 0.002 | 2026-09-12 | افزودن price-improvement و مسیر صریح bad-debt/liquidation |
| 0.003 | 2026-09-12 | افزودن SPOT_INVENTORY_SETTLEMENT و zero-amount omission policy |
13. مدل User Balance
Introduced: 0.001
Last Changed: 0.001
Status: Designed
User از دید UI ممکن است فقط یک Trading Account داشته باشد.
اما از دید Ledger:
User
│
├── USDT
│ ├── AVAILABLE
│ ├── SPOT_LOCKED
│ ├── MARGIN
│ └── WITHDRAWAL_LOCKED
│
├── BTC
│ ├── AVAILABLE
│ ├── SPOT_LOCKED
│ └── WITHDRAWAL_LOCKED
│
└── ETH
├── AVAILABLE
├── SPOT_LOCKED
└── WITHDRAWAL_LOCKED
Total User Entitlement
مثلاً USDT:
Total User USDT =
AVAILABLE
+ SPOT_LOCKED
+ MARGIN
+ WITHDRAWAL_LOCKED
جابجایی بین این Accountها مالکیت User را عوض نمیکند؛ فقط وضعیت استفاده از دارایی را تغییر میدهد.
14. Deposit روی Blockchain
Introduced: 0.001
Last Changed: 0.002
Status: Designed
14.1. Lifecycle
DETECTED
↓
CONFIRMING
↓
CONFIRMED
↓
ACCOUNTED
PostgreSQL:
deposits
id
user_id
asset_id
network_id
address
amount
tx_hash
tx_index / vout / log_index
confirmation_count
required_confirmations
status
accounting_event_id
detected_at
confirmed_at
accounted_at
14.2. Credit User
بعد از رسیدن به confirmation policy:
مثلاً:
0.5 BTC
Transfer:
Deposit همیشه ابتدا به Account متناظر با مقصد واقعی on-chain ثبت میشود:
Debit:
CUSTODY_DEPOSIT_BTC:<custody_wallet_group_id>
Credit:
USER_AVAILABLE_BTC
Amount:
0.5 BTC
استفاده مستقیم از HOT_WALLET_BTC برای Deposit Credit مجاز نیست، مگر اینکه مقصد واقعی تراکنش همان Hot Wallet باشد و آن wallet group در Asset Registry/Custody Registry صریحاً همان Account باشد.
اگر بعداً deposit funds sweep شوند:
CUSTODY_DEPOSIT_BTC
↓ CUSTODY_MOVE
CUSTODY_HOT_BTC
این sweep یک Accounting Event جداست تا Blockchain reconciliation بر اساس wallet/address group ممکن بماند.
اثر اقتصادی:
Broker Asset +0.5 BTC
User Liability +0.5 BTC
14.3. Duplicate Deposit Event
Deposit identifier باید chain-specific و unique باشد.
برای UTXO chain معمولاً چیزی مشابه:
(tx_hash, vout)
برای Token Event معمولاً:
(chain_id, tx_hash, log_index)
Duplicate detection قبل از accounting انجام میشود.
همچنین Accounting Event و Transfer ID idempotent هستند.
14.4. Reorg
Credit تنها بعد از confirmation threshold انجام میشود.
اگر با وجود آن deep reorg رخ داد:
- Transfer قبلی حذف نمیشود.
- Correction/Reversal event ایجاد میشود.
- اگر User balance برای reversal کافی نبود، سیستم نباید invariant مالی را دور بزند.
- مورد به
CHAIN_REORG_RECEIVABLEو Manual/Risk Review منتقل میشود. - User تا حل موضوع در application layer به
FROZENمیرود. - استفاده از TigerBeetle
closedبرای hard-close فقط طبق §9.3 انجام میشود.
Policy دقیق Reorg باید per-network تعریف شود.
Change History — Deposit
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف lifecycle اولیه Deposit، confirmation و accounting |
| 0.002 | 2026-09-12 | تثبیت CUSTODY_DEPOSIT به عنوان منبع Deposit Credit و جداسازی Sweep به CUSTODY_MOVE |
15. Internal Transfer بین Userها
Introduced: 0.001
Last Changed: 0.001
Status: Designed
مثال:
Ali → Sara
0.5 BTC
Transfer:
Debit:
ALI_BTC_AVAILABLE
Credit:
SARA_BTC_AVAILABLE
Custody Asset تغییر نمیکند.
Ali Liability -0.5
Sara Liability +0.5
Broker Asset unchanged
Blockchain transaction ساخته نمیشود.
16. Custody: Hot / Warm / Cold
Introduced: 0.001
Last Changed: 0.001
Status: Designed
مثال انتقال 20 BTC از Hot به Cold:
Debit:
COLD_WALLET_BTC
Credit:
HOT_WALLET_BTC
Amount:
20 BTC
این فقط location دارایی Broker را تغییر میدهد.
User Liability هیچ تغییری نمیکند.
Network Fee باید leg جدا باشد:
Debit:
NETWORK_FEE_EXPENSE_BTC
Credit:
HOT_WALLET_BTC
17. انتقال Asset به Venue / LP
Introduced: 0.001
Last Changed: 0.001
Status: Designed
برای A-Book باید Broker روی Venue/LP دارایی داشته باشد.
مثال:
Hot Wallet → Binance
100,000 USDT
مرحله Broadcast:
Debit:
VENUE_IN_TRANSIT_USDT
Credit:
HOT_WALLET_USDT
پس از credit شدن توسط Venue:
Debit:
BINANCE_USDT_ASSET
Credit:
VENUE_IN_TRANSIT_USDT
VENUE_IN_TRANSIT باید در حالت عادی پس از settlement به صفر برگردد.
18. Spot Trading
Introduced: 0.001
Last Changed: 0.003
Status: Designed
Spot واقعی است.
اگر User:
BUY 1 BTC
انجام دهد، در پایان:
USER_BTC_AVAILABLE += 1 BTC
به عنوان Liability Broker به User.
18.1. Lock کردن Buy Order
Ali:
USDT Available = 100,000
BUY 1 BTC
Limit = 50,000 USDT
در زمان Place Order:
Debit:
USER_USDT_AVAILABLE
Credit:
USER_USDT_SPOT_LOCKED
Amount:
max_required_amount
برای Buy Order با quote asset، مقدار lock باید حد بالای واقعی مصرف User باشد:
max_quote_notional = round_up(price_cap * quantity, quote_asset_scale)
max_required_amount =
max_quote_notional
+ worst_case_client_fee_in_quote
+ explicit_safety_buffer_if_configured
قواعد:
- برای Limit Order،
price_cap = limit_price. - برای Market Order، وجود finite
price_protection_capالزامی است؛ Market Order بدون سقف محاسبهپذیر برای lock پذیرفته نمیشود. - اگر fee از quote asset دریافت میشود، worst-case fee باید داخل lock باشد.
- اگر fee از Asset دیگری دریافت میشود، availability آن Asset جداگانه validate میشود و داخل این amount مخلوط نمیشود.
- unused buffer بعد از final fill/cancel با
SPOT_ORDER_RELEASEآزاد میشود. - rounding همیشه به سمت محافظهکارانه انجام میشود تا settlement به overdraft ناشی از precision نخورد.
برای Sell Order، حداقل base_quantity و در صورت charge شدن fee از base asset، worst-case base fee نیز lock میشود.
این Single-Phase Transfer است.
18.2. چرا Spot Order Lock با Pending Transfer مدل نمیشود؟
در نسخه 0.001، Spot Order Reservation با جابهجایی واقعی:
AVAILABLE → SPOT_LOCKED
مدل میشود.
دلیل:
یک Pending Transfer در TigerBeetle فقط یک بار resolve میشود.
اگر بخشی از Pending Amount post شود، remainder آزاد میشود.
اما Order میتواند چندین Partial Fill مستقل داشته باشد:
Fill 1
Fill 2
Fill 3
Fill 4
...
بنابراین Account مجزای SPOT_LOCKED برای lifecycle سفارش مناسبتر است.
18.3. Partial Fill
فرض:
Order reserve = 50,000 USDT
Fill اول:
Client fill = 10,000 USDT
فقط 10,000 از SPOT_LOCKED مصرف میشود.
باقیمانده:
40,000 USDT
برای Fillهای بعدی locked میماند.
18.4. Cancel Order
Unused reserve:
Debit:
USER_USDT_SPOT_LOCKED
Credit:
USER_USDT_AVAILABLE
Change History — Spot Order Reservation
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف اولیه AVAILABLE → SPOT_LOCKED |
| 0.003 | 2026-09-12 | تعریف دقیق max_required_amount، fee buffer و price protection برای Market Order |
19. Spot Settlement — تفکیک Client Side از Venue Side
Introduced: 0.001
Last Changed: 0.003
Status: Designed
19.1. اصل معماری
از v0.002 Spot Settlement نباید فرض کند:
1 Client Fill = 1 Venue Fill
Client ممکن است:
- از inventory موجود Broker پر شود،
- توسط چند Venue Fill تامین شود،
- قبل/بعد از Venue Fill settle شود،
- از aggregate/net execution استفاده کند.
بنابراین بین Client و Venue یک clearing account per-asset وجود دارد:
SPOT_SETTLEMENT_CLEARING_USDT
SPOT_SETTLEMENT_CLEARING_BTC
SPOT_SETTLEMENT_CLEARING_ETH
...
هدف steady-state:
clearing balance = 0
Client Settlement و Venue Settlement دو Accounting Event مستقلاند و با allocation در PostgreSQL به هم مرتبط میشوند.
19.2. Client Buy Settlement
مثال:
Client buys 1 BTC
Client price = 50,000 USDT
Client-side linked chain:
USDT
Debit:
USER_USDT_SPOT_LOCKED
Credit:
SPOT_SETTLEMENT_CLEARING_USDT
Amount:
50,000 USDT
Code:
SPOT_CLIENT_SETTLEMENT
BTC
Debit:
SPOT_SETTLEMENT_CLEARING_BTC
Credit:
USER_BTC_AVAILABLE
Amount:
1 BTC
Code:
SPOT_CLIENT_SETTLEMENT
این operation وابسته به یک Venue مشخص نیست.
19.3. Venue Execution برای Buy
فرض:
Venue cost = 49,990 USDT
Venue BTC acquired = 1 BTC
Venue-side linked chain:
Quote Asset
Debit:
SPOT_SETTLEMENT_CLEARING_USDT
Credit:
BINANCE_USDT_ASSET
Amount:
49,990 USDT
Code:
SPOT_VENUE_SETTLEMENT
Base Asset
Debit:
BINANCE_BTC_ASSET
Credit:
SPOT_SETTLEMENT_CLEARING_BTC
Amount:
1 BTC
Code:
SPOT_VENUE_SETTLEMENT
بعد از allocation کامل:
BTC clearing = 0
USDT clearing = +10 credit balance
19.4. Fill از Inventory خود Broker
Spot ممکن است بدون Venue Fill فوری از inventory اقتصادی خود Broker تامین شود. در این حالت Venue جعلی ایجاد نمیشود.
Account مورد استفاده:
BROKER_SPOT_INVENTORY_RESERVE_<ASSET>
این Account location دارایی نیست؛ custody location همچنان در CUSTODY_* / VENUE_ASSET است.
Client Buy از Broker Inventory
بعد از Client-side settlement در §19.2، clearing چنین exposureی دارد:
Quote clearing = credit balance
Base clearing = debit balance
یک linked accounting event با code SPOT_INVENTORY_SETTLEMENT ثبت میشود:
Quote leg:
Debit: SPOT_SETTLEMENT_CLEARING_USDT
Credit: BROKER_SPOT_INVENTORY_RESERVE_USDT
Amount: internal_quote_amount
Base leg:
Debit: BROKER_SPOT_INVENTORY_RESERVE_BTC
Credit: SPOT_SETTLEMENT_CLEARING_BTC
Amount: internal_base_quantity
در نتیجه Broker بخشی از BTC inventory خود را به entitlement مشتری تبدیل کرده و در مقابل USDT inventory/equity خود را افزایش داده است.
Client Sell به Broker Inventory
برای Sell، جهت برعکس است:
Base leg:
Debit: SPOT_SETTLEMENT_CLEARING_BTC
Credit: BROKER_SPOT_INVENTORY_RESERVE_BTC
Amount: internal_base_quantity
Quote leg:
Debit: BROKER_SPOT_INVENTORY_RESERVE_USDT
Credit: SPOT_SETTLEMENT_CLEARING_USDT
Amount: internal_quote_amount
BROKER_SPOT_INVENTORY_RESERVE دارای debits_must_not_exceed_credits است؛ بنابراین سیستم نمیتواند بیش از inventory اقتصادی متعلق به Broker را به مشتری بفروشد.
Replenishment بعدی Broker از Venue یک event مستقل است و نباید retrospectively به عنوان همان Client Fill جعل شود.
19.5. Spread Settlement
اگر residual به نفع Broker باشد:
Debit:
SPOT_SETTLEMENT_CLEARING_USDT
Credit:
SPREAD_REVENUE_USDT
Amount:
10 USDT
اگر Venue execution از Client price بدتر باشد و residual به ضرر Broker باشد:
Debit:
SPOT_PRICE_IMPROVEMENT_EXPENSE_USDT
Credit:
SPOT_SETTLEMENT_CLEARING_USDT
Amount:
absolute_residual
هیچ residual نباید بدون classification دائماً در clearing باقی بماند.
19.6. Multi-Venue / Aggregate Execution
یک Client Fill میتواند به چند Venue Fill allocate شود:
Client Fill C1 = 1.0 BTC
Venue Fill V1 = 0.4 BTC @ Binance
Venue Fill V2 = 0.3 BTC @ Bybit
Venue Fill V3 = 0.3 BTC @ Binance
PostgreSQL باید allocation صریح داشته باشد:
spot_execution_allocations
id
client_fill_id
source_type # VENUE_EXECUTION | BROKER_INVENTORY
venue_execution_id nullable
source_accounting_event_id nullable
base_quantity
quote_amount
created_at
Client settlement فقط یک بار انجام میشود؛ هر Venue Fill settlement خودش را دارد. Allocation با source_type=BROKER_INVENTORY نیز مجاز است و نباید venue_execution_id جعلی داشته باشد.
19.7. Atomicity الزامآور
هر accounting event چند-leg که نیاز به atomicity دارد باید:
- تمام legها را در یک
create_transferscall بفرستد. - legها در request contiguous باشند.
- تمام legها به جز آخر
flags.linked = trueداشته باشند. - آخرین leg
flags.linked = falseداشته باشد.
تقسیم یک linked chain بین چند request ممنوع است.
در default configuration، create_transfers حداکثر 8189 event در یک request میپذیرد. اگر یک business operation از این سقف عبور کند، نمیتوان atomicity کامل TigerBeetle را ادعا کرد و طراحی باید قبل از implementation تغییر کند.
20. Spot Sell Settlement
Introduced: 0.001
Last Changed: 0.003
Status: Designed
فرض:
Client sells 1 BTC
Client proceeds = 50,000 USDT
Venue proceeds = 50,010 USDT
20.1. Client Side
BTC:
Debit:
USER_BTC_SPOT_LOCKED
Credit:
SPOT_SETTLEMENT_CLEARING_BTC
Amount:
1 BTC
USDT:
Debit:
SPOT_SETTLEMENT_CLEARING_USDT
Credit:
USER_USDT_AVAILABLE
Amount:
50,000 USDT
این دو leg یک linked chain هستند.
20.2. Venue Side
BTC:
Debit:
SPOT_SETTLEMENT_CLEARING_BTC
Credit:
BINANCE_BTC_ASSET
Amount:
1 BTC
USDT:
Debit:
BINANCE_USDT_ASSET
Credit:
SPOT_SETTLEMENT_CLEARING_USDT
Amount:
50,010 USDT
در این مثال USDT clearing دارای 10 USDT credit residual است که با SPOT_SPREAD به SPREAD_REVENUE_USDT منتقل میشود.
20.3. Invariantهای Spot Clearing
برای هر Asset:
expected long-lived balance = 0
Non-zero balance فقط وقتی مجاز است که حداقل یکی از این وضعیتها وجود داشته باشد:
UNALLOCATED_CLIENT_FILL
UNSETTLED_VENUE_FILL
PARTIAL_VENUE_EXECUTION
PRICE_RESIDUAL_PENDING_CLASSIFICATION
INCIDENT_REQUIRES_REVIEW
هر balance باید از روی spot_execution_allocations و accounting events قابل توضیح باشد.
20.4. Hard Clearing Guard
چون SPOT_SETTLEMENT_CLEARING عمداً must_not_exceed ندارد، TigerBeetle به تنهایی solvency این account را enforce نمیکند. بنابراین کنترل زیر Production Requirement است:
unexplained clearing age > configured_max_age
OR
abs(unexplained clearing exposure) > configured_hard_limit
↓
CRITICAL ALERT
↓
HALT NEW CLIENT SPOT SETTLEMENTS
for affected asset/instrument scope
در حالت halt، این عملیات همچنان باید مجاز بمانند:
- Venue settlement برای کاهش exposure
- inventory settlement برای بستن exposure تاییدشده
- order lock release / cancel
- reconciliation
- correction/reversal حسابرسیشده
رفع circuit breaker فقط پس از reconciliation موفق یا override صریح و auditشده مجاز است.
configured_max_age و configured_hard_limit قبل از Spot Production باید مقدار غیر-null داشته باشند.
اگر در آینده hard bound کاملاً داخل TigerBeetle لازم شود، recipe رسمی Balance Bounds میتواند بررسی شود؛ baseline 0.003 به دلیل هزینه و پیچیدگی پنج linked transfer آن را برای Spot Clearing فعال نمیکند.
Change History — Spot Settlement
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف اولیه Spot Buy/Sell، lock account، partial fill، spread و fee |
| 0.002 | 2026-09-12 | حذف فرض 1:1 بین Client Fill و Venue Fill؛ افزودن SPOT_SETTLEMENT_CLEARING و multi-venue allocation |
| 0.003 | 2026-09-12 | افزودن internal broker inventory settlement، source_type allocation و hard clearing guard/circuit breaker |
21. Spot Trading Fee
Introduced: 0.001
Last Changed: 0.002
Status: Designed
اگر از User:
20 USDT
fee گرفته شود:
Debit:
USER_USDT_SPOT_LOCKED / USER_USDT_AVAILABLE
Credit:
TRADING_FEE_REVENUE_USDT
برای Buy Order، max_required_amount در زمان lock باید در صورت charge شدن fee از quote asset شامل worst-case fee/buffer نیز باشد تا settlement در زمان fill به overdraft نخورد.
Venue execution fee مستقل است:
Debit:
VENUE_EXECUTION_FEE_EXPENSE_USDT
Credit:
BINANCE_USDT_ASSET
Client Fee و Venue Fee هیچوقت با هم net و مخفی نمیشوند.
Change History — Spot Trading Fee
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف اولیه Trading Fee و Venue Fee |
| 0.002 | 2026-09-12 | الزام worst-case fee داخل max_required_amount برای feeهای quote-asset |
22. Position / Derivatives — A-Book
Introduced: 0.001
Last Changed: 0.002
Status: Designed
Position داخل PostgreSQL نگهداری میشود.
نمونه:
positions
id
user_id
instrument_id
side
quantity
entry_price
mark_price
leverage
initial_margin
maintenance_margin
realized_pnl
unrealized_pnl
status
opened_at
closed_at
TigerBeetle Position را نگهداری نمیکند.
22.1. Margin Lock
مثال:
Notional = 10,000 USDT
Leverage = 10x
Margin = 1,000 USDT
Transfer:
Debit:
USER_USDT_AVAILABLE
Credit:
USER_USDT_MARGIN
Amount:
1,000 USDT
User هنوز مالک 1,000 USDT است ولی amount برای trading lock شده است.
22.2. Unrealized PnL
Unrealized PnL در TigerBeetle ثبت نمیشود.
مثال:
Entry = 50,000
Mark = 52,000
Unrealized PnL = +2,000
این مقدار توسط Position/Risk Engine محاسبه میشود.
دلیل:
- Mark Price دائماً تغییر میکند.
- Unrealized PnL settlement واقعی نیست.
- Ledger نباید با هر tick بازار تغییر کند.
22.3. Realized PnL
هنگام Close یا Realization، PnL وارد TigerBeetle میشود.
User Profit
مثال:
User Realized Profit = 500 USDT
Transfer:
Debit:
CLIENT_PROFIT_EXPENSE_USDT
Credit:
USER_USDT_AVAILABLE
User Loss
مثال:
User Realized Loss = 500 USDT
Loss نباید با یک transfer که ممکن است از موجودی User بیشتر باشد ثبت شود.
Baseline v0.002:
collectible_lossاز balance واقعیUSER_MARGINو سپس طبق Risk Policy ازUSER_AVAILABLEمحاسبه میشود.- فقط مقدار collectible از User debit میشود.
shortfallبا مسیر Bad Debt §24 ثبت میشود.- هیچ transfer نباید constraint حساب User را دور بزند.
برای بخش قابل وصول:
Debit:
USER_USDT_MARGIN / USER_USDT_AVAILABLE
Credit:
CLIENT_LOSS_REVENUE_USDT
Amount:
collectible_loss
Policy دقیق اولویت consumption بین MARGIN و AVAILABLE هنوز باید قبل از فعالشدن derivatives production نهایی شود، ولی shortfall هرگز نباید liquidation را block کند.
22.4. Margin Release
پس از بسته شدن Position یا کاهش margin:
Debit:
USER_USDT_MARGIN
Credit:
USER_USDT_AVAILABLE
22.5. A-Book Hedge
Client Exposure:
Client LONG BTCUSDT
Broker exposure مقابل Client دارد و آن را روی Venue hedge میکند.
مفهومی:
Client +10k BTC exposure
Broker vs Client -10k
Broker at Venue +10k
----------------------
Broker Net ≈ 0
Position و Hedge Position در PostgreSQL نگهداری میشوند.
22.6. Hedge Mapping
سیستم نباید فرض کند:
1 Client Fill = 1 Hedge Fill
PostgreSQL باید قابلیت allocation داشته باشد:
hedge_allocations
id
client_fill_id / exposure_id
hedge_fill_id
allocated_quantity
allocated_notional
created_at
این design امکان موارد زیر را میدهد:
- 1:1 hedge
- aggregate hedge
- net hedge
- partial hedge
- multi-venue hedge
22.7. Hedge Realized PnL
اگر Venue hedge:
+490 USDT
سود بسازد:
Debit:
BINANCE_USDT_ASSET
Credit:
HEDGE_PNL_REVENUE_USDT
اگر hedge ضرر کند:
Debit:
HEDGE_PNL_EXPENSE_USDT
Credit:
BINANCE_USDT_ASSET
22.8. Client و Hedge PnL جدا ثبت میشوند
مثال:
Client Profit Expense = 500
Hedge Profit Revenue = 490
Net A-Book result:
490 - 500 = -10 USDT
سپس Fee/Spread میتواند نتیجه نهایی Broker را تغییر دهد.
هرگز Client PnL و Hedge PnL را قبل از ثبت accounting با هم net نکن.
Change History — Position / Derivatives
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف اولیه Position، Margin، Realized/Unrealized PnL و A-Book Hedge |
| 0.001 | 2026-09-12 | تصمیم: Unrealized PnL داخل TigerBeetle ثبت نشود |
| 0.001 | 2026-09-12 | تصمیم: Client PnL و Hedge PnL جداگانه ثبت شوند |
| 0.001 | 2026-09-12 | تصمیم: Mapping بین Client Fill و Hedge Fill از مدل many-to-many پشتیبانی کند |
| 0.002 | 2026-09-12 | مسیر Client Loss به collectible + explicit bad-debt shortfall تفکیک شد |
23. Funding
Introduced: 0.001
Last Changed: 0.002
Status: Designed
Client Funding و Venue Funding مستقل ثبت میشوند و قبل از accounting net نمیشوند.
23.1. Client pays funding
Debit:
USER_USDT_MARGIN / USER_USDT_AVAILABLE
Credit:
CLIENT_FUNDING_REVENUE_USDT
Code:
CLIENT_FUNDING_PAYMENT
23.2. Client receives funding
Debit:
CLIENT_FUNDING_EXPENSE_USDT
Credit:
USER_USDT_AVAILABLE
Code:
CLIENT_FUNDING_RECEIPT
23.3. Venue pays Broker funding
Debit:
VENUE_USDT_ASSET
Credit:
VENUE_FUNDING_REVENUE_USDT
Code:
VENUE_FUNDING_RECEIPT
23.4. Broker pays Venue funding
Debit:
VENUE_FUNDING_EXPENSE_USDT
Credit:
VENUE_USDT_ASSET
Code:
VENUE_FUNDING_PAYMENT
Client Funding و Venue Funding حتی اگر از نظر اقتصادی قابل offset باشند، در Ledger مستقل باقی میمانند.
Change History — Funding
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تفکیک Client و Venue funding به عنوان اصل معماری |
| 0.002 | 2026-09-12 | افزودن accountهای revenue/expense صریح برای هر چهار جهت funding |
24. Liquidation و Bad Debt
Introduced: 0.001
Last Changed: 0.002
Status: Designed / Risk Policy Partially Open
Liquidation decision توسط Risk Engine گرفته میشود. Accounting Service نتیجه مالی را ثبت میکند.
24.1. قانون جلوگیری از Deadlock
User Liability accountها constrained هستند:
debits_must_not_exceed_credits = true
بنابراین Liquidation نباید یک linked transfer برای کل theoretical loss بسازد اگر loss از MARGIN + AVAILABLE بیشتر است؛ در غیر این صورت TigerBeetle leg را reject میکند و chain میتواند liquidation را متوقف کند.
Baseline:
realized_loss =
collectible_loss
+ shortfall
24.2. Collectible Loss
فقط مقدار قابل وصول:
Debit:
USER_MARGIN / USER_AVAILABLE
Credit:
CLIENT_LOSS_REVENUE
Amount:
collectible_loss
Code:
LIQUIDATION_COLLECTIBLE_LOSS
24.3. Shortfall / Bad Debt Recognition
اگر:
realized_loss = 1,200
collectible = 1,000
shortfall = 200
shortfall به عنوان receivable ثبت میشود:
Debit:
CLIENT_BAD_DEBT_RECEIVABLE
Credit:
CLIENT_LOSS_REVENUE
Amount:
200
Code:
BAD_DEBT_RECOGNITION
این باعث میشود User balance منفی نشود و liquidation قابل تکمیل بماند.
24.4. Write-off
اگر receivable غیرقابل وصول تشخیص داده شد:
Debit:
BAD_DEBT_EXPENSE
Credit:
CLIENT_BAD_DEBT_RECEIVABLE
Amount:
writeoff_amount
Code:
BAD_DEBT_WRITEOFF
24.5. Insurance Fund
اگر Insurance Fund فعال باشد، recovery باید Accounting Event جداگانه باشد و نباید completion liquidation را block کند.
در v0.002 فقط حساب پایه زیر reserve شده است:
INSURANCE_FUND_RESERVE
مدل دقیق funding/replenishment/recovery و اثر equity آن قبل از production liquidation باید در Risk Specification نهایی شود.
24.6. Liquidation Fee
Debit:
USER_MARGIN / USER_AVAILABLE
Credit:
LIQUIDATION_FEE_REVENUE
Code:
LIQUIDATION_FEE
Fee فقط تا حد balance قابل وصول گرفته میشود مگر policy دیگری به صورت صریح مستند شود.
Change History — Liquidation
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Skeleton اولیه liquidation |
| 0.002 | 2026-09-12 | افزودن collectible-loss، bad-debt receivable/writeoff و جلوگیری از deadlock به علت account constraint |
25. Withdrawal روی Blockchain
Introduced: 0.001
Last Changed: 0.003
Status: Designed
25.1. Lifecycle
REQUESTED
↓
LOCKED
↓
APPROVED
↓
SIGNED
↓
BROADCASTED
↓
CONFIRMING
↓
CONFIRMED
↓
ACCOUNTED / COMPLETED
Failure stateها:
REJECTED
FAILED_BEFORE_BROADCAST
REQUIRES_REVIEW
REPLACED
25.2. Lock User Balance
در v0.002 فرض میشود requested_amount همان amount دریافتی مقصد است و withdrawal fee در صورت وجود علاوه بر آن از User دریافت میشود.
مثال:
recipient amount = 0.1 BTC
user withdrawal fee = 0.0002 BTC
total lock = 0.1002 BTC
Transfer:
Debit:
USER_BTC_AVAILABLE
Credit:
USER_BTC_WITHDRAWAL_LOCKED
25.3. Broadcast
فرض:
principal = 0.1 BTC
actual network fee = 0.00015 BTC
در زمان Broadcast:
Debit:
ONCHAIN_OUTBOUND_IN_FLIGHT_BTC
Credit:
HOT_WALLET_BTC
Amount:
0.10015 BTC
این نشان میدهد Asset از Wallet خارج شده ولی هنوز settlement نهایی نشده است.
25.3.1. Ordering Invariant اجباری
با توجه به اینکه ONCHAIN_OUTBOUND_IN_FLIGHT دارای credits_must_not_exceed_debits است، ترتیب state transition حسابداری بخشی از correctness سیستم است:
1. WITHDRAWAL_LOCK must be COMPLETED
2. WITHDRAWAL_BROADCAST accounting must be COMPLETED
3. final chain spend / RBF delta must be reflected in IN_FLIGHT
4. only then CONFIRMATION settlement may run
5. principal + network fee + user fee are finalized
Producer حق ندارد event مربوط به principal/network-fee confirmation را قبل از completed شدن broadcast accounting آمادهی submit کند.
برای RBF/replacement، هر افزایش در final on-chain spend باید قبل از confirmation با adjustment همان withdrawal به ONCHAIN_OUTBOUND_IN_FLIGHT اضافه شود. Confirmation نباید بیشتر از debit ثبتشدهی In-Flight credit کند.
در baseline 0.003، هنگام final confirmation legهای زیر باید در یک linked request settle شوند:
principal settlement
network fee expense
user withdrawal fee (if amount > 0)
leg با amount صفر طبق §12.1 حذف میشود.
25.4. Confirmation — Principal
پس از finality:
Debit:
USER_BTC_WITHDRAWAL_LOCKED
Credit:
ONCHAIN_OUTBOUND_IN_FLIGHT_BTC
Amount:
0.1 BTC
User Liability کاهش پیدا میکند و principal asset از In-Flight خارج میشود.
25.5. Network Fee
Debit:
NETWORK_FEE_EXPENSE_BTC
Credit:
ONCHAIN_OUTBOUND_IN_FLIGHT_BTC
Amount:
0.00015 BTC
25.6. User Withdrawal Fee
مثلاً:
0.0002 BTC
Transfer:
Debit:
USER_BTC_WITHDRAWAL_LOCKED
Credit:
WITHDRAWAL_FEE_REVENUE_BTC
در نتیجه:
User charged fee 0.00020
Actual network cost 0.00015
-----------------------------
Gross fee margin 0.00005 BTC
25.7. Failed Before Broadcast
اگر withdrawal قبل از broadcast شکست خورد:
Debit:
USER_WITHDRAWAL_LOCKED
Credit:
USER_AVAILABLE
کل lock آزاد میشود.
25.8. Failure After Broadcast
بعد از Broadcast balance User خودکار release نمیشود.
ابتدا chain state باید مشخص شود.
مواردی مثل:
- dropped tx
- RBF
- replacement tx
- conflicting tx
- delayed tx
باید توسط Withdrawal state machine مدیریت شوند.
25.9. Withdrawal ID با TxHash یکی نیست
withdrawal_id != tx_hash
مثال:
Withdrawal #123
Tx A → broadcast
Tx A → dropped
Tx B → replacement
Tx B → confirmed
در Accounting هنوز فقط یک business withdrawal وجود دارد.
25.10. چرا Withdrawal با Pending Transfer مدل نمیشود؟
این یک تصمیم صریح معماری است.
Withdrawal میتواند بعد از Broadcast دارای:
RBF
replacement transaction
dropped transaction
delayed confirmation
multiple tx_hash for one withdrawal_id
باشد.
Pending Transfer در TigerBeetle حداکثر یک بار resolve میشود و مدل مناسبی برای این state machine نیست.
بنابراین lifecycle مالی Withdrawal با Accountهای posted زیر مدل میشود:
USER_AVAILABLE
↓
USER_WITHDRAWAL_LOCKED
↓
ONCHAIN_OUTBOUND_IN_FLIGHT
↓
confirmed settlement
این تصمیم نباید بدون Decision Log جدید به pending transfer تبدیل شود.
Change History — Withdrawal
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | تعریف lifecycle اولیه Withdrawal |
| 0.001 | 2026-09-12 | تصمیم: withdrawal_id مستقل از tx_hash باشد |
| 0.001 | 2026-09-12 | تصمیم: پس از Broadcast release خودکار موجودی ممنوع است |
| 0.002 | 2026-09-12 | تصمیم صریح: Withdrawal state machine با posted accounts مدل شود، نه Pending Transfer |
| 0.003 | 2026-09-12 | الزام ordering بین lock/broadcast/in-flight/confirmation و RBF delta قبل از final settlement |
26. PostgreSQL Schema — حداقل مدل مورد نیاز
Introduced: 0.001
Last Changed: 0.006
Status: Designed
حداقل tableها:
assets
networks
asset_networks
instruments
users
accounting_accounts
accounting_events
accounting_event_legs
balance_cache
orders
fills
spot_execution_allocations
positions
hedge_orders
hedge_fills
hedge_allocations
deposit_addresses
deposits
withdrawals
blockchain_transactions
venues
venue_accounts
venue_executions
venue_balance_snapshots
reconciliation_runs
reconciliation_items
manual_adjustments
26.1. instruments
positions.instrument_id و trading orderها باید به registry صریح instrument متصل باشند.
instruments
id
symbol
base_asset_id
quote_asset_id
settlement_asset_id
instrument_type
price_scale
quantity_scale
status
created_at
updated_at
price_scale و quantity_scale با TigerBeetle asset scale یکی نیستند و باید جدا مدیریت شوند.
26.2. accounting_accounts
Mapping بین domain و TigerBeetle:
accounting_accounts
id
owner_type
owner_id
asset_id
account_type
partition_key
tigerbeetle_account_id
history_enabled
status
created_at
Unique constraint مفهومی:
(owner_type, owner_id, asset_id, account_type, partition_key)
history_enabled در production باید همیشه true باشد و صرفاً برای audit/debug mapping ذخیره میشود؛ source واقعی flag خود TigerBeetle است.
partition_key تنها مکانیزم مجاز برای sharding accountهای داغ طبق §9.6 است:
- برای هر account معمولی مقدار آن رشته خالی
''است، نهNULL، تا unique constraint همیشه فعال بماند و invariant «یک account به ازای هر (owner, asset, type)» دستنخورده بماند. - برای accountهای shardشده مقدار آن یک partition label پایدار است، مثلاً
BINANCE:BTCUSDT. - این متادیتا فقط در PostgreSQL زندگی میکند.
user_data_64وuser_data_32طبق §9.1 صفر میمانند و هیچ معنای پنهانی نمیگیرند. - aggregation گزارش مالی طبق §9.6 بر اساس
code + partition_keyانجام میشود. partition_keyبعد از provisioning immutable است، چونtigerbeetle_account_idمربوطه غیرقابلتغییر است.
26.3. accounting_events
id
event_type
aggregate_type
aggregate_id
user_id nullable
status
payload
occurred_at
source_system
attempt_count
next_retry_at
locked_by
locked_until
last_error_code
last_error_message
failure_class
supersedes_event_id nullable
created_at
submitted_at
completed_at
failed_at
source_system مقدار source_system_code از §10.2 است که به user_data_32 هر Transfer این event نوشته میشود.
ذخیرهسازی آن اجباری است و قابل استنتاج در زمان submit نیست. هر مقداری که به user_data_* میرود باید persist شود تا retry بایتبهبایت همان payload قبلی را بفرستد؛ در غیر این صورت یک attempt دوم با source_system متفاوت به exists_with_different_user_data_32 میخورد که طبق §10.3 یک Critical Data Integrity Error است و event را به REQUIRES_REVIEW میبرد.
همین استدلال دربارهٔ occurred_at (که به user_data_64 میرود) و accounting_event_legs.flags طبق §26.9 اعمال میشود.
Worker فقط eventی را claim میکند که:
status in (READY, RETRYABLE_FAILED)
AND next_retry_at <= now()
AND (locked_until IS NULL OR locked_until < now())
Claim باید transactional/atomic باشد.
Constraints:
FOREIGN KEY (supersedes_event_id) REFERENCES accounting_events(id)
UNIQUE (supersedes_event_id) WHERE supersedes_event_id IS NOT NULL
Successor فقط پس از explicit terminal attempt ساخته میشود؛ timeout/unknown outcome مجوز ساخت successor نیست.
26.4. accounting_event_legs
id
accounting_event_id
leg_index
tigerbeetle_transfer_id
ledger_id
debit_account_id
credit_account_id
amount
transfer_code
flags
status
tigerbeetle_result_code
submitted_at
completed_at
created_at
Constraints:
UNIQUE(accounting_event_id, leg_index)
UNIQUE(tigerbeetle_transfer_id)
Array از transfer ID داخل accounting_events جایگزین این table نمیشود.
26.5. Spot Allocation
spot_execution_allocations
id
client_fill_id
source_type # VENUE_EXECUTION | BROKER_INVENTORY
venue_execution_id nullable
source_accounting_event_id nullable
base_quantity
quote_amount
created_at
این table امکان چند Venue Fill برای یک Client Fill، aggregation و همچنین تامین بخشی/تمام fill از Broker Inventory را میدهد. برای BROKER_INVENTORY، venue_execution_id باید null باشد و source_accounting_event_id به event داخلی اشاره کند.
26.6. Balance Cache
balance_cache فقط projection است:
NOT SOURCE OF TRUTH
Schema مفهومی:
balance_cache
tigerbeetle_account_id
debits_posted
credits_posted
debits_pending
credits_pending
economic_balance
last_accounting_event_id
refreshed_at
Refresh Rule
بعد از COMPLETED شدن Accounting Event:
- affected Account IDها استخراج شوند.
lookup_accountsبه شکل batch اجرا شود.- cache با cumulative values دریافتی overwrite شود.
refreshed_atثبت شود.
Staleness Baseline
normal target <= 2 seconds
hard stale threshold = 5 seconds
Cache قدیمیتر از 5 ثانیه:
STALE
محسوب میشود و نباید برای authorization مالی مثل withdrawal، margin lock یا order lock استفاده شود.
Financial authorization در نهایت توسط TigerBeetle transfer constraints enforce میشود.
26.7. User Financial Status
users.financial_status
ACTIVE
FROZEN
CLOSED
FROZEN gate عملیاتی است؛ TigerBeetle closed فقط طبق §9.3 استفاده میشود.
26.8. Physical Storage Contract
بخشهای قبلی §26 schema را در سطح مفهومی تعریف میکنند. این بخش نوع فیزیکی PostgreSQL را برای هر مفهوم TigerBeetle تثبیت میکند تا producerهای مختلف نوع متفاوت انتخاب نکنند.
| مفهوم TigerBeetle | عرض | نوع PostgreSQL |
|---|---|---|
id (account / transfer) |
u128 | uuid |
accounting_event_id |
u128 | uuid |
user_data_128 |
u128 | uuid |
amount |
u128 | numeric(39,0) |
user_data_64 / timestamp ms |
u64 | numeric(20,0) |
ledger |
u32 | bigint + CHECK BETWEEN 1 AND 4294967295 |
user_data_32 |
u32 | bigint + CHECK BETWEEN 0 AND 4294967295 |
code |
u16 | integer + CHECK BETWEEN 1 AND 65535 |
flags |
u16 | integer + CHECK BETWEEN 0 AND 65535 |
قاعده اجباری Amount
مقدار مالی هرگز در bigint ذخیره نمیشود.
bigint علامتدار ۶۴ بیتی است و سقف آن 9_223_372_036_854_775_807 است. با ETH scale = 18 طبق §5.2:
1 ETH = 10^18 units -> در bigint جا میشود
10 ETH = 10^19 units -> سرریز bigint
بنابراین یک withdrawal ساده ۱۰ ETH با bigint قابل ذخیره نیست. تمام ستونهای amount و تمام cumulative counterهای TigerBeetle باید numeric(39,0) باشند؛ 39 رقم برای بازه کامل u128 یعنی 2^128 - 1 کافی است.
numeric(39,0) نوع دقیق و integer-only است و نقض §2 یا §34.3 محسوب نمیشود. ممنوعیت floating point همچنان کامل برجاست: real، double precision و money در هیچ ستون مالی مجاز نیستند.
قاعده علامت
- ستونهای amount و cumulative counter:
CHECK (value >= 0)چون در TigerBeetle unsigned هستند. balance_cache.economic_balance: بدونCHECK >= 0، چون accountهایH onlyمانندSPOT_SETTLEMENT_CLEARINGطبق §9.5 عمداً میتوانند هر دو علامت را بگیرند.
Persistence Stack
driver: Npgsql
financial mapping: command/parameter و reader مستقیم Npgsql با نوع صریح
other mapping: Dapper برای جداول غیرمالی مجاز است
schema evolution: versioned raw SQL migration files
قواعد:
- هیچ ORM با change-tracking یا lazy loading روی جداول مالی (
accounting_accounts,accounting_events,accounting_event_legs,balance_cache) استفاده نمیشود. نوشتن مالی باید صریح، قابل بازبینی و قابل ردیابی در SQL باشد. - روی جداول مالی حتی micro-ORM هم برای mapping استفاده نمیشود و نوع هر parameter صریحاً
NpgsqlDbTypeتعیین میشود. دلیل:u128در این schema بهuuidوnumeric(39,0)نگاشت میشود و تبدیل ضمنی و خودکار نوع دقیقاً همان چیزی است که §10.5.2 دربارهٔ آن هشدار داده است. یک تبدیل ضمنی بهdecimalمقادیر بالای7.9e28را خراب میکند بدون اینکه خطایی بدهد. - migrationها فایل
.sqlنسخهدار و immutable هستند؛ یک migration اعمالشده هرگز ویرایش نمیشود و اصلاح فقط با migration جدید انجام میگیرد. این همان قاعده §0.6 در سطح schema است. - constraintهایی مانند partial unique index و
CHECKباید در خود SQL تعریف شوند، نه در لایه application، تا مستقل از code path تضمین شوند. - amount در C# با
System.UInt128نمایش داده میشود و تبدیل آن بهnumericباید در همان helper مرکزی §10.5.2 انجام شود.
26.9. Status و Enumeration Contract
§11 وضعیتهای accounting_events را شمارهگذاری کرده است، ولی accounting_event_legs.status و accounting_events.failure_class تا 0.004 مقدار مجاز مشخصی نداشتند. این بخش آنها را میبندد تا producerهای مختلف رشته دلخواه ننویسند.
Leg Status
PENDING # ID تولید و persist شده، هنوز submit نشده
SUBMITTING # داخل یک request در حال پرواز
COMPLETED # نتیجه ok یا exists با محتوای یکسان
FAILED # نتیجه terminal برای همین leg
PENDING قبل از اولین submit الزامی است، چون §10.1 حکم میکند Transfer ID قبل از submit persist شود.
Failure Class
مقدار failure_class فقط زمانی پر میشود که event به RETRYABLE_FAILED، PERMANENT_FAILED یا REQUIRES_REVIEW برود.
| مقدار | منشأ | Event Status | Retry همان ID |
|---|---|---|---|
INFRASTRUCTURE |
outcome نامعلوم، §10.4 حالت A | RETRYABLE_FAILED |
بله، الزامی |
TB_TRANSIENT_ID_BURN |
یکی از هفت result §10.4 حالت B | PERMANENT_FAILED |
خیر، successor لازم است |
TB_ID_ALREADY_FAILED |
دریافت id_already_failed |
PERMANENT_FAILED |
خیر، successor لازم است |
TB_CONTENT_MISMATCH |
هر exists_with_different_* §10.3 |
REQUIRES_REVIEW |
خیر، retry خودکار ممنوع |
TB_VALIDATION |
rejection قطعی و deterministic مانند ledger_must_not_be_zero |
PERMANENT_FAILED |
خیر، این یک bug است |
قواعد:
TB_CONTENT_MISMATCHهرگز retry خودکار نمیگیرد و طبق §10.3 alert بحرانی تولید میکند.TB_VALIDATIONیعنی payload تولیدشده نامعتبر بوده است؛ successor فقط بعد از fix کد مجاز است، نه به صورت خودکار.- فقط
INFRASTRUCTUREاجازه retry با همان Transfer IDها را میدهد. - این enumeration append-only است؛ حذف یا تغییر معنای مقدار موجود ممنوع است.
Leg Flags
accounting_event_legs.flags دقیقاً همان مقداری است که به TigerBeetle submit میشود، شامل بیت linked.
دلیل: exists_with_different_flags طبق §10.3 یک Critical Data Integrity Error است. اگر flags هنگام submit به صورت derived محاسبه شود، دو نسخه از کد میتوانند برای همان leg مقدار متفاوتی بسازند و retry به این خطا برخورد کند. با persist کردن مقدار نهایی، retry بایتبهبایت همان چیزی را میفرستد که بار اول فرستاده شد.
نتیجه برای chain: طبق §27.4 هر Accounting Event یک linked chain واحد در یک create_transfers request است، بنابراین در زمان ساخت legها:
leg[0 .. n-2] -> flags شامل linked
leg[n-1] -> flags بدون linked # closing leg زنجیره
n == 1 -> flags بدون linked
submitter باید این ساختار را قبل از ارسال verify کند و در صورت نقض، event را بدون submit به REQUIRES_REVIEW ببرد؛ یک chain نیمهباز linked_event_chain_open میگیرد و کل event را بیدلیل میسوزاند.
Owner Type
مقادیر مجاز accounting_accounts.owner_type بر اساس چهار دسته user_data_128 در §9.1:
USER
VENUE
CUSTODY_GROUP
SYSTEM
نگاشت per-code از 0.007 طبق D-026 بسته است. AccountProvisioner میتواند owner_type را از کد بخواند؛ caller همچنان حق دارد همان مقدار را صریح بدهد ولی حق ندارد مقدار دیگری بنویسد.
26.10. Account Provisioning Sequence
ترتیب اجباری برای ساخت یک Account:
1. caller supplies owner_type, owner_id, asset_id, account_type, partition_key
2. generate TigerBeetle time-based account id
3. persist accounting_accounts row FIRST
4. create_accounts with:
flags = exact §9.5 matrix for that code
user_data_128 = owner_id
user_data_64 = 0
user_data_32 = 0
5. Created or identical Exists => success
6. exists_with_different_* => stop; do not recreate; treat as integrity incident
دلیل persist-before-create: §9.1 id را قبل از create_accounts در accounting_accounts میخواهد. اگر cluster پاسخ ندهد، retry همان id را میفرستد؛ ساخت id جدید بعد از یک create مبهم همان خطر §10.4 case A برای transfer است.
Idempotency: ردیف موجود با همان (owner_type, owner_id, asset_id, account_type, partition_key) دوباره با همان tigerbeetle_account_id submit میشود، نه با id تازه.
Change History — PostgreSQL Schema
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Schema list اولیه |
| 0.002 | 2026-09-12 | افزودن instruments، accounting_event_legs، worker lease، spot allocations و cache refresh contract |
| 0.003 | 2026-09-12 | افزودن supersedes_event_id/failure_class و allocation source_type برای Broker Inventory |
| 0.005 | 2026-09-12 | افزودن partition_key به accounting_accounts و بخش 26.8 physical storage contract با الزام numeric(39,0) برای amount |
| 0.006 | 2026-09-12 | §26.10 توالی persist-then-create_accounts؛ owner_type ورودی صریح provisioning است |
27. Cross-System Consistency و TigerBeetle Client Model
Introduced: 0.001
Last Changed: 0.002
Status: Designed
Distributed transaction واقعی بین این سیستمها وجود ندارد:
PostgreSQL
TigerBeetle
LP / Venue
Blockchain
بنابراین نباید فرض شود یک ACID transaction همه آنها را پوشش میدهد.
27.1. Durable Financial Intent
برای Financial Event:
- Business event دریافت شود.
accounting_eventدر PostgreSQL persist شود.- تمام
accounting_event_legsو TigerBeetle IDها persist شوند. - Worker event را با lease claim کند.
- تمام legهای نیازمند atomicity در یک
create_transferscall ساخته شوند. - operation به TigerBeetle ارسال شود.
- response مطابق §10.3 classify شود.
created/okیا validexists=> leg success.- بعد از success همه legها => event
COMPLETED. - cache refresh schedule شود.
- crash در هر مرحله recoverable باشد.
Network ambiguity با retry همان ID حل میشود.
27.2. Venue Fill
Venue Fill باید identifier یکتا داشته باشد:
UNIQUE(venue_id, venue_execution_id)
Duplicate venue event نباید Accounting Event جدید ایجاد کند.
Client Fill و Venue Fill الزاماً 1:1 نیستند.
27.3. TigerBeetle Client Concurrency
TigerBeetle client باید shared باشد.
ممنوع:
HTTP request
↓
new TigerBeetle Client()
Baseline:
Accounting Service Process
│
└── one shared TigerBeetle client per cluster/config
Client بین thread/taskها share میشود تا automatic batching انجام شود.
TigerBeetle client در هر لحظه حداکثر یک request در-flight دارد؛ throughput از batching eventها در request حاصل میشود، نه ساخت تعداد زیادی client per request.
27.4. Batch و Linked Chain
Default maximum برای create_transfers:
8189 events / request
برای operation atomic:
one accounting event
↓
all linked legs
↓
one create_transfers request
Agent حق ندارد chain را بین چند request split کند و همچنان آن را atomic بنامد.
27.5. Worker Recovery
locked_until lease باید finite باشد.
اگر worker crash کند:
locked_until expires
↓
another worker claims event
↓
same persisted transfer IDs submitted/reconciled
SUBMITTING به معنای «حتماً ثبت نشده» نیست؛ بعد از crash باید response/idempotency semantics در نظر گرفته شود.
Change History — Cross-System Consistency
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Durable accounting event و retry اولیه |
| 0.002 | 2026-09-12 | افزودن client sharing، automatic batching، one in-flight request، 8189 limit و worker lease recovery |
28. Reconciliation
Introduced: 0.001
Last Changed: 0.003
Status: Designed
Reconciliation یک feature جانبی نیست؛ بخشی از طراحی اصلی سیستم مالی است.
تمام Accountهای production باید flags.history داشته باشند تا historical balance inspection ممکن باشد.
28.1. TigerBeetle Invariant
برای هر ledger:
Σ debits_posted = Σ credits_posted
28.2. Economic Balance Formula
Asset account:
asset_balance =
debits_posted - credits_posted
Liability account:
liability_balance =
credits_posted - debits_posted
Revenue:
revenue_balance =
credits_posted - debits_posted
Expense:
expense_balance =
debits_posted - credits_posted
28.3. Blockchain Reconciliation
برای هر custody wallet/group:
TigerBeetle Custody Asset
VS
Blockchain observed balance
CUSTODY_DEPOSIT باید به wallet/address group واقعی متصل باشد؛ Deposit مستقیماً به Hot Wallet نسبت داده نمیشود مگر مقصد واقعی chain همان Hot Wallet باشد.
نمونه:
TB CUSTODY_DEPOSIT_BTC:W1 = 1.25000000
Chain wallet group W1 = 1.25000000
difference = 0
Sweep:
CUSTODY_DEPOSIT -> CUSTODY_HOT
یک transfer مستقل است و باید با chain movement قابل تطبیق باشد.
هر Difference خارج از tolerance تعریفشده باید Alert ایجاد کند.
28.4. Venue Reconciliation
برای هر Venue و Asset:
TigerBeetle BINANCE_USDT_ASSET
VS
Binance API balance
حداقل:
- cash balance
- locked balance در صورت relevant بودن
- realized pnl
- fees
- funding
- transfer in/out
- in-flight deposit/withdrawal
28.5. User Liability Reconciliation
برای هر Asset:
Σ User Available
+ Σ User Spot Locked
+ Σ User Margin
+ Σ User Withdrawal Locked
باید با total customer liability reconcile شود.
28.6. Spot Settlement Clearing Reconciliation
برای هر Asset:
SPOT_SETTLEMENT_CLEARING
باید در steady-state صفر باشد.
هر non-zero balance باید به یکی از موارد زیر trace شود:
client fill not yet allocated
venue fill not yet allocated
partial venue execution
price residual pending classification
incident under review
Reconciliation باید بتواند از spot_execution_allocations به Client Fill و Venue Fill برگردد.
Aging برای clearing itemها باید اندازهگیری شود. عبور از hard threshold طبق §20.4 باید علاوه بر Critical Alert، circuit breaker ایجاد Client Spot Settlement جدید را فعال کند؛ این کنترل optional نیست.
28.7. Operational Solvency View
برای هر Asset:
Custody Assets
+ Venue Assets
+ In-Transit Assets
+ Other Receivables
--------------------------------
Total Controlled / Claimed Assets
Customer Liabilities
--------------------------------
Net Broker Inventory / Equity
CLIENT_BAD_DEBT_RECEIVABLE باید جداگانه نمایش داده شود و با liquid asset اشتباه گرفته نشود.
P&L، Expense، Revenue و Reserve accountها باید در financial view نهایی لحاظ شوند.
Change History — Reconciliation
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Blockchain/Venue/User reconciliation اولیه |
| 0.002 | 2026-09-12 | افزودن historical-balance requirement، deposit-wallet mapping و Spot Clearing reconciliation |
| 0.003 | 2026-09-12 | ارتقای Spot Clearing alert به hard reconciliation/circuit-breaker control |
29. Manual Adjustment و Correction
Introduced: 0.001
Last Changed: 0.001
Status: Designed
هیچ admin endpoint نباید مستقیماً balance را تغییر دهد.
Adjustment همیشه Transfer است.
الزام metadata:
adjustment_id
original_event_id
reason
ticket_id
requested_by
approved_by
created_at
برای مبلغهای حساس، Maker/Checker approval پیشنهاد میشود.
30. Observability و Audit Logging
Introduced: 0.001
Last Changed: 0.002
Status: Designed
هر log مالی باید در صورت relevant بودن این فیلدها را داشته باشد:
accounting_event_id
supersedes_event_id
failure_class
tigerbeetle_transfer_id
accounting_leg_index
source_system_code
user_id
order_id
fill_id
position_id
hedge_order_id
hedge_fill_id
deposit_id
withdrawal_id
tx_hash
network_id
venue_id
venue_execution_id
asset_id
amount
service
trace_id
نباید در log ذخیره شوند:
private_key
seed_phrase
API secret
signing secret
full sensitive credential
31. Security Rules
Introduced: 0.001
Last Changed: 0.003
Status: Designed
- Private Key از API و Accounting Service جدا باشد.
- Wallet signing در component جدا یا custody provider انجام شود.
- Venue credentialها secret-managed باشند.
- Manual Adjustment فقط با permission مشخص.
- Production correction نیازمند audit trail.
- Financial endpoints باید idempotency داشته باشند.
- هیچ endpoint عمومی نباید TigerBeetle Account/Transfer دلخواه ایجاد کند.
- Amount validation قبل از TigerBeetle انجام شود.
- Asset/Ledger mismatch باید fail شود.
debit_account_id == credit_account_idباید قبل از submit در application layer reject شود؛ Internal Transfer به همان Account مجاز نیست.- Registry generator نباید
ledger = 0یاcode = 0تولید کند. - Amount منفی در domain model وجود ندارد؛ direction فقط با debit/credit تعیین میشود.
- normal posted leg با
amount = 0طبق §12.1 emit نمیشود.
32. API / Service Contract پیشنهادی Accounting Service
Introduced: 0.001
Last Changed: 0.002
Status: Designed
API دقیق میتواند RPC/HTTP/Event-driven باشد، اما use-caseهای زیر باید وجود داشته باشند:
ensureUserAssetAccounts()
creditConfirmedDeposit()
moveCustodyAsset()
lockSpotOrder()
releaseSpotOrderLock()
settleSpotClientBuyFill()
settleSpotClientSellFill()
settleSpotVenueBuyExecution()
settleSpotVenueSellExecution()
settleSpotInventoryFill()
recordSpotExecutionAllocation()
settleSpotResidual()
chargeTradingFee()
recordVenueExecutionFee()
lockMargin()
releaseMargin()
settleClientRealizedPnl()
settleHedgeRealizedPnl()
settleClientFunding()
settleVenueFunding()
settleLiquidation()
recognizeBadDebt()
writeOffBadDebt()
lockWithdrawal()
releaseWithdrawal()
markWithdrawalBroadcast()
settleConfirmedWithdrawal()
moveAssetToVenue()
moveAssetFromVenue()
createCorrection()
getUserBalance()
getAccountBalance()
runReconciliation()
تمام write commandها باید:
operation_id
occurred_at
source_system_code
داشته باشند.
operation_id باید قبل از Financial Submit پایدار و idempotent باشد.
از 0.007 این مقدار در PostgreSQL به صورت (aggregate_type, aggregate_id) ذخیره میشود و unique است. تکرار همان command همان event را برمیگرداند، نه یک posting دوم.
32.1. HTTP Command API (local / integration)
پروتکل local برای تست دستی: JSON روی HTTP. هیچ endpoint عمومی حق ندارد debit/credit دلخواه بسازد؛ فقط commandهای زیر مجازند.
POST /v1/users/{userId}/ensure
POST /v1/deposits
POST /v1/transfers
POST /v1/custody/moves
POST /v1/margin/locks
POST /v1/margin/releases
POST /v1/pnl/client
POST /v1/positions
POST /v1/positions/{id}/close
GET /v1/users/{userId}/balances?asset=USDT
GET /v1/events/{id}
GET /v1/positions/{id}
Amountها integer خرد داراییاند (مثلاً USDT scale 6: 1000000 = 1 USDT).
POST /v1/positions فقط USER_AVAILABLE → USER_MARGIN را میزند و ردیف positions را در PostgreSQL مینویسد. Unrealized PnL به ledger نمیرود.
از 0.008 commandهای باقیماندهٔ §32 هم HTTP دارند. Spot client/venue settlement یک Accounting Event است ولی هر leg ledger خودش را دارد؛ TigerBeetle فقط داخل یک transfer همان ledger را اجباری میکند، نه برای کل chain.
POST /v1/withdrawals/lock
POST /v1/withdrawals/release
POST /v1/withdrawals/broadcast
POST /v1/withdrawals/confirm
POST /v1/spot/locks
POST /v1/spot/releases
POST /v1/spot/client-buy
POST /v1/spot/client-sell
POST /v1/spot/venue-buy
POST /v1/spot/venue-sell
POST /v1/spot/inventory-buy
POST /v1/spot/inventory-sell
POST /v1/spot/spread
POST /v1/fees/trading
POST /v1/fees/venue
POST /v1/venue/in
POST /v1/venue/out
POST /v1/hedge/pnl
POST /v1/funding/client
POST /v1/funding/venue
POST /v1/liquidations
POST /v1/bad-debt/writeoff
POST /v1/corrections
GET /v1/reconciliation/{asset}
GET /ops
GET /ops/snapshot
GET /ops/stream
GET /docs
GET /docs/glossary
GET /docs/architecture
/ops کنسول عملیاتی local است. Snapshot زنده PostgreSQL و TigerBeetle را نشان میدهد و از 0.010 همان commandهای نامدار §32 را از فرمهای همین صفحه صدا میزند. Debit/credit دلخواه همچنان ممنوع است؛ فقط همان endpointهای نامدار. پاک کردن داده از کنسول نیست. معنی نام فرمانها و حسابها: BROKER_ACCOUNTING_GLOSSARY_FA.md.
/docs همان دو فایل markdown را بهصورت HTML سرو میکند.
سرویس compose بهنام accounting ایمیج API را بالا میآورد و روی 127.0.0.1:5080 publish میکند. Nginx میزبان accounting.m0oo0m.xyz کل / را به http://accounting/ میفرستد (deploy/nginx-accounting.conf). این hostname کنسول و /v1 را هم نشان میدهد. آدرس TigerBeetle میتواند hostname باشد؛ قبل از ساخت client به IPv4 resolve میشود چون native client فقط IP میپذیرد. native client هم io_uring میخواهد؛ کانتینر accounting مثل replica با seccomp:unconfined اجرا میشود. اگر هسته kernel.io_uring_disabled را روشن کرده باشد باید روی host خاموش شود.
شروع دوبارهٔ local: volumeهای compose را دور بریز و stack را بالا بیاور.
docker compose down -v && docker compose up -d
Venue پیشفرض local: 33333333-3333-3333-3333-333333333333.
POST /v1/positions/{id}/close با realizedPnl علامتدار:
realizedPnl > 0 -> CLIENT_PROFIT_EXPENSE → USER_AVAILABLE
سپس USER_MARGIN → USER_AVAILABLE (آزادسازی مانده)
realizedPnl < 0 -> collectible از USER_MARGIN سپس USER_AVAILABLE → CLIENT_LOSS_REVENUE
سپس ماندهٔ margin آزاد میشود
shortfall → CLIENT_BAD_DEBT_RECEIVABLE + BAD_DEBT_EXPENSE طبق §24
realizedPnl = 0 -> فقط آزادسازی margin
اولویت مصرف ضرر از 0.007: اول USER_MARGIN بعد USER_AVAILABLE (baseline §22.3).
32.2. Local identity defaults
برای محیط dev، در نبود caller صریح:
BROKER_SYSTEM_ID = 11111111-1111-1111-1111-111111111111
DEFAULT_CUSTODY_ID = 22222222-2222-2222-2222-222222222222
DEFAULT_VENUE_ID = 33333333-3333-3333-3333-333333333333
user_data_128 حسابهای SYSTEM همان BROKER_SYSTEM_ID است؛ حسابهای custody پیشفرض همان DEFAULT_CUSTODY_ID؛ حسابهای venue پیشفرض همان DEFAULT_VENUE_ID.
Spot Client Settlement و Spot Venue Settlement API جدا هستند؛ API واحدی که implicitly یک Client Fill را به یک Venue Fill bind کند مجاز نیست.
Change History — Accounting API
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Use-case list اولیه |
| 0.002 | 2026-09-12 | تفکیک Client/Venue Spot settlement و افزودن bad-debt/liquidation operations |
| 0.007 | 2026-09-12 | HTTP JSON برای deposit/transfer/margin/position |
| 0.008 | 2026-09-12 | HTTP برای withdrawal، spot، venue، hedge، funding، liquidation، correction، reconciliation؛ DEFAULT_VENUE_ID |
| 0.009 | 2026-09-12 | کنسول /ops فقطخواندنی با snapshot زنده PostgreSQL + TigerBeetle |
| 0.010 | 2026-09-12 | فرم commandهای نامدار §32 داخل /ops |
| 0.011 | 2026-09-12 | POST /ops/reset برای شروع دوباره تست local |
| 0.012 | 2026-09-12 | reset محلی فایل data تایگر را هم از نو format میکند |
| 0.013 | 2026-09-12 | reset از /ops حذف شد؛ wipe محلی با docker compose down -v |
| 0.014 | 2026-09-12 | واژهنامهٔ کنسول در BROKER_ACCOUNTING_GLOSSARY_FA.md |
| 0.015 | 2026-09-12 | /docs HTML برای واژهنامه و معماری؛ دامنه فقط همین مسیر |
| 0.016 | 2026-09-12 | ایمیج API و make push به رجیستری داکر |
| 0.017 | 2026-09-12 | edge دامنه با Nginx میزبان؛ Caddy compose حذف شد |
| 0.018 | 2026-09-12 | سرویس compose accounting و Nginx accounting.m0oo0m.xyz |
| 0.019 | 2026-09-12 | resolve hostname تایگر به IPv4؛ libgssapi در ایمیج runtime |
| 0.020 | 2026-09-12 | seccomp:unconfined برای کانتینر accounting بهخاطر io_uring کلاینت |
| 0.021 | 2026-09-12 | کانتینر accounting: IPC_LOCK/SYS_ADMIN و listen روی 0.0.0.0 |
33. State Ownership
Introduced: 0.001
Last Changed: 0.002
Status: Designed
| Data | Source of Truth |
|---|---|
| User financial balance | TigerBeetle |
| Available balance | TigerBeetle |
| Spot locked amount | TigerBeetle |
| Margin locked amount | TigerBeetle |
| Financial transfer history | TigerBeetle |
| Order state | PostgreSQL |
| Fill | PostgreSQL |
| Position | PostgreSQL |
| Entry Price | PostgreSQL |
| Mark Price | Market/Risk System |
| Unrealized PnL | Risk/Position Service |
| Hedge position | PostgreSQL/Venue projection |
| Blockchain transaction | PostgreSQL + Blockchain |
| Tx finality | Blockchain Integration |
| Venue execution | PostgreSQL + Venue |
| Reconciliation result | PostgreSQL |
| Spot settlement clearing balance | TigerBeetle |
| Balance cache | PostgreSQL projection only |
34. Forbidden Patterns
Introduced: 0.001
Last Changed: 0.003
Status: Designed
این موارد ممنوع هستند:
34.1. Direct balance mutation
user.balance -= 100
بدون Financial Transfer.
34.2. PostgreSQL balance as source of truth
استفاده از PostgreSQL balance/cache به عنوان Financial Source of Truth ممنوع است.
34.3. Floating point money
استفاده از float / double برای money/asset ممنوع است.
34.4. Mutating history
Delete یا update کردن Transfer مالی برای اصلاح اشتباه ممنوع است.
34.5. Unrealized PnL posting
ثبت Unrealized PnL با هر market tick در TigerBeetle ممنوع است.
34.6. PnL netting before accounting
Net کردن Client PnL و Hedge PnL قبل از Accounting ممنوع است.
34.7. Fee netting before accounting
Net کردن Client Fee، Venue Fee، Funding یا Network Fee قبل از Accounting ممنوع است.
34.8. Withdrawal identity mistake
فرض:
withdrawal_id == tx_hash
ممنوع است.
34.9. New ID on ambiguous retry
ساخت Transfer ID جدید برای retry یک submission با outcome نامعلوم ممنوع است.
34.10. Financial write without durable intent
اجرای تغییر مالی بدون accounting_event_id و persisted leg IDs ممنوع است.
34.11. Direct Client-to-Venue Spot dependency
Spot client settlement نباید مستقیماً وابسته به یک Venue Fill مشخص باشد.
ممنوع:
1 client fill -> must equal exactly 1 venue fill
34.12. Splitting linked chains
تقسیم linked chain بین دو create_transfers request و ادعای atomicity ممنوع است.
34.13. Client-per-request
ساخت TigerBeetle client برای هر HTTP/RPC request ممنوع است.
34.14. Ignoring content mismatch
تبدیل exists_with_different_* به success/retry عادی ممنوع است.
این خطا Critical Data Integrity Incident است.
34.15. Provisioning without history
ساخت Account production بدون:
flags.history = true
ممنوع است مگر یک Decision Log جدید صریحاً استثنا تعریف کند.
34.16. Changing immutable semantics silently
تغییر موارد زیر بدون ledger/account migration و Document-First change ممنوع است:
asset scale
account code meaning
ledger meaning
user_data mapping
account flags baseline
34.17. Reusing partial IDs after a linked-chain burn
اگر یک leg explicit ID-burning error بگیرد، استفاده مجدد از ID سایر legهای همان failed Accounting Event برای successor ممنوع است.
Successor باید event جدید با تمام Transfer IDهای جدید باشد.
34.18. Normal zero-value posted legs
Emit کردن posted business transfer با amount = 0 صرفاً برای ثابت نگهداشتن تعداد legها ممنوع است. Zero فقط در API flowهایی که معنای خاص مستند دارد مجاز است.
34.19. Self-transfer / zero registry values
ارسال Transfer با debit و credit یکسان یا تولید ledger=0 / code=0 ممنوع است و باید قبل از TigerBeetle reject شود.
Change History — Forbidden Patterns
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Forbidden list اولیه |
| 0.002 | 2026-09-12 | افزودن client/venue decoupling، linked-request atomicity، history flag و integrity mismatch rules |
| 0.003 | 2026-09-12 | ممنوعیت reuse بخشی از linked IDs پس از burn، zero-value normal legs و self-transfer/zero registry values |
35. تستهای اجباری
Introduced: 0.001
Last Changed: 0.003
Status: Designed
حداقل integration/chaos testها:
Account Provisioning
- تمام accountهای production با
historyساخته شوند - user_data mapping مطابق §9 باشد
- account code/ledger mismatch reject شود
- duplicate account creation idempotent باشد
- self-transfer در application layer reject شود
ledger=0وcode=0در registry/provisioning reject شوند- هر Account Code دقیقاً flagهای جدول §9.5 را داشته باشد
- provisioning با flag متفاوت از matrix fail شود
- دو account با
partition_keyیکسان برای همان(owner, asset, type)توسط unique constraint reject شوند - دو account با
partition_keyمتفاوت برای همان(owner, asset, type)مجاز باشند partition_key = NULLدر سطح schema reject شود
Idempotency
- retry همان Transfer باعث double posting نشود
existsبا payload یکسان success محسوب شود- هر
exists_with_different_*event را بهREQUIRES_REVIEWببرد - failed semantic retry با payload جدید و ID قبلی ممنوع باشد
exceeds_creditsیک Transfer ID را بسوزاند و retry همان ID بهid_already_failedبرسد- هر یک از هفت ID-burning result با client >= 0.16.4 classification صحیح داشته باشد
- failure یک leg در linked chain کل Accounting Event را
PERMANENT_FAILEDکند - successor event دارای
supersedes_event_idصحیح و تمام Transfer IDهای جدید باشد - هیچ ID از linked chain شکستخورده در successor reuse نشود
- unknown/ambiguous response با همان IDها retry شود و successor نسازد
Balance
- overdraft User رد شود
- negative constrained Broker Asset رد شود
- Clearing account بتواند موقتاً debit/credit باشد
- ذخیره و بازخوانی amount برابر
2^128 - 1درnumeric(39,0)بدون از دست رفتن دقت انجام شود - amount معادل ۱۰ ETH با
scale = 18یعنی10^19بدون سرریز ذخیره شود - تلاش برای ذخیره amount منفی توسط
CHECKدر سطح schema reject شود CLIENT_BAD_DEBT_RECEIVABLEو Asset/In-Flight accounts از flag matrix تجاوز نکنند- normal zero-amount business leg emit نشود
Deposit
- confirmed deposit یک بار credit شود
- source همیشه custody destination account صحیح باشد
- duplicate chain event دوباره credit نشود
- sweep به hot transfer جدا داشته باشد
- reorg correction قابل ثبت باشد
- reorg freeze application-level فعال شود
Spot
- Buy full fill
- Sell full fill
- partial fill
- multiple partial fills
- cancel without fill
- cancel after partial fill
- one Client Fill -> multiple Venue Fills
- multiple Client Fills -> aggregate Venue execution
- multi-venue execution
- delayed Venue Fill
- client settlement بدون Venue 1:1 dependency
- Client Buy کاملاً از Broker Inventory
- Client Sell کاملاً به Broker Inventory
- Client Fill ترکیبی از Broker Inventory و چند Venue
- Broker Inventory reserve نتواند بیش از موجودی اقتصادی خود debit شود
- allocation با
source_type=BROKER_INVENTORYvenue_execution_id جعلی نداشته باشد - price improvement expense
- positive spread revenue
- client trading fee
- venue fee
- clearing returns to zero after full allocation
- stale clearing balance alert
- hard clearing threshold circuit breaker ایجاد Client Spot Settlement جدید را متوقف کند
- settlement/reconciliation/correction در حالت circuit breaker ادامه پیدا کند
- one failed linked leg causes whole linked chain to fail
- linked legs are sent in one request
Position / A-Book
- margin lock
- margin release
- realized profit
- realized collectible loss
- loss greater than user funds creates bad-debt receivable
- liquidation does not deadlock on
exceeds_credits - bad-debt writeoff
- client PnL independent of hedge PnL
- positive hedge PnL
- negative hedge PnL
- multiple hedge fills for one client exposure
- one hedge fill allocated to multiple client exposures
Funding
- client pays funding
- client receives funding
- venue pays funding
- venue receives funding
- client and venue funding are never pre-netted
Withdrawal
- lock
- reject before broadcast
- successful broadcast/confirm
- network fee
- user withdrawal fee
- RBF/replacement
- one withdrawal with multiple tx_hash
- duplicate confirmation event
- crash after broadcast
- crash after TigerBeetle write
- insufficient User balance
- no automatic release after broadcast
- principal/network-fee confirmation قبل از completed شدن broadcast accounting submit نشود
- RBF fee delta قبل از confirmation به IN_FLIGHT اضافه شود
- final linked confirmation IN_FLIGHT را به صفر برگرداند
TigerBeetle Client Compatibility
- Accounting implementation با client رسمی .NET (
tigerbeetleNuGet) ساخته شود - dependency version کمتر از
0.16.4در CI reject شود - upgrade client create-transfer result semantics tests را اجرا کند
Clientبه صورت singleton resolve شود و scoped/transient registration تست را fail کند- round-trip تبدیل
UInt128↔Guid↔ PostgreSQLuuidبرای مقادیر مرزی صحیح باشد id = 0وid = 2^128 - 1قبل از submit reject شوند- flagهای هر Account Code از §9.5 به مقدار صحیح
AccountFlagsنگاشت شوند
Worker Recovery
- lease expiry allows another worker to claim
- crash in
SUBMITTINGdoes not duplicate money - same persisted leg IDs reused
- concurrent workers cannot claim same event
Balance Cache
- refresh after COMPLETED
- cache > 5s is marked stale
- stale cache cannot authorize withdrawal/order/margin
Reconciliation
- blockchain mismatch detect شود
- venue mismatch detect شود
- in-transit stale balance detect شود
- spot clearing stale balance detect شود
- bad debt receivable separately reported شود
- historical account balance query works for all production accounts
Historical Import
اگر migration mode = HISTORICAL_IMPORT:
- imported/live events never mix in a batch
- timestamp unique/past/strictly increasing
- full migration reconciliation before enabling live writer
Change History — Tests
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Test matrix اولیه |
| 0.002 | 2026-09-12 | افزودن multi-venue spot، ID mismatch، client batching, bad debt, history و migration tests |
36. Crash / Retry Scenarios
Introduced: 0.001
Last Changed: 0.002
Status: Designed
Agent باید حداقل این crash pointها را تست کند:
1. accounting_event persisted ولی legs هنوز کامل persist نشدهاند
2. event + legs persisted، قبل از TigerBeetle submit
3. بعد از submit ولی قبل از response
4. بعد از TigerBeetle success ولی قبل از PostgreSQL status update
5. بعد از event COMPLETED ولی قبل از balance_cache refresh
6. Venue fill دریافت شده ولی قبل از accounting
7. Client Spot Settlement انجام شده ولی Venue Settlement هنوز نرسیده
8. Venue Settlement انجام شده ولی allocation update crash کرده
9. Blockchain tx broadcast شده ولی قبل از ذخیره tx_hash
10. Withdrawal confirmed شده ولی قبل از accounting completion
11. Worker در حالت SUBMITTING crash کرده و lease منقضی شده
در تمام موارد:
durable event
+
durable leg IDs
+
TigerBeetle idempotency
+
worker lease
=
recoverable operation
36.1. Ambiguous Response
اگر درخواست ارسال شده ولی response قابل acknowledge نیست:
- payload تغییر نمیکند.
- Transfer ID تغییر نمیکند.
- retry با همان intent انجام میشود.
36.2. Existing Object
اگر result:
exists
و محتوا یکسان است، recovery success است.
اگر result:
exists_with_different_*
باشد:
REQUIRES_REVIEW
critical alert
no blind retry
Change History — Crash & Retry
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | crash pointهای اولیه |
| 0.002 | 2026-09-12 | افزودن lease recovery، Spot decoupling crash points و exists mismatch handling |
37. Definition of Done برای هر Feature مالی
Introduced: 0.001
Last Changed: 0.001
Status: Designed
یک feature مالی Done نیست مگر اینکه:
- این سند قبل از code تغییر کرده باشد.
- Version سند bump شده باشد.
- Global Change Log بروزرسانی شده باشد.
- Local Change History بخش مربوطه بروزرسانی شده باشد.
- TigerBeetle Account/Transfer flow مستند باشد.
- PostgreSQL state changes مستند باشد.
- Idempotency strategy مشخص باشد.
- Retry strategy مشخص باشد.
- Reconciliation impact مشخص باشد.
- Failure states مشخص باشند.
- Integration test نوشته شده باشد.
- Crash/retry test در صورت مالی بودن flow نوشته شده باشد.
- implementation با همین سند تطبیق داده شده باشد.
38. قالب ثبت تغییر توسط Agent
هر تغییر باید در Global Change Log با این قالب ثبت شود:
## Version 0.00X — YYYY-MM-DD
### Changed
- ...
### Reason
- ...
### Affected Components
- ...
### Database / Migration
- ...
### Accounting Impact
- ...
### Reconciliation Impact
- ...
### Tests
- ...
### Compatibility
- ...
39. قالب Change History محلی
برای هر بخش:
### Change History — <Section Name>
| Version | Date | Change |
|---|---|---|
| 0.001 | 2026-09-12 | Initial design |
| 0.002 | 2026-09-15 | ... |
Agent نباید entry قدیمی را overwrite کند.
اگر تصمیم قبلی غلط یا منسوخ شد:
Deprecated in 0.00X
Replaced by ...
ثبت شود.
40. Decision Log
Introduced: 0.001
Last Changed: 0.003
Status: Active
D-001 — TigerBeetle Financial Source of Truth
Version: 0.001
تصمیم:
تمام balanceهای مالی authoritative در TigerBeetle هستند.
دلیل:
- auditability
- immutable transfer history
- strong accounting invariants
- idempotent transfer IDs
D-002 — PostgreSQL برای Trading State
Version: 0.001
Position، Order، Fill، Blockchain Tx و Venue Execution در PostgreSQL نگهداری میشوند.
D-003 — Unrealized PnL خارج از Ledger
Version: 0.001
Unrealized PnL توسط Position/Risk Engine محاسبه میشود و فقط Realized PnL settle میشود.
D-004 — Spot Lock Account
Version: 0.001
Spot reservation با:
USER_AVAILABLE → USER_SPOT_LOCKED
انجام میشود، نه یک Pending Transfer بلندمدت.
دلیل اصلی: پشتیبانی طبیعی از multiple partial fills.
D-005 — A-Book Client/Hedge Separation
Version: 0.001
Client financial result و Hedge financial result به صورت مستقل ثبت میشوند.
D-006 — Withdrawal Business ID
Version: 0.001
withdrawal_id مستقل از tx_hash است.
D-007 — Fee Transparency
Version: 0.001
Spread، Client Fee، Venue Fee، Funding و Network Fee legهای مستقل حسابداری هستند و نباید در exchange rate یا net value پنهان شوند.
D-008 — Spot Client/Venue Decoupling
Version: 0.002
Client Spot Settlement و Venue Settlement از طریق:
SPOT_SETTLEMENT_CLEARING
از یکدیگر جدا هستند.
سیستم هیچ فرض 1:1 بین Client Fill و Venue Fill ندارد.
D-009 — Production Accounts Require History
Version: 0.002
تمام Accountهای production با:
flags.history = true
ساخته میشوند.
ساخت account بدون history نیازمند Decision Log صریح جدید است.
D-010 — TigerBeetle Time-Based IDs + Persisted Leg IDs
Version: 0.002
Account و Transfer IDها از time-based scheme توصیهشده TigerBeetle استفاده میکنند.
هر Transfer ID قبل از submit در accounting_event_legs persist میشود.
Grouping توسط:
Transfer.user_data_128 = accounting_event_id
انجام میشود.
D-011 — Withdrawal Uses Posted State Accounts, Not Pending Transfer
Version: 0.002
Withdrawal lifecycle با:
USER_AVAILABLE
USER_WITHDRAWAL_LOCKED
ONCHAIN_OUTBOUND_IN_FLIGHT
مدل میشود.
دلیل: RBF/replacement و چند tx_hash برای یک withdrawal با one-shot pending resolution سازگار نیست.
D-012 — Historical Migration Is Pre-Go-Live
Version: 0.002
Migration mode باید قبل از live writes مشخص شود:
FRESH_START
HISTORICAL_IMPORT
Historical import بعد از شروع ترافیک عادی به عنوان migration معمولی مجاز نیست.
D-013 — Shared TigerBeetle Client
Version: 0.002
هر process یک TigerBeetle client shared برای cluster/config دارد.
Client-per-request ممنوع است.
Throughput بر batching تکیه میکند.
D-014 — Immutable user_data Mapping
Version: 0.002
Account:
user_data_128 = canonical entity id
user_data_64 = 0
user_data_32 = 0
Transfer:
user_data_128 = accounting_event_id
user_data_64 = occurred_at_ms
user_data_32 = source_system_code
تغییر این معنا برای objectهای جدید بدون migration/versioned schema ممنوع است.
D-015 — Explicit Bad Debt Path
Version: 0.002
Loss بیشتر از User funds نباید با debit نامعتبر به User account ثبت شود.
realized_loss =
collectible_loss
+ bad_debt_shortfall
Shortfall از مسیر:
CLIENT_BAD_DEBT_RECEIVABLE
عبور میکند و liquidation را block نمیکند.
D-016 — Go + TigerBeetle Client Compatibility Floor
Version: 0.003
Status: Deprecated in 0.004
Replaced by: D-023
Accounting Service و TigerBeetle integration با Go پیادهسازی میشوند.
module: github.com/tigerbeetle/tigerbeetle-go
minimum supported client: 0.16.4
minimum Go toolchain: 1.21
Dependency باید exact در go.mod/go.sum pin شود و downgrade زیر 0.16.4 ممنوع است.
منسوخ در
0.004: زبان پیادهسازی به .NET تغییر کرد. بخش مربوط به client floor یعنی>= 0.16.4منسوخ نشده و در D-023 عیناً حفظ شده است.
D-017 — Burned ID Requires Superseding Accounting Event
Version: 0.003
اگر TigerBeetle یکی از هفت state-dependent ID-burning result را صریحاً برگرداند، attempt جاری terminal است.
برای linked event نیز هیچ Transfer ID از event شکستخورده reuse نمیشود.
Business re-attempt فقط با:
new accounting_event_id
supersedes_event_id = previous_event_id
all-new persisted transfer IDs
مجاز است.
D-018 — Per-Code Account Flag Matrix Is Immutable Baseline
Version: 0.003
Provisioning فقط بر اساس جدول §9.5 انجام میشود؛ inference runtime از category ممنوع است.
D-019 — Broker Inventory Is an Explicit Spot Settlement Source
Version: 0.003
Spot Client Fill میتواند از BROKER_SPOT_INVENTORY_RESERVE تامین شود و نباید برای این حالت Venue جعلی ساخته شود.
D-020 — Omit Zero-Amount Normal Business Legs
Version: 0.003
Normal posted leg با computed amount صفر ساخته نمیشود. Zero فقط در TigerBeetle operationهایی با semantics خاص و مستند مجاز است.
D-021 — Spot Clearing Guard Is a Hard Production Control
Version: 0.003
SPOT_SETTLEMENT_CLEARING unconstrained باقی میماند، ولی breach در age/exposure threshold باید circuit breaker ایجاد Client Spot Settlement جدید را فعال کند.
D-022 — Withdrawal In-Flight Ordering Is Mandatory
Version: 0.003
Broadcast accounting و هر RBF spend delta باید قبل از confirmation settlement در ONCHAIN_OUTBOUND_IN_FLIGHT ثبت شده باشند.
D-023 — .NET به عنوان زبان پیادهسازی
Version: 0.004
Supersedes: D-016
Accounting Service و TigerBeetle integration با .NET پیادهسازی میشوند.
language: .NET / C#
official client package: tigerbeetle (NuGet)
namespace: TigerBeetle
minimum runtime: .NET >= 8.0
minimum supported client: 0.16.4
دلیل: انتخاب پلتفرم تیم توسعه.
مواردی که این تصمیم تغییر نمیدهد:
- semantic floor
>= 0.16.4برای ID-burning semantics طبق D-017 دستنخورده است. - shared/singleton client و ممنوعیت client-per-request طبق D-013 دستنخورده است.
- الزام linked-chain atomicity در یک request و سقف پیشفرض 8189 event دستنخورده است.
- هیچ Ledger registry، Account Code registry، Transfer Code registry، flag matrix §9.5 یا
user_datamapping تغییر نکرده است. - هیچ accounting flow، invariant یا reconciliation rule تغییر نکرده است.
الزامات:
- package version باید exact pin شود و CI باید downgrade زیر
0.16.4را fail کند. - تبدیل
UInt128↔Guid↔ PostgreSQLuuidباید در یک helper مرکزی با endianness تستشده انجام شود.
D-024 — Hot Account Sharding Uses partition_key, Not user_data
Version: 0.005
sharding accountهای داغ §9.6 با ستون accounting_accounts.partition_key انجام میشود. مقدار پیشفرض '' و بخشی از unique constraint است:
UNIQUE (owner_type, owner_id, asset_id, account_type, partition_key)
دلیل: §9.6 اجازه چند Account با یک code را میداد ولی unique constraint §26.2 آن را غیرممکن میکرد. استفاده از user_data_64/32 برای partition label نیز گزینه نبود، چون §9.1 آن فیلدها را روی صفر freeze کرده و D-014 افزودن معنای پنهانی را ممنوع میکند.
نتیجه: partition metadata فقط operational و در PostgreSQL است؛ contract مالی TigerBeetle دستنخورده میماند.
D-025 — Physical Storage Contract و Persistence Stack
Version: 0.005
u128 id / user_data_128 -> uuid
u128 amount / counter -> numeric(39,0)
u64 -> numeric(20,0)
u32 -> bigint + CHECK
u16 -> integer + CHECK
driver -> Npgsql
mapping -> Dapper
schema evolution -> versioned immutable raw SQL migrations
دلیل اصلی انتخاب numeric(39,0) برای amount: سقف bigint برابر 9.22 * 10^18 است و با ETH scale = 18 مقدار ۱۰ ETH یعنی 10^19 سرریز میکند. بنابراین bigint برای amount مالی این پروژه نادرست است.
numeric(39,0) دقیق و integer-only است؛ ممنوعیت floating point در §34.3 دستنخورده است و real / double precision / money در هیچ ستون مالی مجاز نیستند.
دلیل نبودن ORM با change-tracking روی جداول مالی: نوشتن مالی باید صریح و قابل بازبینی باشد و lazy loading یا flush خودکار میتواند side effect مالی غیرمنتظره تولید کند.
D-026 — Per-code owner_type mapping
Version: 0.007
1001-1004 user liabilities -> USER
2006 CLIENT_BAD_DEBT_RECEIVABLE -> USER
2001-2005 custody / outbound -> CUSTODY_GROUP
2101-2102 venue -> VENUE
3xxx revenue, 4xxx expense,
5xxx clearing, 6xxx equity -> SYSTEM
دلیل: user_data_128 در زمان create_accounts immutable است. بدون نگاشت ثابت، دو caller میتوانند برای یک code دو owner_type بنویسند و identity unique §26.2 را بشکنند.
41. Global Change Log
Version 0.021 — 2026-09-12
Changed
Urlsاز appsettings برداشته شد؛ کانتینر صریح روی0.0.0.0:5080listen میکند.- سرویس
accountingعلاوه بر seccomp،IPC_LOCK/SYS_ADMINوapparmor:unconfinedدارد تا io_uring روی Ubuntu 24 وقتیkernel.io_uring_disabled=2است کار کند.
Reason
curl به host:5080 با RST برمیگشت در حالی که ASPNETCORE_URLS درست بود: یا process با io_uring میمرد، یا Kestrel فقط loopback داخل کانتینر را میگرفت.
Affected Components
- appsettings.json
- launchSettings.json
- docker-compose.yml
- Dockerfile
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
هیچ.
Compatibility
ایمیج باید rebuild/push شود. اگر باز io_uring PermissionDenied بود: sysctl -w kernel.io_uring_disabled=0 روی host.
Version 0.020 — 2026-09-12
Changed
- سرویس compose
accountingمثل replica باseccomp:unconfinedاجرا میشود تا native client io_uring را بگیرد.
Reason
seccomp پیشفرض Docker syscallهای io_uring را میبندد و tb_client با PermissionDenied panic میکند.
Affected Components
- docker-compose.yml
- §32.1
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
هیچ.
Compatibility
سازگار با 0.019. فقط recreate کانتینر لازم است، نه rebuild ایمیج.
Version 0.019 — 2026-09-12
Changed
- قبل از
new Clientآدرسهای hostname به IPv4 resolve میشوند. - ایمیج runtime بستهٔ
libgssapi-krb5-2را دارد و connection string Postgresgssencmode=disableاست.
Reason
Native client تایگر tigerbeetle:3000 را رد میکند. Npgsql بدون libgssapi در کانتینر شروع را خراب میکند.
Affected Components
- TigerBeetleOptions
- Dockerfile
- docker-compose.yml
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
- TigerBeetleRegistrationTests همچنان
127.0.0.1است.
Compatibility
سازگار با 0.018. ایمیج باید از نو build/push شود.
Version 0.018 — 2026-09-12
Changed
- سرویس
accountingداخل docker-compose ایمیج API را به Postgres و TigerBeetle وصل میکند و127.0.0.1:5080را publish میکند. deploy/nginx-accounting.confهمان سایتaccounting.m0oo0m.xyzاست: TLS موجود،proxy_pass http://accounting/.
Reason
سرور تست از قبل Nginx و گواهی این دامنه را دارد؛ API باید یک سرویس compose با نام accounting باشد.
Affected Components
- docker-compose.yml
deploy/nginx-accounting.conf- §32.1
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
هیچ.
Compatibility
جایگزین محدودیت «فقط /docs» در 0.017 برای این hostname.
Version 0.017 — 2026-09-12
Changed
- دامنهٔ
/docsروی Nginx میزبان terminate میشود (deploy/nginx-docs.conf). profile composeedge/ Caddy حذف شد.
Reason
سرور تست از قبل Nginx دارد؛ پورت ۸۰/۴۴۳ را نباید Caddy بگیرد.
Affected Components
- §32.1
deploy/nginx-docs.conf- docker-compose.yml
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
هیچ.
Compatibility
جایگزین edge Caddy در 0.015.
Version 0.016 — 2026-09-12
Changed
- Dockerfile برای
Broker.Accounting.ApiوMakefileبا هدفهایimageوpushبه رجیستری داکر.
Reason
سرور تست باید ایمیج ساختهشده را از رجیستری بکشد، نه اینکه روی خود سرور dotnet run شود.
Affected Components
- Dockerfile
- Makefile
- §32.1
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
هیچ.
Compatibility
سازگار با 0.015. فقط packaging.
Version 0.015 — 2026-09-12
Changed
GET /docs,/docs/glossaryو/docs/architectureدو سند markdown را HTML میکنند.- profile اختیاری compose بهنام
edgeیک Caddy میآورد که دامنه را فقط به/docsمیرساند.
Reason
همکاران باید سند را روی سرور تست با hostname ببینند، بدون اینکه کنسول یا commandهای /v1 روی اینترنت باز شوند.
Affected Components
- §32.1
- Broker.Accounting.Api
/docs - docker-compose profile
edge deploy/Caddyfile
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
هیچ.
Compatibility
سازگار با 0.014. فقط hosting.
Version 0.014 — 2026-09-12
Changed
- فایل
BROKER_ACCOUNTING_GLOSSARY_FA.mdاصطلاحات کنسول و مرحلههای واریز/برداشت/custody/اسپات را با ارجاع به بخشهای همین سند توضیح میدهد. سیاست جدید نیست.
Reason
نام فرمانهای /ops برای خوانندهٔ غیر از سند کامل مبهم است (مثلاً custody در برابر بدهی کاربر، یا چهار مرحلهٔ withdrawal).
Affected Components
BROKER_ACCOUNTING_GLOSSARY_FA.md- §0، §32.1
Database / Migration
هیچ.
Accounting Impact
هیچ.
Reconciliation Impact
هیچ.
Tests
هیچ.
Compatibility
سازگار با 0.013.
Version 0.013 — 2026-09-12
Changed
- دکمه و
POST /ops/resetاز کنسول local حذف شد. - پاک کردن کامل Postgres + TigerBeetle محلی با دور ریختن volumeهای compose است:
docker compose down -v && docker compose up -d، بعد restart کردن API.
Reason
Wipe از داخل process API session تایگر را evict میکند و کنسول را میشکند. down -v هر دو volume را تمیز برمیگرداند.
Affected Components
/opsUI- §32.1
- docker-compose.yml
Database / Migration
volumeهای postgres-data و tigerbeetle-data حذف میشوند. migration و seed دارایی با استارت بعدی API برمیگردند.
Accounting Impact
کلاستر TB و جدولهای عملیاتی هر دو از صفر شروع میشوند.
Reconciliation Impact
snapshot خالی است تا اولین command بعد از migrate.
Tests
هیچ.
Compatibility
جایگزین 0.011 / 0.012 reset HTTP. فقط local.
Version 0.012 — 2026-09-12
Changed
POST /ops/resetفایل data محلی TigerBeetle را حذف و دوباره format میکند، بعد replica را بالا میآورد. PostgreSQL عملیاتی مثل0.011truncate میشود. client مشترک process یکبار عوض میشود چون replica جدید session قبلی را evict میکند؛ این client-per-request نیست.
Reason
بدون wipe کلاستر، حسابهای orphan در TB میماند و تست «از صفر» تمیز نبود.
Affected Components
- LocalTigerBeetleReset
- TigerBeetleClientHandle
- docker-compose
tigerbeetle-wipe(profilewipe) - §32.1
/ops/reset
Database / Migration
هیچ. volume محلی /data/0_0.tigerbeetle از نو ساخته میشود.
Accounting Impact
بعد از reset هیچ transfer/accountی در TB نیست تا اولین provision.
Reconciliation Impact
snapshot خالی است.
Tests
- local docker compose
Compatibility
سازگار با 0.011. فقط local.
Version 0.011 — 2026-09-12
Changed
POST /ops/resetجدولهای عملیاتی PostgreSQL را برای تست local خالی میکند. registry دارایی/ابزار دست نخورده میماند.
Reason
در کنسول تست لازم است بشود از صفر دوباره داده گذاشت، بدون drop کردن دیتابیس و migration.
Affected Components
- §32.1
- LocalLedgerReset
/opsUI
Database / Migration
هیچ جدول تازهای نیست. TRUNCATE روی جدولهای عملیاتی.
Accounting Impact
حسابهای قبلی TigerBeetle orphan میشوند و دیگر map نمیشوند. account جدید با id تازه provision میشود.
Reconciliation Impact
بعد از reset، snapshot خالی است تا اولین command.
Tests
- UI confirm قبل از reset
Compatibility
سازگار با 0.010. فقط برای local.
Version 0.010 — 2026-09-12
Changed
- کنسول
/opsعلاوه بر گزارش زنده، فرم تمام commandهای نامدار §32 را دارد تا تست دستی بدون curl انجام شود.
Reason
دیدن مانده بدون توانایی زدن همان commandها هنوز به ابزار جدا نیاز داشت.
Affected Components
/opsUI
Database / Migration
هیچ.
Accounting Impact
هیچ مسیر posting تازهای اضافه نشد؛ UI همان API موجود را صدا میزند.
Reconciliation Impact
بدون تغییر.
Tests
- فرمها همان endpointهای موجود را صدا میزنند.
Compatibility
سازگار با 0.009.
Version 0.009 — 2026-09-12
Changed
- کنسول عملیاتی local در
/opsاضافه شد.GET /ops/snapshotیک گزارش واحد از صف event، حسابها، موجودی زنده TigerBeetle، کش PostgreSQL، پوزیشن، allocation و reconciliation میدهد.GET /ops/streamهمان snapshot را با SSE تازه میکند.
Reason
در تست دستی نباید برای دیدن اثر هر command به REPL تایگر یا SQL جدا رفت. Phase 6 tooling در لایه Accounting Service یعنی همین گزارشها؛ comparator چین/venue همچنان به observation خارجی نیاز دارد.
Affected Components
- §32.1 HTTP surface
- OpsSnapshotQueries
- static
/opsconsole
Database / Migration
هیچ جدول تازهای اضافه نشد.
Accounting Impact
هیچ. کنسول write مالی ندارد.
Reconciliation Impact
همان گزارش §0.008 حالا در UI زنده دیده میشود.
Tests
- build کنسول static با API
Compatibility
سازگار با 0.008.
Version 0.008 — 2026-09-12
Changed
- HTTP commandهای Phase 3–6 طبق §32 اضافه شد: withdrawal، spot client/venue/inventory، fee، venue funding، hedge PnL، funding، liquidation، write-off، correction و گزارش reconciliation.
- یک linked chain میتواند چند ledger داشته باشد؛ هر transfer فقط با حسابهای همان ledger است.
Reason
پیادهسازی «همه فازها» در لایه Accounting Service یعنی همان use-caseهای §32، نه ناظر بلاکچین یا adapter صرافی. آنها سرویس جدا هستند و از همین commandها مصرف میکنند.
Affected Components
- §32.1 HTTP surface
- AccountingCommands
- WellKnownIds.DefaultVenue
Database / Migration
جدول عملیاتی spot_execution_allocations طبق §19.6 اضافه شد. Ledger همچنان فقط در TigerBeetle است.
Accounting Impact
هیچ account/transfer code تازهای اضافه نشد. مسیرها همان §14–§25 هستند.
Reconciliation Impact
GET /v1/reconciliation/{asset} فقط گزارش میدهد: جمع liability کاربر، clearing، in-transit، و ردیفهای stale cache. comparator چین/venue هنوز به API خارجی وصل نیست.
Tests
- domain owner mapping همچنان پوشش D-026
- integration: withdrawal lock/broadcast/confirm، spot client+venue+spread+inventory، liquidation/write-off، hedge/funding، custody location move
Compatibility
سازگار با 0.007.
Version 0.007 — 2026-09-12
Changed
- D-026 نگاشت هر Account Code به
owner_typeرا بست. - §32.1 قرارداد HTTP JSON برای commandهای حسابداری local اضافه شد.
operation_idبه unique(aggregate_type, aggregate_id)گره خورد.- جدول
positionsطبق §22 و instrument نمونهBTCUSDTاضافه شد. - اولویت collectible loss برای API:
USER_MARGINسپسUSER_AVAILABLE.
Reason
تست دستی open/close پوزیشن و transfer بدون حدس debit/credit دلخواه ممکن نبود. HTTP فقط commandهای سند را expose میکند.
Affected Components
- §26.9 owner_type
- §32 HTTP command API
- §42.1 owner_type gate
- D-026
- migration 0004
Database / Migration
0004_positions_and_command_idempotency.sqlUNIQUE (aggregate_type, aggregate_id)رویaccounting_events- جدول
positionsو seedBTCUSDTperpetual
Accounting Impact
هیچ account code یا flag تازهای اضافه نشد. فقط مسیر HTTP روی transfer codeهای موجود §12 سوار شد.
Reconciliation Impact
هیچ.
Tests
- open position موجودی available را کم و margin را زیاد میکند
- close با سود available را به اندازهٔ pnl+margin زیاد میکند
- transfer liability را بین دو کاربر جابهجا میکند بدون تغییر custody
Compatibility
سازگار با 0.006.
Version 0.005 — 2026-09-12
Changed
- ستون
partition_keyبهaccounting_accountsاضافه شد و unique constraint به(owner_type, owner_id, asset_id, account_type, partition_key)تغییر یافت. - §9.6 به مکانیزم فیزیکی
partition_keyگره خورد و صریح شد که partition metadata فقط در PostgreSQL است. - بخش جدید §26.8 با عنوان Physical Storage Contract اضافه شد.
- بخش جدید §26.9 مقادیر مجاز
accounting_event_legs.status،accounting_events.failure_classوaccounting_accounts.owner_typeرا تعریف کرد. - §26.9 صریح کرد که
accounting_event_legs.flagsشامل بیتlinkedاست و همان مقدار submit میشود، تا retry نتواندexists_with_different_flagsتولید کند. - نگاشت هر Account Code به
owner_typeبه عنوان یک open decision در §42.1 ثبت شد. - نوع فیزیکی هر مفهوم TigerBeetle در PostgreSQL تثبیت شد: u128 id به
uuid، amount بهnumeric(39,0)، u64 بهnumeric(20,0)، u32 بهbigintو u16 بهintegerباCHECK. - استفاده از
bigintبرای amount مالی صریحاً ممنوع شد. - Persistence stack تثبیت شد: Npgsql + Dapper + migrationهای raw SQL نسخهدار و immutable.
- D-024 و D-025 اضافه شدند.
Reason
پیادهسازی دیتا مدل نیاز به نوع فیزیکی قطعی داشت و دو تناقض در baseline قبلی آشکار شد:
۱. §9.6 اجازه چند Account با یک code را میداد ولی unique constraint §26.2 آن را غیرممکن میکرد و §9.1 هم user_data_64/32 را روی صفر freeze کرده بود، بنابراین هیچ محل مجازی برای partition label وجود نداشت.
۲. سند هیچجا نوع فیزیکی amount را تعیین نکرده بود. انتخاب طبیعی bigint با ETH scale = 18 نادرست است، چون سقف bigint برابر 9.22 * 10^18 است و ۱۰ ETH یعنی 10^19 سرریز میکند. کشف این مورد بعد از go-live نیازمند migration نوع ستون روی جداول مالی بود.
۳. سه ستون schema هیچ مجموعه مقدار مجازی نداشتند: accounting_event_legs.status، accounting_events.failure_class و accounting_accounts.owner_type. §26.9 آنها را از رفتار مستندشده §10.3، §10.4 و §9.1 استخراج و تثبیت کرد.
Affected Components
- §9.6 hot account sharding
- §26.2
accounting_accounts - §26.8 physical storage contract
- §26.9 status و enumeration contract
- §35 تستهای Account Provisioning و Balance
- §42.1 Pre-Provisioning Gate
- D-024، D-025
Database / Migration
این نسخه baseline اولین migration است و روی هیچ داده production اعمال نمیشود.
accounting_accounts.partition_keyاز نوعtext NOT NULL DEFAULT ''.- unique constraint شامل
partition_key. - تمام ستونهای amount و cumulative counter از نوع
numeric(39,0)باCHECK >= 0. balance_cache.economic_balanceاز نوعnumeric(39,0)بدونCHECK >= 0.
Accounting Impact
هیچ. هیچ ledger، account code، transfer code، flag matrix، flow یا invariant حسابداری تغییر نکرده است. تغییرات صرفاً representation در PostgreSQL هستند.
Reconciliation Impact
هیچ تغییر قاعدهای. §9.6 از قبل aggregation بر اساس code + external metadata را الزام کرده بود و اکنون آن metadata نام ستون مشخص دارد.
Tests
- unique constraint با
partition_keyیکسان و متفاوت - رد شدن
partition_key = NULL - round-trip دقت
numeric(39,0)برای2^128 - 1 - ذخیره
10^19بدون سرریز - رد شدن amount منفی توسط
CHECK
Compatibility
سازگار با 0.004. هیچ تصمیم immutable قبلی نقض نشده است. user_data_64/32 طبق §9.1 و D-014 روی صفر باقی میمانند و floating point طبق §34.3 همچنان ممنوع است.
Version 0.004 — 2026-09-12
Changed
- زبان پیادهسازی Accounting Service و TigerBeetle integration از Go به .NET / C# تغییر کرد.
- Client رسمی به package
tigerbeetleدر NuGet با namespaceTigerBeetleتغییر کرد. - runtime floor روی
.NET >= 8.0تثبیت شد. - D-016 به
Deprecated in 0.004تغییر یافت و D-023 جانشین آن شد. - §10.5 بازنویسی شد و سه زیربخش اضافه شد: client lifetime، type mapping/u128 storage و deployment note.
TigerBeetle.Clientبه صورت singleton در DI الزامی شد وusingدر scope هر request صریحاً ممنوع شد.- نگاشت flagهای §9.5 به
AccountFlagsمستند شد. - baseline ذخیرهسازی u128 در PostgreSQL روی
uuidتثبیت شد و helper تبدیل مرکزی با endianness تستشده الزامی شد. - الزام pin کردن exact package version با Central Package Management جایگزین الزام
go.mod/go.sumشد. - تستهای compatibility مربوط به singleton lifetime، round-trip تبدیل
UInt128/Guid/uuid، مقادیر رزروشده ID و نگاشت flag اضافه شدند. - §42.1، §43 Phase 0 و §44 با انتخاب جدید همراستا شدند.
Reason
تغییر پلتفرم انتخابی تیم توسعه. تغییر در سطح client/runtime است و هیچ تصمیم حسابداری را جابهجا نمیکند.
Affected Components
Accounting Service
TigerBeetle Client Integration
Worker Runtime
Dependency/Build/CI
PostgreSQL ID column types
Database / Migration
هیچ migration مالی لازم نیست. چون هنوز هیچ Account/Transfer production ساخته نشده است، تثبیت نوع ستونهای ID روی uuid تغییر baseline است نه migration.
Accounting Impact
هیچ. Ledger registry، Account Code registry، Transfer Code registry، flag matrix §9.5، user_data mapping، تمام accounting flowها و تمام invariantها بدون تغییر باقی ماندهاند.
Reconciliation Impact
هیچ.
Tests
§35 بخش TigerBeetle Client Compatibility گسترش یافت.
Compatibility
0.004 نسبت به 0.003 از نظر مدل مالی کاملاً backward-compatible است. تنها ناسازگاری در سطح toolchain/dependency است.
Version 0.003 — 2026-09-12
Changed
- Go به عنوان زبان Accounting Service/TigerBeetle integration انتخاب شد.
- حداقل TigerBeetle client semantics روی
>= 0.16.4تثبیت شد. - هفت state-dependent ID-burning result به صورت صریح مستند شدند.
- linked-chain retry rule تغییر کرد: بعد از explicit burn، کل attempt terminal و successor دارای تمام IDهای جدید است.
supersedes_event_idوfailure_classبهaccounting_eventsاضافه شدند.- flag matrix قطعی per Account Code اضافه شد.
BROKER_SPOT_INVENTORY_RESERVEوSPOT_INVENTORY_SETTLEMENTاضافه شدند.- Spot fill از Broker Inventory بدون Venue جعلی مستند شد.
- Spot allocation دارای
source_typeشد. - hard circuit breaker برای Spot Clearing exposure/aging تعریف شد.
- تعریف
max_required_amountشامل price cap، worst-case fee، buffer و rounding شد. - Withdrawal ordering بین lock/broadcast/in-flight/confirmation و RBF delta اجباری شد.
- zero-amount normal business legs حذف میشوند.
- self-transfer و ledger/code صفر در application layer ممنوع شدند.
- Change History مربوط به Spot Trading Fee اصلاح شد.
- تستهای ID burn، superseding event، internal inventory و withdrawal ordering اضافه شدند.
Reason
بستن رفتارهای غیرقابلبازگشت و retry semantics قبل از Phase 1/provisioning، و حذف آخرین فرضهای پنهان در Spot/Withdrawal flows.
Affected Components
Accounting Service
TigerBeetle Client Layer
Accounting Worker
Account Provisioning
Spot Settlement
Spot Reconciliation
Wallet/Withdrawal
PostgreSQL Schema
CI/Test Harness
Database / Migration
accounting_events.failure_classاضافه میشود.accounting_events.supersedes_event_idself-FK اضافه میشود.spot_execution_allocations.source_typeو source reference اضافه میشوند.- Account Code
6002 BROKER_SPOT_INVENTORY_RESERVEاضافه میشود. - Transfer Code
207 SPOT_INVENTORY_SETTLEMENTاضافه میشود. - provisioning باید دقیقاً flag matrix §9.5 را استفاده کند.
اگر Accountهای production قبلاً با flag متفاوت ساخته شدهاند، in-place edit مجاز نیست و migration/reprovisioning لازم است.
Accounting Impact
- Insufficient-funds rejection دیگر retry ساده با همان ID نیست.
- یک failed linked attempt lineage مستقل دارد و با successor جدید ادامه پیدا میکند.
- internal inventory اکنون source حسابداری رسمی Spot است.
- Spot Clearing exposure دارای hard operational gate است.
Reconciliation Impact
- Broker inventory reserve وارد Spot reconciliation میشود.
- successor event lineage باید در incident/audit قابل پیمایش باشد.
- withdrawal in-flight باید قبل از final settlement تمام broadcast/RBF spend را منعکس کند.
Tests
§35 و §36 بروزرسانی شدند.
Compatibility
0.003 حداقل TigerBeetle client 0.16.4 را فرض میکند. Client پایینتر supported نیست.
اگر provisioning واقعی بر اساس v0.002 انجام شده باشد، flag matrix باید با Accountهای موجود compare شود؛ هر mismatch نیازمند migration است.
Version 0.002 — 2026-09-12
Changed
- Spot Client Settlement از Venue Settlement جدا شد.
SPOT_SETTLEMENT_CLEARINGper-asset اضافه شد.- فرض
1 Client Fill = 1 Venue Fillحذف شد. - multi-venue و aggregate spot allocation اضافه شد.
- Accountهای Funding برای Client و Venue در هر دو جهت کامل شدند.
LIQUIDATION_FEE_REVENUE,CLIENT_BAD_DEBT_RECEIVABLE,BAD_DEBT_EXPENSEاضافه شدند.- naming روی
HEDGE_PNL_REVENUE/HEDGE_PNL_EXPENSEیکسان شد. - Ledger IDها از Account Code namespace جدا شدند.
flags.historyبرای تمام Accountهای production اجباری شد.- Account
user_data_*mapping تثبیت شد. - Transfer
user_data_*mapping تثبیت شد. - TigerBeetle time-based ID به عنوان ID scheme اصلی انتخاب شد.
- persisted per-leg IDs و
accounting_event_legsاضافه شدند. - handling صریح
existsوexists_with_different_*تعریف شد. - historical import از تصمیم late-stage به pre-go-live gate منتقل شد.
- Deposit Credit همیشه از
CUSTODY_DEPOSITمتناظر با مقصد واقعی chain عبور میکند. - Custody sweep به Accounting Event جدا تبدیل شد.
instrumentsو worker lease columns به PostgreSQL schema اضافه شدند.- Balance cache refresh و staleness SLA تعریف شد.
- shared TigerBeetle client و automatic batching به معماری اضافه شد.
- linked-chain atomicity به ارسال در یک request محدود و صریح شد.
- مسیر bad debt برای liquidation عمیق اضافه شد.
- Withdrawal posted-state design به Decision Log اضافه شد.
- تستهای multi-venue, bad-debt, ID mismatch, history و worker recovery اضافه شدند.
Reason
رفع تناقضهای معماری و تصمیمهای immutable قبل از شروع provisioning و implementation.
Affected Components
Accounting Service
Spot Settlement
Execution Service
Position/Risk
Wallet Service
Reconciliation
PostgreSQL
TigerBeetle Provisioning
Worker Runtime
Database / Migration
accounting_event_legsاضافه میشود.instrumentsاضافه میشود.spot_execution_allocationsاضافه میشود.- lease/retry columns به
accounting_eventsاضافه میشود. - initial Account/Transfer registries تغییر کردهاند.
- اگر Accountهای v0.001 قبلاً در یک TigerBeetle cluster واقعی ساخته شده باشند، migration/reprovisioning لازم است؛ fieldهای immutable را in-place تغییر ندهید.
Accounting Impact
Breaking architecture refinement قبل از go-live.
Reconciliation Impact
- Spot Clearing reconciliation اضافه شد.
- Deposit reconciliation به custody destination group متصل شد.
- historical balance requirement با
historyflag تثبیت شد. - Bad Debt receivable باید جدا گزارش شود.
Tests
§35 و §36 بروزرسانی شدند.
Compatibility
0.002 نسبت به هر provisioning واقعی مبتنی بر account flags/mapping نسخه 0.001 backward-compatible فرض نمیشود.
اگر هنوز هیچ Account production ساخته نشده، 0.002 baseline جدید است.
Version 0.001 — 2026-09-12
Changed
- معماری اولیه Accounting برای Broker A-Book تعریف شد.
- TigerBeetle به عنوان Financial Source of Truth انتخاب شد.
- PostgreSQL به عنوان Operational/Trading Source of Truth انتخاب شد.
- مدل User Liability Account تعریف شد.
- مدل Broker Asset Account تعریف شد.
- Spot Buy/Sell و Partial Fill تعریف شد.
SPOT_LOCKEDبرای reservation سفارش معرفی شد.- Margin Account تعریف شد.
- Realized و Unrealized PnL از یکدیگر جدا شدند.
- Client PnL و Hedge PnL مستقل شدند.
- مدل many-to-many برای Hedge Allocation در نظر گرفته شد.
- Deposit lifecycle تعریف شد.
- Withdrawal lifecycle تعریف شد.
- Hot/Cold Custody تعریف شد.
- Venue Asset و Venue In-Transit تعریف شدند.
- Reconciliation به عنوان بخش الزامی معماری تعریف شد.
- Idempotent Accounting Event و Transfer ID strategy تعریف شد.
- Document-First Development Policy اجباری شد.
Reason
ایجاد baseline معماری قابل پیادهسازی برای:
A-Book Derivatives
+
Real Spot
+
Blockchain Custody
+
On-Chain Deposit/Withdrawal
Affected Components
Accounting Service
OMS
Position Service
Risk Service
Execution/Hedge Service
Wallet Service
PostgreSQL
TigerBeetle
Reconciliation
Database / Migration
Initial schema required.
Accounting Impact
Initial accounting model.
Reconciliation Impact
Initial reconciliation model.
Tests
Test matrix در بخش 35 تعریف شد.
Compatibility
Initial version; backward compatibility موضوعیت ندارد.
42. Open Decisions و Go-Live Blockers
این بخش فقط شامل تصمیمهایی است که واقعاً هنوز باز هستند. موارد immutable مربوط به history, ID scheme, user_data, initial scale, account flags و TigerBeetle client semantics از v0.003 دیگر «بعداً تصمیم میگیریم» نیستند.
42.1. Pre-Provisioning Gate
قبل از اولین create_accounts واقعی باید این موارد verify شوند:
- Initial ledger namespace تعریف شده است.
- Initial asset scaleها تعریف شدهاند.
- Account Code Registry اولیه freeze شده است.
- Transfer Code Registry اولیه freeze شده است.
-
flags.historypolicy تعریف شده است. -
user_data_*mapping تعریف شده است. - TigerBeetle ID strategy تعریف شده است.
-
migration_modeبرای محیط مقصد انتخاب شده است:FRESH_STARTیاHISTORICAL_IMPORT. - نگاشت هر Account Code به
owner_typeطبق D-026. - Implementation language = .NET / C#.
- Official client =
tigerbeetleNuGet package (namespaceTigerBeetle). - Minimum supported TigerBeetle client =
0.16.4. - Runtime floor compatible with official client prerequisite (
.NET >= 8.0); exact TFM is pinned by implementation/CI. - u128 storage baseline = PostgreSQL
uuidبا helper تبدیل مرکزی. - Physical storage contract §26.8 تعریف شده است؛ amount =
numeric(39,0)وbigintممنوع است. - مکانیزم sharding =
accounting_accounts.partition_keyطبق D-024. - Per-code Account Flag Matrix در §9.5 freeze شده است.
اگر migration_mode = HISTORICAL_IMPORT باشد، import و reconciliation باید قبل از live writer تکمیل شوند.
42.2. قبل از Spot Production
- Venueهای اولیه
- Fee currency rules
- instrument rounding policy
- price/quantity scale per instrument
- Spot settlement aging SLA و hard exposure threshold
- allocation policy برای aggregate/multi-venue execution
- Broker Inventory mode برای هر asset فعال/غیرفعال و seed/replenishment policy
42.3. قبل از Derivatives Production
- Hedge policy: 1:1 / net / threshold based
- Cross-margin یا isolated-margin
- Position margin consumption priority
- Maintenance Margin model
- Liquidation trigger model
- Insurance Fund funding/recovery model
- Funding rate/source/calculation policy
- Negative balance collection/business policy
مسیر accounting برای shortfall از v0.002 مشخص است، ولی business/risk policy هنوز باید نهایی شود.
42.4. قبل از Blockchain Production
- Networkهای اولیه
- Confirmation policy هر Network
- Reorg policy هر Network
- Withdrawal fee policy per network
- RBF/replacement policy per network
- Custody wallet grouping strategy
42.5. Operational Decisions
- Reconciliation frequency
- Alert thresholds
- Manual Adjustment approval workflow
- Maker/Checker threshold
- Balance cache capacity/retention policy
- Accounting worker concurrency/backoff policy
هر تصمیم باز هنگام نهاییشدن باید:
- در بخش اصلی مربوطه نوشته شود.
- Decision Log داشته باشد.
- Version bump شود.
- Global Change Log بروزرسانی شود.
- سپس implement شود.
43. دستور شروع کار برای Agent
Agent باید قبل از شروع کدنویسی Pre-Provisioning Gate در §42.1 را بررسی کند.
Phase 0 — Architecture Gate
Validate document version = 0.005+
Select migration_mode
Verify implementation language = .NET
Verify tigerbeetle NuGet package >= 0.16.4 and exact version pin
Freeze initial asset/ledger registry
Verify exact per-code account flag matrix + user_data mapping
Run design-level accounting invariant tests
Phase 1 — Foundation
Asset Registry
Instrument Registry
TigerBeetle shared client
Account code registry
Transfer code registry
Accounting account provisioning
Phase 2 — Durable Accounting Runtime
Accounting Event
Accounting Event Legs
ID generation + persistence
Worker lease
Retry/recovery
Result classification
Balance query
Balance cache
Phase 3 — Custody
Deposit
Internal transfer
Withdrawal
Custody movement
Venue funding
Phase 4 — Spot
Order lock/release
Client spot settlement
Venue spot settlement
Execution allocation
Partial fill
Fees/spread/price-improvement
Spot clearing reconciliation
Phase 5 — Derivatives
Margin
Position realized pnl
A-Book hedge accounting
Funding
Liquidation
Bad debt
Phase 6 — Reconciliation & Operations
Blockchain reconciliation
Venue reconciliation
User liability reconciliation
Spot clearing reconciliation
Alerts
Manual corrections
Operational tooling
از 0.008 commandهای حسابداری Phase 3–6 در Accounting Service پیاده شدهاند. این به معنی ناظر بلاکچین، client صرافی، یا matching OMS نیست؛ آنها سرویس جدا هستند و فقط همین commandها را صدا میزنند.
در شروع هر Phase:
- همین document review شود.
- Open decisionهای آن Phase بسته شوند.
- document version/change log در صورت تغییر bump شود.
- سپس code تغییر کند.
44. منابع فنی TigerBeetle
Implementation باید با documentation رسمی نسخه client/server مورد استفاده تطبیق داده شود.
Account Reference:
https://docs.tigerbeetle.com/reference/account/Transfer Reference:
https://docs.tigerbeetle.com/reference/transfer/Create Transfers Result Codes:
https://docs.tigerbeetle.com/reference/requests/create_transfers/Requests / Batching / One In-Flight Request:
https://docs.tigerbeetle.com/coding/requests/.NET Client:
https://docs.tigerbeetle.com/coding/clients/dotnet/Balance Bounds Recipe:
https://docs.tigerbeetle.com/coding/recipes/balance-bounds/Data Modeling / Asset Scale / ID Scheme / user_data:
https://docs.tigerbeetle.com/coding/data-modeling/Historical Account Balances:
https://docs.tigerbeetle.com/reference/requests/get_account_balances/Two-Phase Transfers:
https://docs.tigerbeetle.com/coding/two-phase-transfers/Currency Exchange Recipe:
https://docs.tigerbeetle.com/coding/recipes/currency-exchange/Reliable Transaction Submission:
https://docs.tigerbeetle.com/coding/reliable-transaction-submission/
Agent باید هنگام implementation API details را با documentation رسمی version مورد استفاده verify کند.
اگر documentation version مورد استفاده با این سند تفاوت دارد:
Update Document
↓
Version / Change Log
↓
Then Code
نه برعکس.
45. خلاصه معماری
USER
│
┌───────────┴────────────┐
│ │
SPOT DERIVATIVES
│ │
Available / Locked Available / Margin
│ │
▼ ▼
Client Settlement Client Position
│ │
▼ ▼
SPOT_SETTLEMENT_CLEARING Hedge Engine
│ │
┌───────┼────────┐ │
│ │ │ │
▼ ▼ ▼ ▼
Inventory Venue A Venue B LP / Venue
│ │ │
└───────┬────────┘ │
▼ ▼
Venue Assets Venue Assets
│ │
└──────────┬─────────────┘
▼
In Transit
│
▼
Hot / Warm / Cold
│
▼
Blockchain
TigerBeetle:
Financial ownership
Balances
Account history
Transfers
Spot clearing
Fees
Realized PnL
Margin
Funding
Settlement
Bad-debt receivable/writeoff
PostgreSQL:
Asset/Instrument registry
Orders
Client fills
Spot execution allocations
Positions
Hedge orders/fills/allocations
Deposits
Withdrawals
Blockchain transactions
Accounting event intents/legs
Worker leases
Reconciliation state
Non-authoritative balance cache
اصول نهایی
Balance نتیجه Transferها است.
Transfer نتیجه تغییر دستی Balance نیست.
Client execution
!=
Venue execution
Unrealized PnL
!=
Ledger posting
Ambiguous retry
=
same persisted ID
exists_with_different_*
=
critical integrity incident
هر تغییر معماری:
Document First
Code Second
Change Log Always