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:
caType | Vault response |
|---|---|
CashDividend | Multiplier increase — no action, NAV auto-adjusts. |
StockDividend | Multiplier increase — no action, NAV auto-adjusts. |
ForwardSplit | Multiplier change — MultiplierMismatch guard re-aligns automatically. |
ReverseSplit | Multiplier change — same as above. |
UnitSplit | Multiplier change — same as above. |
SpinOff | Pause vault. Multisig decides whether to distribute the spin or force-exit the leg. |
CashMerger | Pause vault. Likely force-exit the leg and rebalance into the remainder. |
StockMerger | Pause vault. May require basket reconfiguration by multisig. |
StockAndCashMerger | Pause vault. Hybrid — requires manual reconciliation. |
Redemption | Pause vault. Force-exit the affected leg. |
WorthlessRemoval | Pause vault. Remove the leg and redistribute weight. |
NameChange | UI-only: update the symbol label; no on-chain action. |
RightsDistribution | Pause 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.
| Flag | Blocks | Typical trigger |
|---|---|---|
deposits_paused | deposit, collect_mgmt_fee, collect_perf_fee | Pending corp-action within 48 h |
rebalance_paused | rebalance_leg | Asset halt, corp-action window, forced exit in progress |
oracle_frozen | deposit, withdraw_usdc, fee collection | Multiplier activation window (\u00b115 min per Backed guidance) |
halted | All of the above | Manual 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 | Scheduledand within the 48 h window. - Re-apply on
versionbump (Corrected) so amended effective times are respected. - Clear pause on
Cancelled,Dismissed, or when the event drops from/upcoming(meaningeffectiveTimehas 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:
- Fetches
GET /public/corporate-actions/upcomingand filters to SKU-member symbols. - Loads the prior
(eventId, version, paused)map from Redis. - Calls
reconcileCorpActions(actions, prior)\u2014 a pure reducer that emitsapply/clearintents only when the required on-chain state differs from prior state. - For each intent, logs a structured record for the multisig signers and (once SDK builders land) submits
set_pause_flagsagainst the affected SKUs. - 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.