> For AI agents: the documentation index is at https://docs.umbraprivacy.com/llms.txt, and every page is available as Markdown at its URL with .md appended.

# uUSDv (/docs/architecture/uusdv)

uUSDv (Umbra USDv) carries Solomon Labs' USDv yield into places USDv itself earns nothing. One
uUSDv is a share of the USDv the uUSDv program holds. Solomon pays rewards to the program's yield
account, and the program releases them to every share, so a uUSDv balance never changes while each
unit becomes worth more USDv. Anyone may wrap or unwrap at any time, without permission, and use
uUSDv anywhere
a Token-2022 token goes.

## How the price works

A share's price is the program's **assets** divided by the share count. Wrapping buys shares at
that price, and unwrapping sells them at that price. Both round in favour of the shares that stay,
so no sequence of wraps, unwraps and payments within one moment returns more USDv than went in.

The assets are the USDv the program holds, less everything not yet released. A payment from
Solomon raises the balance at once but the price only gradually: it releases linearly over a
**release window** fixed at launch, counted from when it arrived. Someone who wraps just before a
payment and unwraps just after it earns only the seconds they held. Payments that arrive within the
same fourteenth of a window share one release schedule, so the program needs at most fifteen of
them.

Releases are capped. The **release cap**, a yearly rate between 5% and 20% of the assets set when the program is initialised, limits how fast
income becomes price. Income beyond what the cap allows over one window waits in a **backlog**,
which drains only at the part of the cap that the scheduled releases leave unused. A large
donation, or Solomon catching up on missed payments, therefore raises the price at the cap's pace
rather than at once.

When someone unwraps, they give up their share of the income still releasing. That share moves to
the backlog, so those who stay never collect it at once: it releases at the cap's spare room, which
can be sooner than the payment it came from would have released, but never faster than the cap.

The window must be at least as long as the longest gap between Solomon's payments, late payments
included. A shorter window lets a well-timed entrant collect part of a payment without having held
through it. Measurements behind these choices, from the design's simulator, are in the program's
property tests.

### The formulas

All amounts are raw USDv units, which uUSDv shares since both have six decimals. $C$ is the USDv
the program has booked in its two accounts, $S$ the uUSDv share count, $W$ the release window in
seconds, $c$ the release cap in basis points and $Y = 31\,536\,000$, the seconds in a 365-day year.
Time $t$ is the cluster's clock.

**Assets.** The price counts the USDv held, less the income still releasing in the buckets, $U(t)$,
and the backlog not yet drained, $B(t)$:

$$
A(t) = C - U(t) - B(t), \qquad P(t) = \frac{A(t)}{S}
$$

$P(t)$ is the USDv one uUSDv is worth.

**Release.** A bucket holding $a_i$ that started releasing at $s_i$ has left, at time $t$:

$$
U_i(t) = a_i \cdot \frac{\min\bigl(W,\ \max(0,\ s_i + W - t)\bigr)}{W}, \qquad U(t) = \sum_i U_i(t)
$$

**Wrap and unwrap.** Unwrapping $s$ shares pays, and wrapping $d$ USDv mints:

$$
\text{payout} = \left\lfloor \frac{s \cdot A_{\text{redeem}}(t)}{S} \right\rfloor, \qquad
\text{shares} = \left\lfloor \frac{d \cdot S}{A_{\text{mint}}(t)} \right\rfloor
$$

$A_{\text{redeem}}$ rounds each $U_i$ up and $A_{\text{mint}}$ rounds each down, so both sides round
in favour of the shares that stay. The two differ by at most one raw unit per bucket.

**The cap.** The cap releases at most this much a second, and this much over one window:

$$
r_{\text{cap}} = \frac{A \cdot c}{10^4 \cdot Y}, \qquad R_W = \left\lfloor \frac{A \cdot c \cdot W}{10^4 \cdot Y} \right\rfloor
$$

**Income.** A payment $I$ found by a checkpoint goes to the current slot's bucket up to the room the
cap leaves over one window, and the rest to the backlog:

$$
I_{\text{bucket}} = \min\bigl(I,\ \max(0,\ R_W - U)\bigr), \qquad I_{\text{backlog}} = I - I_{\text{bucket}}
$$

**Backlog.** The backlog drains at whatever part of the cap the buckets leave unused, until it is
empty. $r_{\text{cap}}$ is fixed at the assets of the last operation:

