The three gates
Vaultable, accrual, exit. An asset must clear all three before it can back a market.
Whether an asset can back a permissionless market is a narrower question than whether the asset is any good. Three gates decide it, and each one exists because a specific thing goes wrong when it is skipped.
Can a freshly created PDA-owned ATA actually receive and send it?
Does its yield arrive in a way a stored-reserve curve can hold?
Can a holder get out, at size, without the issuer's permission?
Gate 1: vaultable
vaultVerdict in relayer/src/verify-mints.ts has exactly two clauses, and
that is not an accident of drafting.
if (das === "frozen") -> "a fresh vault ATA cannot receive it"
if (hook) -> "runs on every transfer_checked; we do not pass its accounts"
Every permissioned RWA checked so far fails one of exactly these two, and never a third. That is why the function has two clauses rather than a growing list.
Both are point-in-time verdicts. The transfer hook slot is present but
empty on most of the vaultable set, and its authority can fill it later. The
registry says to re-run npm run verify-mints before every launch batch.
What the live mint state says today
Better than expected on two counts, both re-read off the mainnet mint accounts
on 2026-08-25. defaultAccountState is present on 31 of the 44 hard entries and
is initialized rather than frozen on every one of them, so no issuer
allowlist gates a market PDA's vault. And the transferHook slot is present on
32 of the 44 with a null programId on every one, so transfer_checked needs no
extra accounts today.
Both are powers held in reserve rather than exercised, and both are one issuer decision away from changing.
Gate 2: accrual
yieldVerdict in relayer/src/staked.ts is pure: no RPC, no fetch, no clock.
The caller does the I/O and this decides, so the decision is testable without a
network.
The entry in seed.json is a claim. This is the cross-examination, and two of
its three checks can be settled against the chain and a live price. A registry
that took the issuer's word for how yield arrives would eventually list a
rebasing token and quietly hand its yield to whoever swaps first.
The one outright refusal
if (staked.accrual === "rebasing") {
return { ok: false, why:
"rebasing: the vault balance grows behind the pool's accounting, so the yield is " +
"paid to whoever swaps first rather than to holders" };
}
A balance that grows inside the vault is inventory the pool does not know it
has. peard_amm stores reserves rather than measuring them, precisely so a
stranger cannot reprice the curve by sending tokens in, and the same property
means a rebase is invisible to the curve and therefore free money for the first
arbitrageur.
The chain outranks the declaration
If the mint carries interestBearingConfig or scaledUiAmountConfig, accrual
is uiAmount whatever the entry claims, because those extensions make the
displayed balance diverge from the raw amount. The verdict notes the drift
rather than refusing, and adds the operational consequence: the UI must show the
scaled balance and never the raw reserve, or holders read a shortfall that is
not there.
The reverse is a refusal. Declaring uiAmount accrual on a mint carrying
neither extension means nothing scales the balance, so the entry is simply
wrong.
A soft signal worth keeping
A dollar that has been earning for a while and still marks at a dollar is either
brand new or not accruing the way the entry claims. valueAccruing below
$1.0050 gets a note, not a refusal.
Eight entries declare a yield, and seven of them are still hard
| Entry | Asset | Accrual | Redemption lock | Liquidity | Grade |
|---|---|---|---|---|---|
SYRUP-USDC | syrupUSDC | valueAccruing | 30 days | $11.7m | hard |
EUSX | eUSX | valueAccruing | 7 days | $8.2m | hard |
ONYC | ONYC | valueAccruing | none | $5.5m | hard |
USDY | USDY | valueAccruing | none | $2.9m | hard |
CETES | CETES | uiAmount | none | $105k | hard |
TESOURO | TESOURO | uiAmount | none | $83k | index since 2026-08-25 |
KTB | KTB | uiAmount | none | $33k | hard |
GILTS | GILTS | uiAmount | none | $13k | hard |
TESOURO still declares a yield and is no longer a vault candidate: it was
demoted to index grade on 2026-08-25 and its assetMint came off with the
grade. The declaration is kept because the entry is kept.
All four Etherfuse sovereign bonds accrue by uiAmount, which is why the UI rule
above matters for them specifically. Three of them are hard today.
Gate 3: exit
A cooldown is survivable only because holders of a market's quote asset exit by selling it, not by redeeming it. That is a claim about the secondary market, so it gets tested rather than assumed.
if (staked.lockDays > 0) {
if (depth !== "deep") {
return { ok: false, why:
`${staked.lockDays}-day redemption cooldown and the secondary market is ${depth}: ` +
`a holder could neither redeem nor sell` };
}
}
So lockDays is deliberately not disqualifying on its own. It only bites
when the secondary market is thin, which is exactly when a locked entry has to
clear the depth probe rather than the probe being optional.
SYRUP-USDC carries a 30-day cooldown and passes because $11.7m of pool
liquidity fills the probe. A 30-day cooldown on a $13k book would not.
primaryKyc is disclosed, not refused
Seven of the eight yield entries declare primaryKyc: true, meaning allowlisted
minting and redemption; EUSX is the one that does not. That is a note on the
verdict, because only the secondary market is open to holders anyway and the
secondary market is the exit being tested.
What clearing all three does not mean
It does not mean the asset is safe. It means the mechanics work. Everything in
ISSUER_POWERS is a power the issuer holds over tokens a vault already holds,
and none of them stops a market from working:
| Extension | What the issuer can do |
|---|---|
permanentDelegate | move your tokens |
pausableConfig | freeze all transfers |
defaultAccountState | gate who may hold it at all |
mintCloseAuthority | close the mint |
transferFeeConfig | take a cut of transfers |
transferHook | run code on every transfer |
scaledUiAmountConfig | restate every balance |
All of them belong in front of the user. Pairable carries the risk badge the
program writes, and seed.json mirrors issuerPowers per entry so the app can
show them without a mainnet round trip.
That mirror is currently incomplete, and it understates the issuer's
powers, which is the wrong direction to be wrong in. Read off the mainnet
mints on 2026-08-25, the chain carries scaledUiAmountConfig on 31 hard
entries where issuerPowers records 13, transferHook on 32 where it records
19, and defaultAccountState on 31 where it records 18. Re-derive it with
npm run verify-mints before trusting the badge.
A freeze authority is not an extension, and this table does not see it.
ISSUER_POWERS reads the Token-2022 extension list, but the freeze authority
lives on the base mint and applies to classic SPL mints too. On 2026-08-25,
37 of the 44 hard entries carry a live freeze authority and issuerPowers
records it on two of them, USDY and SYRUP-USDC. The seven with none are
SOL, USDE, CETES, GILTS, KTB, PST and EUSX. A freeze authority
does not stop a market from working, so it belongs in the disclosure column
rather than in vaultVerdict, but it belongs somewhere.
peard