How it works.
One factory, one implementation, one clone per ETF. The operator relays Jev; the contract bounds what the operator can do.
The mandate
A mandate is a JSON object with name, symbol, legs (stock symbols in leg order), maxWeight (bps), everyMin, mandate (the text) and optional notes. Its keccak over canonical JSON (keys sorted at every level, no whitespace) is the config the ETF is created with. The creator publishes the text to POST /api/etf after launch; the store accepts it only if it hashes to the chain's config and its legs and cap match the chain. The operator refuses to ask Jev for an ETF whose published mandate does not hash to its config.
The questions
Every everyMin minutes, if the ETF holds a deposit, the operator builds a state: per leg the last print, previous close, today's move, 5 and 20 day returns, 20 day realized vol, 20 day high and low, the current weight and the current target; plus cash now, NAV, the cap, whether New York is open and the time. It then asks Jev one score question per leg and one for cash, each carrying the mandate in its instructions:
"w_NVDA": { "type": "score", "instructions": "Mandate: …\nGiven the market state, how much of the book should NVDA be right now?",
"legend": { "0": "none: exclude it from the book", "1": "light: well under its usual share", "2": "neutral: its usual share", "3": "heavy: close to the cap", "4": "maximum: at the cap" } }
"cash": { "type": "score", "instructions": "Mandate: …\nGiven the market state, how much of the book should sit in USDG cash right now?",
"legend": { "0": "none: fully invested", "1": "a little: about a tenth in cash", "2": "about a quarter in cash", "3": "about half in cash", "4": "all of it: step aside" } }
Jev answers each with a score from 0 to 4, a probability per level and a confidence.
The weight rule
The cash score is interpolated over its legend: 0 → 0%, 1 → 10%, 2 → 25%, 3 → 50%, 4 → 100% in cash. invested is one minus that. Each leg's weight is invested × score ÷ Σscores, capped at maxWeight; whatever the caps cut off stays in cash. If every score is zero the book goes to cash. The confidence stored on chain is the mean of the answers' confidences. The whole record (state, questions, answers, usage, decision) is written to the store first; its keccak is the answer passed to rebalance.
The vault
JevETF is an ERC-20 whose shares are a claim on tracked reserves per leg plus tracked USDG cash, never balanceOf. rebalance(targets, answer, confBps) is operator-only, refuses while paused, and never more often than every ten minutes. Inside one Uniswap v4 lock it sells every leg above its target value (marked at the pool's own price) down to the target, then buys every leg below it from the proceeds plus cash; every swap is priced before and after and the transaction reverts if the vault moved a pool more than maxImpactBps. Sub-cent moves are skipped. The operator cannot withdraw, cannot trade anything outside the ETF's legs, and cannot set a weight above the cap.
Deposits and redemptions
A deposit mirrors the book: the invested USDG is split across the legs and cash in the proportions the vault holds right now, and shares are priced at the smallest growth any leg saw, so a light fill on one leg is never paid for by the others. The first deposit (or one after everyone has left) follows the targets instead and opens at one share per dollar. ETH deposits are sold for USDG through the ETH/USDG pool first. Redemption is in kind: burn shares, receive your slice of every leg and of the cash, no swap, no fee, paused or not.
Paying Jev
Two entry fees, each at most 1%: jevFeeBps goes to the ETF's budget, creatorFeeBps to creatorOwed, which the creator claims. Once an hour the operator calls settle(calls, amount, ref) with the sum of the priced calls since the last settlement; the contract reverts unless amount ≤ calls × maxPerCall (factory-wide, at most one cent, set to 0.001 USDG) and amount ≤ budget. Calls are priced at Jev's own usage.cost when it reports one. A call that never reached the chain is not billed. Invariant: USDG.balanceOf(etf) ≥ cash + budget + creatorOwed; anything above it can be swept into cash with sync().
Verifying a call
Fetch /api/calls?a=<etf>. Take a record, drop hash, tx and block, serialise it canonically, keccak it: you get hash. Read lastAnswer() on the ETF, or the Rebalanced event's first topic: they match. Then check the decision.targets against the rule above from the answers, and the state against the tape.
Limits
| What | Value |
|---|---|
| Legs | 1 to 10, each listed once, from the factory's open listings |
| Cap per name | set at launch, 1% to 100% |
| Impact cap | set at launch, at most 5% per pool per swap |
| Fees | entry only, each at most 1%; no exit fee |
| Rebalance | operator only, at least 10 minutes apart |
| Per call | at most maxPerCall, factory-wide, hard cap 0.01 USDG |
| Opening deposit | at least 1 USDG invested; 1000 wei of shares burned |
| Marks | pool spot price; a dashboard number and a sizing input, not an oracle to lend against |
| Audit | none |