$$
\frac{dB}{dt} = -\max\Bigl(0,\ r_{\text{cap}} - \sum_{i \text{ releasing}} \frac{a_i}{W}\Bigr)
$$

**Leaving.** An unwrap of $s$ shares scales every bucket by $\frac{S - s}{S}$, and what that removes
from $U$ joins the backlog, so the price does not move when someone leaves.

## The freeze

Only an unwrap moves USDv out of the program's two accounts, and the program records each account's
balance after every wrap and unwrap. Before doing anything else, every wrap, unwrap and checkpoint
compares those records with the real balances. If either account holds less than it should, even
by one raw unit and even when the other account rose by as much, USDv has left without an unwrap,
which only Solomon's powers over USDv could do. The program then **freezes**: nothing can be
wrapped or unwrapped until governance unfreezes it. Solomon freezing either account freezes the
program the same way. Permanent counters record every unit that ever left each account without an
unwrap.

A refused wrap or unwrap cannot record anything, because a failed instruction changes nothing. The
next one finds the same shortfall and is refused too, and the first checkpoint records the freeze
for good. Anyone may call a checkpoint.

The missing USDv is taken first from the backlog, then from the income still releasing, and only
then from the price, where the program records it as a loss. While the program is frozen, USDv that
arrives repays that loss straight to the price. Nobody can enter or leave in the meantime, so a
repayment reaches exactly the holders who bore the loss, at once.

Governance unfreezes the program through the timelock. Its proposal names the freeze it ends and
when that freeze began, so every new outflow is a new freeze that an earlier proposal cannot end.
An unfreeze is refused while either account is still frozen, and while a loss is still outstanding,
unless the proposal writes the rest off. A write-off declares the loss final: USDv that arrives
afterwards releases like any other income, because with wrapping open an instant repayment could be
timed by a newcomer. If a loss leaves the shares worth nothing, anything that arrives goes straight
to the price, so restitution can never be locked away.

## Instructions

### Wrap

Moves USDv from any account the signer owns or is delegated, and mints the shares it buys to any
uUSDv account. The program first books any payment that has arrived, so the price is current. It
counts what actually reached its account, not what was asked. A wrap is refused while the program
is frozen or wrapping is paused, when it would pass the deposit limit, or when it buys fewer shares
than the caller's minimum.

### Unwrap

Burns shares from an account the signer owns or is delegated, and pays the USDv they are worth to
any USDv account outside the program. The payout comes from the yield account and, for any part it
cannot cover, the principal account. When the destination requires a memo on incoming
transfers, the program logs one first. Neither the guardian nor governance can stop an unwrap. Only
a freeze does, and only USDv leaving the program's accounts, or Solomon freezing one of them, causes
one.

### Checkpoint and quote

`checkpoint` books whatever the program's two USDv accounts show: a payment, a donation, USDv
taken from either account, or a frozen account.
Anyone may call it, and it never moves tokens. `quote` returns what a checkpoint would leave,
without writing anything: the assets an unwrap prices against, the share count, and the assets a
wrap prices against. A lending market or any other integrator should price uUSDv with `quote`, never
with a market price, which a flash loan can move.

### The two USDv accounts

The program holds USDv in two accounts and counts their combined balance. Wraps pay into the
**principal account** and Solomon pays rewards into the **yield account**. No instruction moves USDv
from one to the other, so USDv leaves the principal account only through an unwrap. Solomon pays
rewards on the two accounts' combined balance, so rewards compound without being moved.

USDv sent to any other token account, including one that names a program authority as its owner, is
not counted and no instruction can move it.

### Initialize

Opens the program once. It creates the USDv accounts, the uUSDv mint and the account that holds the
**seed**: 1 USDv wrapped at launch, whose shares no instruction can move. The seed keeps the
share count above zero, so the first depositor cannot set the price. It also writes uUSDv's name,
symbol and URI into the mint, where wallets read them; governance can replace them later. Only the
bootstrap key built into the program may run it.

## Who controls what

### The guardian

One key may pause wrapping, at once. A pause never touches unwrapping, so it can slow money coming
in but never trap money going out.

### Governance

