uUSDv
A Token-2022 token whose every unit is a share of the USDv the uUSDv program holds, worth more USDv as Solomon Labs' rewards are released to holders at one price for wrapping and unwrapping.
uUSDv 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, 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 without a fee, and use uUSDv anywhere a Token-2022 token goes.
Nimbus never holds the USDv. The program does, and its rules below decide who can move it.
Addresses
- Program:
uUSDvQ2s5f8LirnG3Qqn6YEBkYTFJT8av2aaPjoUsmH - uUSDv mint:
uUSDvgstJR56s4qLHtMRsP2jhTHsJrBaY4EUfBpkKAa
The mint is an address the program derives from its own id, so no other mint can pass for uUSDv. uUSDv has the same six decimals as USDv.
How the price works
Every wrap and unwrap uses one price: the program's assets divided by the number of uUSDv shares.
Here cash is the USDv in the program's two accounts.
The assets are the USDv the program holds, less every reward it has not yet released. A reward raises the program's balance the moment it arrives, but it raises the price only as it is released.
Wrapping
A wrap deposits USDv and mints the shares it buys at the current price:
The program counts the USDv that actually reached its account, never more than the amount asked, so a token with a transfer fee cannot buy shares it did not pay for.
Unwrapping
An unwrap burns shares and pays the USDv they are worth:
Rounding
Both results are rounded down, and the rewards still releasing are counted in the program's favour on each side: rounded down when a wrap prices a deposit and up when an unwrap prices a payout. A wrap therefore never buys a share for less than an unwrap pays for it, and no sequence of wraps and unwraps returns more USDv than went in. What rounding costs is at most a few raw units, millionths of a USDv.
The starting price
The program opens holding a seed of 1 USDv, wrapped into 1,000,000 raw shares that no instruction can ever unwrap. One uUSDv starts at exactly one USDv, and the seed keeps the share count above zero for good, so the first depositor cannot set the price by being alone.
How rewards are released
The release window
Each reward releases in a straight line over the release window, starting when the program books it:
Halfway through the window, half of the reward has reached the price. At the end of the window all of it has.
The window is set once, at launch, and can never be changed. It must be between 14 hours and 70 days, in whole multiples of 14 seconds. It should be at least as long as the longest gap between Solomon's payments, late payments included: with a shorter window, a reward could finish releasing before the next one arrives, and someone who wrapped just before a payment would collect a share of it after holding for only part of the period it pays for.
Release schedules
The program divides time into periods one fourteenth of a window long and keeps one release schedule per period, so at most fifteen run at once. Rewards that arrive within the same period join one schedule. Its start moves to the average arrival time of everything in it, weighted by amount and rounded later, so joining releases nothing early. Rounding it earlier would release a sliver at once, which someone holding most shares could collect in the same transaction.
The release cap
How fast rewards turn into price is capped. The release cap is a yearly rate, between 5% and 20% of the assets, chosen at launch and adjustable by governance within that range.
When a reward arrives, up to room of it starts releasing at once in the current schedule. The
rest waits in the backlog. The backlog drains only at whatever part of the cap rate the
schedules leave unused:
A schedule filled to the full room releases exactly at the cap rate, so while it runs the backlog waits, and once it ends the backlog drains at the full cap. The cap rate is recalculated from the assets every time the program books anything.
The cap exists so that no single event moves the price at once: a large donation, Solomon catching up on missed payments, or a repayment after a loss all raise the price at the cap's pace. That pace also limits what anyone can gain by wrapping just before such an event.
A worked example
Take a program holding 1,000,000 USDv of assets, a 10% cap and a 14-day window.
- The cap rate is 100,000 USDv a year, about 273.97 USDv a day, and the cap over one window is about 3,835.62 USDv.
- A 2,000 USDv reward fits inside the room, so all of it releases over the 14 days, about 142.86 USDv a day. A holder with 1% of the shares throughout gains 20 USDv. A holder with 1% who unwraps after 7 days gains 10 USDv.
- A 5,000 USDv reward exceeds the room. About 3,835.62 USDv releases over the 14 days at the full cap, and about 1,164.38 USDv waits in the backlog. When the schedule ends, the backlog drains at the cap rate, so the whole reward has reached the price after about 18 days.
Figures are rounded, and the cap rate grows slightly as rewards raise the assets.
Joining and leaving
Leaving forfeits only rewards still releasing
When someone unwraps, every release schedule shrinks in proportion to the shares that remain. The leaver's part of the rewards still releasing moves to the backlog, where it drains at the cap's spare room to everyone who stays. The leaver keeps everything already released.
An exit does not move the price for anyone else. The payout comes out of the assets in exact proportion to the shares burned, and the forfeited rewards stay in the unreleased total, only moving from the schedules to the backlog:
Joining buys no rewards already in flight
A wrap prices against the assets, which exclude everything still releasing. A newcomer pays the price released so far and gains only as the rewards still in flight release while they hold.
Edge cases
Unwrapping before any reward is released
You get back what you wrapped and nothing more. With no reward released in between, the price at unwrap is the price you wrapped at. Before any reward has ever been released, that price is exactly one, and a wrap followed by an unwrap returns the same number of raw units. Once rewards have moved the price, a round trip with nothing released in between returns what you wrapped, less at most a few raw units of rounding.
Wrapping just before a reward and unwrapping just after
You earn only for the seconds you held. The reward reaches the price in a straight line over the window, so an unwrap moments after it arrives collects almost nothing of it, and the rest goes to those who stay.
Leaving halfway through a release
You keep your share of the half already released. Your share of the other half goes to the backlog and reaches the holders who stay.
A reward while nobody else holds uUSDv
It releases to the shares that exist, which may be only the seed. The seed's shares can never be unwrapped, so what accrues to them stays in the program for good and adds to the price of every later share in proportion.
A large donation or a catch-up payment
Anyone may send USDv straight to the program's principal or yield account. The next booking counts it as a reward. It releases at the cap's pace, partly through the backlog when it is larger than the room, never at once.
USDv sent anywhere else
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.
Very small wraps and unwraps
A wrap worth less than one raw share is refused, and so is an unwrap that would pay less than one raw unit of USDv.
Slippage limits
Every wrap names the fewest shares it accepts and every unwrap the least USDv it accepts. Either is refused, with nothing moved, if the price moved past that limit before it landed.
The deposit limit
A wrap is refused if it would take the program's balance past the deposit limit. Governance can lower the limit below the current balance: that refuses new wraps until unwraps bring the balance under it, and never blocks an unwrap.
Paused wraps
The guardian can pause wrapping. A pause never touches unwrapping, and only governance can resume wrapping.
When USDv itself is paused
If Solomon pauses USDv, no USDv can move, so neither wraps nor unwraps can run until USDv resumes.
When every share is worth nothing
After a loss that leaves the assets at zero, wraps are refused, because no price exists to buy at. Any USDv that arrives then goes straight to the price instead of releasing, so a repayment can never be locked away behind a cap that releases a share of nothing.
Destinations that require a memo
When the account an unwrap pays into requires a memo on incoming transfers, the program sends the
memo uUSDv unwrap first.
Wallets with CPI Guard
An account with Token-2022's CPI Guard on must wrap or unwrap through a delegate.
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.
Who bears a loss
The missing USDv is taken in this order:
- from the backlog;
- then from every release schedule, in proportion;
- only then from the price, where the program records it as a loss.
Rewards not yet released absorb a loss before any holder's principal does.
Repayment
While the program is frozen, USDv that arrives repays the recorded loss straight to the price, before anything else. Nobody can enter or leave in the meantime, so a repayment reaches exactly the holders who bore the loss, at once. Anything beyond the loss is ordinary income and releases under the cap.
Unfreezing
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.
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 reward that has arrived, so the price is current. A wrap is refused while the program is frozen or wrapping is paused, when it would pass the deposit limit, when it is worth less than one share, 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. 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. An unwrap is also refused when it pays less than one raw unit, less than the caller's minimum, or would reach into the seed.
Checkpoint and quote
checkpoint books whatever the program's two USDv accounts show: a reward, a donation, USDv taken
from either account, or a frozen account. Anyone may call it, and it never moves tokens. Wraps and
unwraps run the same booking first, so a checkpoint only brings the books up to date between them.
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. Unwraps pay from the yield account first, so wrapped USDv leaves the principal account only once the yield account is empty. Solomon pays rewards on the two accounts' combined balance, so rewards compound without being moved.
Initialize
Opens the program once. It creates the USDv accounts, the uUSDv mint and the account that holds the seed, and sets the release window, the release cap, the deposit limit and the guardian. It also writes uUSDv's name, symbol and URI into the mint, where wallets read them. Only the bootstrap key built into the program may run it.
Launch settings
| Setting | Allowed values | Who can change it later |
|---|---|---|
| Release window | 14 hours to 70 days, in multiples of 14 seconds | Nobody |
| Release cap | 5% to 20% of the assets a year | Governance |
| Deposit limit | 1 USDv or more | Governance |
| Guardian | Any key | Governance |
| Name, symbol and URI | Up to 32, 10 and 200 bytes | Governance |
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, governance may resume wrapping, set the release cap within its range, 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 when that pause began, so a resume can never be queued ahead of a pause, and one queued before a later pause cannot end it. 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 Nimbus 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, so a hook set later does not stop it.
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.
Reference
| 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. |
libs/typescript/errors/error-manifest.json.Last updated