xVault Docs
Integrations

Corporate actions

How xVault responds to each of the 13 corporate-action types exposed by the xStocks API.

The xStocks API surfaces upcoming corporate actions via GET /public/corporate-actions/upcoming. Each node carries a caType enum with thirteen possible values. xVault's corp-action-watch job polls hourly and maps each type to an action:

caTypeVault response
CashDividendMultiplier increase — no action, NAV auto-adjusts.
StockDividendMultiplier increase — no action, NAV auto-adjusts.
ForwardSplitMultiplier change — MultiplierMismatch guard re-aligns automatically.
ReverseSplitMultiplier change — same as above.
UnitSplitMultiplier change — same as above.
SpinOffPause vault. Multisig decides whether to distribute the spin or force-exit the leg.
CashMergerPause vault. Likely force-exit the leg and rebalance into the remainder.
StockMergerPause vault. May require basket reconfiguration by multisig.
StockAndCashMergerPause vault. Hybrid — requires manual reconciliation.
RedemptionPause vault. Force-exit the affected leg.
WorthlessRemovalPause vault. Remove the leg and redistribute weight.
NameChangeUI-only: update the symbol label; no on-chain action.
RightsDistributionPause vault. Rights are typically handled off-chain; unclaimed rights are usually worthless.

Why dividends need no action

Backed applies cash and stock dividends as a multiplier increase on the underlying xStock. The vault's raw balance is unchanged, but its scaled balance — and therefore its NAV contribution — rises by exactly the dividend net of withholding tax.

Use `netCashflowUsd`, not gross

Dividends are paid net of withholding tax. Use netCashflowUsd when projecting APY from historical actions — using the gross figure overstates yield.

Pause semantics

xVault uses a granular PauseFlags struct rather than a single on/off switch. Each flag gates a distinct class of instruction, so narrow events (e.g. a 30-minute multiplier activation window) do not force a full deposit / withdraw outage.

FlagBlocksTypical trigger
deposits_pauseddeposit, collect_mgmt_fee, collect_perf_feePending corp-action within 48 h
rebalance_pausedrebalance_legAsset halt, corp-action window, forced exit in progress
oracle_frozendeposit, withdraw_usdc, fee collectionMultiplier activation window (\u00b115 min per Backed guidance)
haltedAll of the aboveManual emergency stop

withdraw_in_kind remains open under every flag combination \u2014 users are never trapped.

Admins set flags with set_pause_flags(PauseFlags). The legacy set_paused(bool) ix maps to halted for backward compatibility.

Watcher is a reconciler, not a one-shot trigger

xStocks corp-actions are mutable: they carry a status field (Initial, Corrected, Scheduled, Cancelled, Suggested, Dismissed) and a version counter. Fields like effectiveTimeUtc and multiplierNew can be amended post-publication. The keeper's corp-action-watch job is a reducer keyed on (eventId, version):

  • Apply pause when a severe action is Initial | Corrected | Scheduled and within the 48 h window.
  • Re-apply on version bump (Corrected) so amended effective times are respected.
  • Clear pause on Cancelled, Dismissed, or when the event drops from /upcoming (meaning effectiveTime has passed and post-action cleanup has run).
  • Ignore Suggested \u2014 that status is advisory only.

State is persisted in Redis so intents are idempotent across keeper restarts and leader failover. See services/keeper/src/corp-actions.ts for the reducer and its unit tests.

Multiplier mismatch guard

The vault program carries a MultiplierMismatch check on every balance-moving ix:

require!(
  current_multiplier == expected_multiplier,
  ErrorCode::MultiplierMismatch
);

This is defense-in-depth against mid-flight multiplier updates. If the keeper has stale state while an update is applied on-chain, transfers revert cleanly rather than settle at a stale scaled amount.

Watcher job contract

corp-action-watch runs hourly and:

  1. Fetches GET /public/corporate-actions/upcoming and filters to SKU-member symbols.
  2. Loads the prior (eventId, version, paused) map from Redis.
  3. Calls reconcileCorpActions(actions, prior) \u2014 a pure reducer that emits apply / clear intents only when the required on-chain state differs from prior state.
  4. For each intent, logs a structured record for the multisig signers and (once SDK builders land) submits set_pause_flags against the affected SKUs.
  5. Surfaces upcoming actions on the dapp banner so users see them before they deposit.

See Operations → Keeper and oracles for how this job interacts with the rebalancer.

On this page