Sign in

Some inputs are not good enough to act on:

  • The oracle doesn’t give a high confidence score about the price.
  • The price jumped too far since the last slot.
  • The random number has not arrived yet.

On Solana, just as on any other blockchain where transactions are atomic, your program cannot wait for a better input. It has to decide now, with the data it was given. If acting is its only output, it will act on bad data too. So a failure to proceed alongside with its reason must be an output as well. The refusal is not a crash, it's just one of the outcomes the protocol was designed to have.

Examples

Drift. The program grades the oracle before every action. The band around the price (Pyth's confidence interval) is too wide: uncertain. The update is too old: stale. The price is too far from its own five-minute average: too volatile. Each action has its own rule. The pool fills only on a price that passed every check.

Which oracle grades each Drift action accepts A table with seven oracle grades as rows, from valid to non-positive, and five actions as columns. An immediate fill against the AMM accepts only a valid price. An order priced relative to the oracle gets a price only while the oracle is valid, stale for the AMM or short of data points. A margin check accepts the same three grades. A liquidation accepts everything except a too volatile or non-positive price. A funding update accepts everything except a too uncertain, too volatile or non-positive price. The riskiest action for the protocol needs the best price, and liquidation, which protects the protocol, accepts the most. Drift: which oracle grade each action accepts oracle grade AMM fill oracle order margin check liquidation funding valid ok ok ok ok ok stale for AMM older than 10 slots refused ok ok ok ok insufficient data too few price sources refused ok ok ok ok stale for margin older than 120 slots refused refused refused ok ok too uncertain confidence above 2% refused refused refused ok refused too volatile 5x away from its TWAP refused refused refused refused refused non-positive price at or below 0 refused refused refused refused refused The AMM takes the other side of the trade, so it needs the best price. Liquidation protects the protocol, so it accepts the most. Limits are the defaults in Drift's code.

The refusals also look different: when the AMM cannot fill because the price is not good enough, the program just logs the AMM cannot fill and does not fail the transaction, the order stays open and another user can still match it.

Kamino keeps a plausible range per token. A USDC price far from $1 is refused, even if the oracle is confident. But for different actions there are different constraints. If you need to borrow, the protocol should be confident that everything is in normal state. A liquidation needs only a loaded, fresh, usable price, so a price that drifted from its average or left its plausible range still allows liquidation, while it stops every new borrow.

Kamino stores the result of its price checks A reserve refresh runs six price checks: price loaded, price age, TWAP age, distance from TWAP, plausible range and usage allowed. In this example the price is too far from its TWAP, so that flag stays unset, and the other five are set. The flags are stored in the reserve as a status. A borrow needs all six flags, finds one missing and fails with a stale reserve error. A liquidation needs only price loaded, price age and usage allowed, finds them, and goes through. The refusal is stored in the reserve refresh reserve: run every price check reserve status, valid for this slot only price loaded set TWAP age set price age set near its TWAP unset usage allowed set plausible range set a failed check is logged, not thrown borrow: needs all six one flag missing fails: reserve stale liquidation: needs three loaded, age, usage allowed goes through The checks run once. Each instruction reads the answer and asks only for what it needs.

A coin flip on Switchboard VRF stores its refusal. While a round is open, the player cannot start a new one. Only the oracle's answer closes it. The settling instruction asks for the revealed value. If it is not revealed, the instruction fails with a "randomness not resolved" error.

Sometimes a trade on a live market is refused because the oracle is not sure. A user can lose some trades. But at least the protocol is not lost, when the oracle is wrong.