Stock Splits
How Dinari applies forward and reverse stock splits to dShares
Dinari processes both forward and reverse stock splits through a single, unified pipeline. The only differences are the ratio's direction and how leftover fractions are handled.
graph LR
A[Trading Halts] --> B[Token Rebased] --> C[Balances Verified] --> D[Trading Resumes]
Reading a split
A split is described by two fields:
| Field | Meaning |
|---|---|
split_from | Shares you hold before |
split_to | Shares you hold after |
A 10-for-1 forward split has split_from = 1, split_to = 10. A 1-for-40 reverse split has split_from = 40, split_to = 1.
The direction is simply which value is larger:
const isReverse = split.split_from > split.split_to;
const multiplier = split.split_to / split.split_from; // 10 forward, 0.025 reverseis_reverse = split.split_from > split.split_to
multiplier = split.split_to / split.split_from # 10.0 forward, 0.025 reverseDates and status
| Field | Meaning |
|---|---|
record_date | Date a holder must hold to be entitled |
payable_date | Trading halts at the end of this day |
ex_date | Trading resumes at split-adjusted prices |
status | PENDING → IN_PROGRESS → COMPLETE |
Splits are usually scheduled over a weekend to minimize disruption. While a split is IN_PROGRESS, the stock is not available for trading.
Forward splits
What happens
- Trading halts. New orders are rejected, active orders are cancelled, and the dShare™ token is paused.
- The token is rebased to match the split ratio.
- Dinari verifies balances across all systems and confirms the underlying stock changes are reflected.
- Trading resumes. New orders are accepted and the token is unpaused.
Balances scale automatically. Dinari adjusts the token's balance-per-share once rather than minting to each holder individually, so there is no per-wallet transaction to watch for — read the new balance from the portfolio endpoint afterCOMPLETE.
sequenceDiagram
autonumber
participant D as Dinari
participant P as Partner
participant U as End user
Note over D: 10-for-1 split announced — status = PENDING
P->>D: Poll for splits
D-->>P: split_from 1, split_to 10,<br/>payable_date, ex_date
P->>U: Notify: split scheduled
Note over D: End of payable_date — status = IN_PROGRESS
D->>D: Halt trading, cancel open orders,<br/>rebase the token
P->>P: Pause trading in your UI
Note over D: ex_date — status = COMPLETE, trading resumes
P->>D: Read portfolio
D-->>P: Balance x10, value unchanged
P->>U: Update ledger, show new share count
Reverse splits
A reverse split does the opposite: the share count falls and the price rises proportionally. A 1-for-40 reverse split turns 40 dShares into 1.
Reverse splits use the same pipeline as forward splits — halt, rebase, verify, resume. The ratio is simply inverted. The one difference that matters to your integration is fractional handling.
Fractional handling
Because dShares are fractionable, most reverse splits produce no rounding problem: the holder ends up with fewer dShares at a higher price and the position value is unchanged.
The exception is when the post-split stock becomes non-fractionable. The leftover fraction cannot be represented, so it is liquidated and the holder receives the proceeds in USD+ instead of dShares. A small enough position can end at zero shares plus a cash credit.
sequenceDiagram
autonumber
participant D as Dinari
participant P as Partner
participant U as End user
Note over D: 1-for-40 reverse split announced — status = PENDING
P->>D: Poll for splits
D-->>P: split_from 40, split_to 1,<br/>payable_date, ex_date
P->>U: Notify: reverse split scheduled
Note over D: End of payable_date — status = IN_PROGRESS
D->>D: Halt trading, cancel open orders,<br/>rebase the token
P->>P: Pause trading in your UI
alt Post-split balance remains fractionable
D->>D: Holder keeps a smaller<br/>dShare balance
else Post-split balance is non-fractionable
D->>D: Burn the leftover fraction,<br/>credit USD+ of equivalent value
D->>P: Direct notification of<br/>affected holders
end
Note over D: ex_date — status = COMPLETE, trading resumes
P->>D: Read portfolio and cash balances
D-->>P: New balances, any USD+ credit
P->>U: Update ledger, show new share count<br/>and any cash in lieu
Duplicate asset records
A reverse split is sometimes accompanied by a CUSIP change, which can cause the market data provider to emit a duplicate stock record under the same symbol. Dinari refreshes the asset list and merges the duplicate into the existing record, so the stock ID your integration references stays stable. No action is required from you.
Recommended Flow
Both directions call for the same integration work.
- Poll for splits daily on the stocks your users hold. Splits are scheduled well in advance.
- Pause trading in your interface while
statusisIN_PROGRESS, and expect open orders to be cancelled. - Re-read balances once
statusisCOMPLETEand update your ledger. For reverse splits, check cash balances too — a USD+ credit may have replaced a fraction. - Show the ratio, not just the new number. "Your 5 shares became 50 in a 10-for-1 split, value unchanged" prevents most support tickets.
SDK examples
Detect upcoming and in-progress splits
import Dinari from '@dinari/api-sdk';
const client = new Dinari({
apiKeyID: process.env['DINARI_API_KEY_ID'],
apiSecretKey: process.env['DINARI_API_SECRET_KEY'],
environment: 'sandbox',
});
async function main() {
const stockID = 'stock-id-here';
const splits = await client.v2.marketData.stocks.splits.listForStock(stockID, {
page: 1,
page_size: 10,
});
for (const s of splits) {
const isReverse = s.split_from > s.split_to;
console.log(
`${s.split_from}-for-${s.split_to} ` +
`${isReverse ? 'reverse' : 'forward'} — ` +
`${s.status}, ex-date ${s.ex_date}`
);
}
}
main();import os
from dinari_api_sdk import Dinari
client = Dinari(
api_key_id=os.environ.get("DINARI_API_KEY_ID"),
api_secret_key=os.environ.get("DINARI_API_SECRET_KEY"),
environment="sandbox",
)
splits = client.v2.market_data.stocks.splits.list_for_stock(
stock_id="stock_xxx",
page=1,
page_size=25,
)
for s in splits:
is_reverse = s.split_from > s.split_to
direction = "reverse" if is_reverse else "forward"
print(f"{s.split_from}-for-{s.split_to} {direction} — "
f"{s.status}, ex-date {s.ex_date}")Info: Each
StockSplitcontainsid,stock_id,split_from,split_to,record_date,payable_date,ex_date, andstatus.
Gate trading on split status
async function canTrade(stockID: string): Promise<[boolean, string | null]> {
const splits = await client.v2.marketData.stocks.splits.listForStock(stockID);
for (const s of splits) {
if (s.status === 'IN_PROGRESS') {
const direction = s.split_from > s.split_to ? 'reverse split' : 'stock split';
return [
false,
`Trading is paused for a ${s.split_from}-for-${s.split_to} ` +
`${direction}. It resumes on ${s.ex_date}.`,
];
}
}
return [true, null];
}
const [allowed, message] = await canTrade('stock-id-here');
if (!allowed) {
showBanner(message);
}def can_trade(stock_id: str) -> tuple[bool, str | None]:
splits = client.v2.market_data.stocks.splits.list_for_stock(stock_id=stock_id)
for s in splits:
if s.status == "IN_PROGRESS":
direction = "reverse split" if s.split_from > s.split_to else "stock split"
return False, (
f"Trading is paused for a {s.split_from}-for-{s.split_to} "
f"{direction}. It resumes on {s.ex_date}."
)
return True, None
allowed, message = can_trade("stock_xxx")
if not allowed:
show_banner(message)Reconcile balances after a split completes
const accountID = 'your-account-id';
// Snapshot before the split
const beforePortfolio = await client.v2.accounts.getPortfolio(accountID);
const before = new Map(beforePortfolio.assets.map((b) => [b.stock_id, b.amount]));
// ... after status flips to COMPLETE ...
const portfolio = await client.v2.accounts.getPortfolio(accountID);
for (const b of portfolio.assets) {
const old = before.get(b.stock_id);
if (old !== undefined && old !== b.amount) {
console.log(`${b.symbol} on chain ${b.chain_id}: ${old} -> ${b.amount}`);
ledger.setBalance(accountID, b.stock_id, b.amount, 'stock_split');
}
}
// Reverse splits may also produce a USD+ credit in lieu of a fraction
const cash = await client.v2.accounts.getCash(accountID);ACCOUNT_ID = "account_xxx"
# Snapshot before the split
before = {
b.stock_id: b.amount
for b in client.v2.accounts.get_portfolio(account_id=ACCOUNT_ID).assets
}
# ... after status flips to COMPLETE ...
portfolio = client.v2.accounts.get_portfolio(account_id=ACCOUNT_ID)
for b in portfolio.assets:
old = before.get(b.stock_id)
if old is not None and old != b.amount:
print(f"{b.symbol} on chain {b.chain_id}: {old} -> {b.amount}")
ledger.set_balance(ACCOUNT_ID, b.stock_id, b.amount, reason="stock_split")
# Reverse splits may also produce a USD+ credit in lieu of a fraction
cash = client.v2.accounts.get_cash(account_id=ACCOUNT_ID)Each
DshareBalancein the portfolio containsstock_id,symbol,amount,chain_id, andtoken_address.
Updated about 3 hours ago
