Risk, origins and asset classes
The slider's two effects#
Policy.risk is a u8 from 0 to 100, chosen by the deployer inside the origin's ceiling. It does two unrelated things, both enforced on chain.
| Effect | Enforced in | Failure |
|---|---|---|
| Caps deployment: at most risk percent of vault equity may sit at venues at once | passive_deploy and agent_fund_venue | RiskCapExceeded |
| Gates asset classes: a class is unreachable until risk clears its minimum | register_venue_account | RiskBelowAssetClass |
cap = equity * risk / 100 risk 0 ──▶ cap is zero. The bankroll accumulates and never trades. risk 40 ──▶ at most 40% of equity may be out at venues at once. risk 100 ──▶ the entire vault may be deployed.
The cap is checked against equity, which is the bankroll plus everything already deployed, so it is a cap on the share of the whole vault rather than on a single transfer. A policy already at its cap cannot deploy another unit until either the bankroll grows or something comes back.
The origin also caps the slider itself: risk may not exceed the origin's max_risk, checked at both init_policy and set_policy, failing with RiskAboveOrigin.
The seven asset classes#
One bit each, so an origin's mask is a set of them. The bit position is also the index into the min_risk_for_class array.
| Class | Bit | Value | Minimum risk |
|---|---|---|---|
| STABLE | 0 | 1 | 0 |
| MAJOR | 1 | 2 | 0 |
| SOL_MEME | 2 | 4 | 0 |
| STOCK | 3 | 8 | 10 |
| PERP | 4 | 16 | 80 |
| OPTION | 5 | 32 | 80 |
| LP | 6 | 64 | 0 |
| (unused) | 7 | 128 | 0 |
The array is [u8; 8] while only seven classes are defined, so the eighth slot exists and is zero. Minimum risk values are the live Config.min_risk_for_class on this deployment.
A venue account carries exactly one class bit. register_venue_account requires asset_class to be non-zero and a power of two, so the two gates below are unambiguous.
The immutable risk gate#
"minRiskForClass": [0, 0, 0, 10, 80, 80, 0, 0]In plain terms, on this deployment:
- stablecoins, majors, Solana memecoins and LP positions are reachable at any risk, including risk 0;
- stocks need risk at least 10;
- perpetuals and options both need risk at least 80.
That 80 is the number that decides whether a policy can ever trade perpetuals or options at all, so any interface offering a perps or options choice has to show it next to the risk slider.
Where the gate actually bites#
init_policy ──▶ checks risk <= 100 and risk <= origin.max_risk
✔ a risk-50 perps policy is created
register_venue_account ──▶ checks risk >= min_risk_for_class[PERP] = 80
✘ RiskBelowAssetClassThere is a way out, as long as the policy is not locked: set_policy can raise the risk, and the venue registration will then succeed. After lock_policy the risk is frozen, and a locked policy created below the threshold can never register the venue it needs.
The gate lives in the second transaction, so a policy can be created below the threshold and looks fine until it tries to open the venue it was created for.
Origins#
An origin is where an agent comes from: a protocol-level registry entry holding a name, an asset_class_mask and a max_risk. The mask decides which asset classes a policy of that origin may ever touch, so the origin genuinely determines what gets bought. A venue account outside the mask cannot be registered at all.
That is why the registry below contains two origins named perps and two named options: the later pair could not replace the earlier pair, only sit beside them.
The origin registry, live#
All six of these are written on mainnet, read back through /origins. Every origin currently has max_risk 100, so the origin ceiling is not what limits any of them; the class gate is.
| Id | Name | Mask | Classes | Exclusive? |
|---|---|---|---|---|
| 1 | stocks-only | 79 | STABLE MAJOR SOL_MEME STOCK LP | no |
| 2 | perps-and-options | 127 | all seven | no |
| 3 | options | 111 | STABLE MAJOR SOL_MEME STOCK OPTION LP | no |
| 4 | perps | 95 | STABLE MAJOR SOL_MEME STOCK PERP LP | no |
| 5 | perps | 17 | STABLE PERP | yes |
| 6 | options | 33 | STABLE OPTION | yes |
{
"source": "chain:Origin accounts",
"items": [
{ "id": 1, "pubkey": "4LjMhsTWQrwqLPbHY6z5gf1CCxVennQcMSKmwTpfAw5g", "name": "stocks-only", "asset_class_mask": 79, "max_risk": 100 },
{ "id": 2, "pubkey": "2tz9VZc4HDbsL5txrrhrv1EVBxxLZXpSS2LucJ75nL2v", "name": "perps-and-options", "asset_class_mask": 127, "max_risk": 100 },
{ "id": 3, "pubkey": "8N9BrFu5uexDaA47sms2qQnA4Lb6BGxe6qXKoNNvA42R", "name": "options", "asset_class_mask": 111, "max_risk": 100 },
{ "id": 4, "pubkey": "4eW1t36s7CbF3vjYHsC28z53JrKsGrLNDbw71Q9eSTSn", "name": "perps", "asset_class_mask": 95, "max_risk": 100 },
{ "id": 5, "pubkey": "HG4WUkdk1gHpnPgKcYQLcdesCdjjz4FDNnDmUjryWipy", "name": "perps", "asset_class_mask": 17, "max_risk": 100 },
{ "id": 6, "pubkey": "3MByFXNXWXpyzrHJdoPpidP1GbqvG8EPAdzqFktADiW9", "name": "options", "asset_class_mask": 33, "max_risk": 100 }
]
}The exclusive pair, and why it exists#
Ids 3 and 4 carry the right venue bit but also STABLE, MAJOR, SOL_MEME, STOCK and LP. An interface pill labelled "PERPS" over id 4 would claim more exclusivity than the chain actually enforces: a policy on that origin could legitimately register a stock venue instead.
Ids 5 and 6 were added for exactly that pill. Mask 17 is STABLE plus PERP; mask 33 is STABLE plus OPTION. Nothing else. Those two are the exclusive pair the interface uses, and mask 17 has a working precedent as the test origin in the program's own test suite.
| If an interface says | Use origin | Because |
|---|---|---|
| PERPS, exclusively | id 5, mask 17 | STABLE and PERP only, nothing else registrable |
| OPTIONS, exclusively | id 6, mask 33 | STABLE and OPTION only |
| Perps and options together | id 2, mask 127 | every class |
| Stocks, no derivatives | id 1, mask 79 | no PERP or OPTION bit at all |
Why the LP bit buys nothing#
The LP bit is deliberately absent from origins 5 and 6, and its absence costs nothing. compound_lp does not go through a registered venue account at all: it forwards an instruction to the pinned AMM program with remaining accounts, signing as its own lp_auth address, and never touches the allowlist. So an LP bit in a mask does not enable anything that would otherwise be blocked.
Reading a policy's risk position#
"origin": {
"pubkey": "HG4WUkdk1gHpnPgKcYQLcdesCdjjz4FDNnDmUjryWipy",
"name": "perps",
"assetClassMask": 17,
"maxRisk": 100
},
"risk": 80,
"targetLeverageX": 5,
"venueAccounts": [
{
"tokenAccount": "3KZXskLQHPTu2Vtjb4MVBpF4VVFjiUouLMjGzZ7bAVdF",
"kind": "Remote",
"assetClass": 16,
"revoked": false,
"bridgedOut": { "value": "50000000000", "source": "chain:VenueAccount.bridged_out" }
}
]Risk is exactly 80, which is the minimum the PERP class requires, and assetClass 16 is the PERP bit. Had the deployer set risk 79, the venue registration would have failed and this policy would have no way to trade.