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:

FieldMeaning
split_fromShares you hold before
split_toShares 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 reverse
is_reverse = split.split_from > split.split_to
multiplier = split.split_to / split.split_from   # 10.0 forward, 0.025 reverse

Dates and status

FieldMeaning
record_dateDate a holder must hold to be entitled
payable_dateTrading halts at the end of this day
ex_dateTrading resumes at split-adjusted prices
statusPENDINGIN_PROGRESSCOMPLETE

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

  1. Trading halts. New orders are rejected, active orders are cancelled, and the dShare™ token is paused.
  2. The token is rebased to match the split ratio.
  3. Dinari verifies balances across all systems and confirms the underlying stock changes are reflected.
  4. 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 after COMPLETE.
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.

  1. Poll for splits daily on the stocks your users hold. Splits are scheduled well in advance.
  2. Pause trading in your interface while status is IN_PROGRESS, and expect open orders to be cancelled.
  3. Re-read balances once status is COMPLETE and update your ledger. For reverse splits, check cash balances too — a USD+ credit may have replaced a fraction.
  4. 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 StockSplit contains id, stock_id, split_from, split_to, record_date, payable_date, ex_date, and status.


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 DshareBalance in the portfolio contains stock_id, symbol, amount, chain_id, and token_address.



Did this page help you?