Through the [governance timelock](https://docs.umbraprivacy.com/docs/security/governance), governance may resume wrapping, set
the release cap between 5% and 20%, set the deposit limit, replace the guardian, unfreeze the
program, and replace uUSDv's name, symbol and URI. Each proposal names the value it replaces, so it
executes only against the state it was made for. A resume names the pause it ends and the moment
that pause began, so a resume can never be queued ahead of a pause, and one queued before a later
pause cannot end it; an unfreeze is bound to its freeze the same way. A resume ends only the
guardian's pause, never a freeze. A metadata update names the count of earlier updates, so it never
lands on metadata that changed after it was proposed. Governance cannot move USDv, mint or burn
uUSDv, or change the release window.

### The program upgrade authority

The upgrade authority can replace the program, as for every Umbra program.

### Solomon

USDv is Solomon's token, and its mint keeps powers the program cannot override. Solomon can pause
USDv, freeze an account, move tokens through its permanent delegate, or set a transfer hook. A
paused USDv stops wrapping and unwrapping until it resumes. A clawback from either of the program's
accounts, or freezing either of them, freezes the program until governance unfreezes it. The
program forwards the accounts a transfer hook needs, up to sixteen per transfer, so a
hook set later does not stop it; a hook that needed more would stop wrapping and unwrapping until
the program was upgraded.

The program relies on Solomon paying rewards on the principal and yield accounts' combined
balance, into the yield account, and on Solomon never using its permanent delegate on the
program's accounts or freezing them, acting against a thief's own addresses instead.

## Limits

- **Stolen USDv.** Wrapping is instant and unscreened, so a thief can wrap stolen USDv and unwrap
  clean USDv. Holders are protected only by Solomon acting against the thief's own addresses, as for every
  pool of USDv.
- **Missed payments.** When Solomon misses payments and then catches up, someone who wraps just
  before the catch-up shares in it. The cap bounds how much.
- **Changes in Solomon's schedule or rate.** When the yield still releasing drops at once, because
  Solomon pays more often, cuts its rate or resumes after a pause, whoever holds across the change
  receives a one-time share of it. This comes out of yield, never out of USDv deposited.
- **Large exits and entries.** A holder who leaves gives up their share of the yield still
  releasing; in steady flows this balances what entrants inherit.
- **CPI Guard.** An account with Token-2022's CPI Guard on wraps or unwraps through a delegate.

## Reference

**Errors**

| Code | Error                           | Meaning                                                                                                                                                                          |
| ---- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`  | `UnsupportedInstructionVersion` | Returned when the instruction envelope's version is not the one this build supports.                                                                                             |
| `1`  | `UnknownInstruction`            | Returned when the instruction tag has no handler.                                                                                                                                |
| `2`  | `MismatchingAccountKeys`        | The instruction supplied a number of accounts the handler does not name. Every destructure binds an exact arity, so this is returned for too many accounts as well as too few.   |
| `3`  | `InvalidPayload`                | Returned when the payload has the wrong length.                                                                                                                                  |
| `4`  | `NotOwnedByProgram`             | Returned when an account this program must own is owned by something else.                                                                                                       |
| `5`  | `PdaMismatch`                   | Returned when an account is not the address its seeds derive.                                                                                                                    |
| `6`  | `InvalidSystemProgram`          | Returned when the account in the System-program slot is not the System program.                                                                                                  |
| `7`  | `InvalidTokenProgram`           | Returned when the account in the token-program slot is not Token-2022.                                                                                                           |
| `8`  | `InvalidAssociatedTokenProgram` | Returned when the account in the associated-token-program slot is not that program.                                                                                              |
| `9`  | `InvalidMemoProgram`            | Returned when the account in the memo-program slot is not the Memo program.                                                                                                      |
| `10` | `AlreadyInitialized`            | Returned when the target account already exists, so creating it would overwrite state.                                                                                           |
| `11` | `NotInitialized`                | Returned when the wrapper's state account is absent, wrongly sized, or not active.                                                                                               |
| `12` | `Unauthorized`                  | Returned when the signer is not the key this instruction requires.                                                                                                               |
| `13` | `WrongUsdvMint`                 | Returned when the mint passed as USDv is not the USDv mint this program serves.                                                                                                  |
| `14` | `WrongUsdvDecimals`             | Returned when the USDv mint does not have the decimals this program was built for.                                                                                               |
| `15` | `WrongVaultAccount`             | Returned when an account is not the principal account, yield account, wUSDv mint or locked account the wrapper recorded at initialize.                                           |
| `16` | `TokenAccountMalformed`         | Returned when a token account's data is too short to be one.                                                                                                                     |
| `17` | `InvalidReleaseWindow`          | Returned when the release window is not a whole number of slots or is out of bounds.                                                                                             |
| `18` | `CapOutOfRange`                 | Returned when the release cap is outside the accepted bounds.                                                                                                                    |
| `19` | `TvlCapBelowSeed`               | Returned when the TVL cap is below the seed, which would refuse every wrap.                                                                                                      |
| `20` | `InvalidMetadata`               | Returned when the token name or symbol is empty, or a metadata field is not UTF-8.                                                                                               |
| `21` | `SeedNotReceived`               | Returned when the seed transfer did not deliver exactly the seed to the principal account.                                                                                       |
| `22` | `WrapsPaused`                   | Wrapping is paused. Unwrapping still works.                                                                                                                                      |
| `23` | `NoAssets`                      | wUSDv is worth nothing right now, so a deposit cannot be priced.                                                                                                                 |
| `24` | `TvlCapExceeded`                | The deposit would take the wrapper past its deposit limit.                                                                                                                       |
| `25` | `NothingMinted`                 | The deposit is worth less than one wUSDv raw unit.                                                                                                                               |
| `26` | `SharesBelowMinimum`            | The deposit buys fewer wUSDv than the minimum you set.                                                                                                                           |
| `27` | `SharesOverflow`                | The wUSDv supply would overflow.                                                                                                                                                 |
| `28` | `SharesOutOfRange`              | The amount of wUSDv to unwrap is zero or more than can be redeemed.                                                                                                              |
| `29` | `NothingPaid`                   | The wUSDv unwrapped is worth less than one USDv raw unit.                                                                                                                        |
| `30` | `PayoutBelowMinimum`            | The unwrap pays less USDv than the minimum you set.                                                                                                                              |
| `31` | `SourceIsVault`                 | Returned when a wrap names the wrapper's own principal or yield account as its source.                                                                                           |
| `32` | `DestinationIsVault`            | Returned when an unwrap names the wrapper's own principal or yield account as its destination.                                                                                   |
| `33` | `HookAccountsMismatch`          | Returned when the payload splits more transfer-hook accounts than the instruction carries.                                                                                       |
| `34` | `TooManyHookAccounts`           | Returned when one transfer carries more transfer-hook accounts than this program forwards.                                                                                       |
| `35` | `UnknownOperation`              | Returned when a proposal or authority record governs another operation than the one the instruction acts on.                                                                     |
| `36` | `TimelockNotElapsed`            | Returned when a proposal has not yet reached its \`execute_after_slot\`.                                                                                                         |
| `37` | `DelayOverflow`                 | Returned when \`current_slot + delay_slots\` would overflow a \`u64\` while queueing a proposal.                                                                                 |
| `38` | `ProposalExpired`               | Returned when a proposal matured but was not executed inside its grace window, so it expired.                                                                                    |
| `39` | `GovernanceDelayBelowMinimum`   | Returned when a governance delay is below the minimum the timelock enforces.                                                                                                     |
| `40` | `EpochExhausted`                | Returned when a record's epoch has reached its maximum, so it cannot advance.                                                                                                    |
| `41` | `WrongAccount`                  | Returned when an account is not a live governance record or pending proposal of this program.                                                                                    |
| `42` | `WrongRentRecipient`            | Returned when the rent recipient is not the account that paid the proposal's rent.                                                                                               |
| `43` | `TypedProposeRequired`          | Returned when the generic propose is asked to queue a reassignment, which must go through propose_reassign_authority so it waits every delay it shortens.                        |
| `44` | `ProposalVoided`                | Returned when the proposal was voided after it was queued: its operation's authority changed, its delay was raised, or its queue was voided. Anyone may close it.                |
| `45` | `ProgramFrozen`                 | The wrapper is frozen: USDv left its accounts other than through an unwrap, or one of its accounts is frozen. Nothing can be wrapped or unwrapped until governance unfreezes it. |
| `46` | `NotFrozen`                     | Returned when an unfreeze finds the wrapper not frozen.                                                                                                                          |
| `47` | `LossOutstanding`               | Returned when an unfreeze that writes nothing off finds a loss still outstanding.                                                                                                |
| `48` | `ProgramAccountFrozen`          | Returned when an unfreeze finds one of the wrapper's USDv accounts still frozen.                                                                                                 |

Read at build time from `libs/typescript/errors/error-manifest.json`.


Last updated 12 October 2026
