Kerne Logo

Security disclosure

skUSD admin status

Update (2026-07-08): skUSD was redeployed on 2026-07-03 to 0x96F5102C15b839757f811A98CEc3725Ac21DfA14 to reset a distorted share price. On 2026-07-08 the 2-of-3 Safe was granted DEFAULT_ADMIN (0x92c6cc05) and the bot strategist EOA was granted STRATEGIST (0x68738752), and the bot executed the vault's first on-chain yield distribution, 0.10 kUSD at genesis scale (0x5deef3d6). The deployer Trezor deliberately retained both roles through the external audit, so an admin-fixable finding could be acted on without waiting for a second signer. Hexens published its final report on July 31, 2026, and on August 2, 2026 the Trezor renounced STRATEGIST_ROLE (0xfc9117cf) and then DEFAULT_ADMIN_ROLE (0x46a13f64), irreversibly. The live vault has exactly one admin today, the 2-of-3 Safe, and exactly one strategist, the bot EOA. The Safe can still re-grant either role with two signatures and there is no timelock, so read that as one admin today, not one admin forever. Current status is at /security/audits. Everything else on this page documents the prior vault's completed rotation (2026-05-18) and remains accurate for that contract, the retired skUSD v1 at 0xdEd74F7E06efc76455C07418b8b74Cc2bc009DB4, which is no longer the deposit path.

skUSD is a live ERC-4626 wrapper for kUSD that you can deposit into today from app.kerne.fi. The prior vault's admin and strategist roles were rotated on May 18, 2026 from a single Trezor to the 2-of-3 Kerne Safe (admin) and the bot strategist EOA (yield-distribution authority). The Trezor that previously held both roles has renounced both, irreversibly. This page is the canonical record of the rotation, with every transaction hash linked to Basescan.

Last updated: August 2, 2026.

Live vault role topology (post-2026-08-02 renounce)

The live skUSD contract at 0x96F5102C15b839757f811A98CEc3725Ac21DfA14 holds the following AccessControl state on Base mainnet. It defines exactly two roles and, since August 2, 2026, each has exactly one holder.

One caveat, stated plainly: the Safe holds DEFAULT_ADMIN_ROLE and there is no timelock and no guard, so with two signatures it can grant STRATEGIST_ROLE to a new address, or grant DEFAULT_ADMIN_ROLE to another party, at any time. What the renounce removes is the ability of any single key to do that. It is accurate to say there is one admin today; it would not be accurate to say the role set can never change.

Live vault rotation log (July 8, 2026 to August 2, 2026)

Five transactions on Base mainnet, in execution order. The two renounces were held back deliberately: while an external audit is in progress, a single-key admin backstop lets an admin-fixable finding be remediated immediately rather than waiting on a second signer. Hexens published its final report on July 31, 2026, so that reason expired and the roles were given up three days later. The two renounces were sent as separate transactions rather than batched, so a failure on the second could not compound the first. Click any hash to verify on Basescan.

  1. Trezor: grantRole(DEFAULT_ADMIN_ROLE, Safe)
  2. Trezor: grantRole(STRATEGIST_ROLE, bot)
  3. Bot: skUSD.distributeYield(0.10 kUSD), smoke test proving the bot strategist path works end-to-end before any renounce
  4. Trezor: renounceRole(STRATEGIST_ROLE, Trezor)
  5. Trezor: renounceRole(DEFAULT_ADMIN_ROLE, Trezor). IRREVERSIBLE.

Prior vault role topology (post-2026-05-18 rotation)

The prior skUSD contract at 0xdEd74F7E06efc76455C07418b8b74Cc2bc009DB4 holds the following AccessControl state on Base mainnet, unchanged since the 2026-05-18 rotation:

Rotation execution log (2026-05-18)

Seven transactions on Base mainnet, in execution order. Click any hash to verify on Basescan.

  1. Trezor → bot: transfer 0.10 kUSD (pre-funds the smoke test)
  2. Trezor: grantRole(DEFAULT_ADMIN_ROLE, Safe)
  3. Trezor: grantRole(STRATEGIST_ROLE, bot)
  4. Bot: kUSD.approve(skUSD, 0.10 kUSD)
  5. Bot: skUSD.distributeYield(0.10 kUSD), smoke test that proves the new strategist topology works end-to-end
  6. Trezor: renounceRole(STRATEGIST_ROLE, Trezor)
  7. Trezor: renounceRole(DEFAULT_ADMIN_ROLE, Trezor). IRREVERSIBLE.

The order matters: grants land before any renounce so the contract is never admin-less. The smoke distributeYield call lands before the Trezor renounces, so the new strategist topology is proven working before the irreversible step. Pre-state and post-state assertions are embedded in the rotation scripts: scripts/skusd_rotate_phase1_grants_trezor.py, scripts/skusd_smoke_distribute_yield_bot.py, scripts/skusd_rotate_phase3_renounce_trezor.py.

What this rotation closes

The pre-rotation posture (parked-solo-admin) gave a single Trezor the ability to distributeYield() with attacker-controlled donations to manipulate share price, or to grant new roles to additional addresses. Both capabilities are now distributed: yield distribution requires the bot key, role changes require 2-of-3 Safe quorum, and the original Trezor has no authority over either path.

The skUSD source still ships the two structural defenses that were present before rotation: assets tracked via an internal _trackedAssets ledger (so a direct ERC-20 donation cannot inflate share price without an authenticated distributeYield() call) and a _decimalsOffset() = 6 that pushes the first-depositor share-rounding attack into economically prohibitive territory. Full source is Sourcify-verified (full match) and also on Blockscout.

The exit guarantee (unchanged by rotation)

skUSD is a wrapper. The underlying asset is kUSD at 0x5C2EfdF0D8D286959b42308966bc2B97f5680AA3. kUSD is administered by the 2-of-3 Kerne Safe. It is mintable 1:1 from USDC via the live mint PSM at 0xaBDE1138aa1Ce88d1dF06422C0c3b05D70569803, which was redeployed on 2026-07-10 and is the only PSM holding kUSD MINTER_ROLE today. kUSD is redeemable 1:1 for USDC via the redeem reserve at 0xFf3025ec18e301855aB0f36Ec6ECa115a29A5Fbc and via the retired mint PSM at 0x07eBb486e11BD217e6085eb5ab663e4517595993, which had its MINTER_ROLE revoked on 2026-07-10 but keeps its USDC reserve so the kUSD minted through it stays backed and redeemable. Anyone holding kUSD can redeem it for USDC at those reserves at any time, regardless of skUSD state.

skUSD redemption (the redeem() path on the vault) is permissionless and instant. There is no withdrawal cooldown on skUSD. Each share holder can burn their shares for their pro-rata kUSD entitlement at any time, then redeem that kUSD for USDC at the PSM.

On-chain proofs you can verify yourself

Every claim on this page is verifiable with public RPC reads. The first block reads the LIVE skUSD vault at 0x96F5102C15b839757f811A98CEc3725Ac21DfA14, which is the current custody state. The second block reads the RETIRED skUSD v1 at 0xdEd74F7E06efc76455C07418b8b74Cc2bc009DB4 and only confirms the historical 2026-05-18 rotation record above.

# ---- LIVE skUSD (redeployed 2026-07-03). Current role state. ----

# Safe holds DEFAULT_ADMIN_ROLE on the live vault (should return true)
cast call 0x96F5102C15b839757f811A98CEc3725Ac21DfA14 \
  "hasRole(bytes32,address)(bool)" \
  0x0000000000000000000000000000000000000000000000000000000000000000 \
  0x52d3E450bA6c299B1B07298F1E87DD74732D4877 \
  --rpc-url https://base-rpc.publicnode.com

# Bot holds STRATEGIST_ROLE on the live vault (should return true)
cast call 0x96F5102C15b839757f811A98CEc3725Ac21DfA14 \
  "hasRole(bytes32,address)(bool)" \
  0x17a8e30262c1f919c33056d877a3c22b95c2f5e4dac44683c1c2323cd79fbdb0 \
  0x09a2780ac8Be6D5d2d1F85A8D92b09D40C9CA37e \
  --rpc-url https://base-rpc.publicnode.com

# Deployer Trezor holds NEITHER role on the live vault (both return false,
# renounced 2026-08-02). skUSD is plain OpenZeppelin AccessControl, NOT
# AccessControlEnumerable, so getRoleMemberCount is absent and REVERTS: hasRole
# only answers for an address you already name. To prove the holder set is
# exactly one per role, replay every role event from the creation block with
# cast logs --from-block 48175381 (4 grants, 2 revokes, zero RoleAdminChanged).
cast call 0x96F5102C15b839757f811A98CEc3725Ac21DfA14 \
  "hasRole(bytes32,address)(bool)" \
  0x0000000000000000000000000000000000000000000000000000000000000000 \
  0x14f04cE02f35B29Af564A98544dD7e2393993946 \
  --rpc-url https://base-rpc.publicnode.com

# Live vault assets
cast call 0x96F5102C15b839757f811A98CEc3725Ac21DfA14 "totalAssets()(uint256)" \
  --rpc-url https://base-rpc.publicnode.com


# ---- RETIRED skUSD v1. Rotation record only, not the deposit path. ----

# Safe holds DEFAULT_ADMIN_ROLE on the retired vault (should return true)
cast call 0xdEd74F7E06efc76455C07418b8b74Cc2bc009DB4 \
  "hasRole(bytes32,address)(bool)" \
  0x0000000000000000000000000000000000000000000000000000000000000000 \
  0x52d3E450bA6c299B1B07298F1E87DD74732D4877 \
  --rpc-url https://base-rpc.publicnode.com

# Bot holds STRATEGIST_ROLE on the retired vault (should return true)
cast call 0xdEd74F7E06efc76455C07418b8b74Cc2bc009DB4 \
  "hasRole(bytes32,address)(bool)" \
  0x17a8e30262c1f919c33056d877a3c22b95c2f5e4dac44683c1c2323cd79fbdb0 \
  0x09a2780ac8Be6D5d2d1F85A8D92b09D40C9CA37e \
  --rpc-url https://base-rpc.publicnode.com

# Trezor no longer holds DEFAULT_ADMIN_ROLE on the retired vault
# (should return false; both renunciations landed 2026-05-18)
cast call 0xdEd74F7E06efc76455C07418b8b74Cc2bc009DB4 \
  "hasRole(bytes32,address)(bool)" \
  0x0000000000000000000000000000000000000000000000000000000000000000 \
  0x14f04cE02f35B29Af564A98544dD7e2393993946 \
  --rpc-url https://base-rpc.publicnode.com

# The 2026-05-18 smoke distributeYield landed (totalAssets >= 0.10 kUSD)
cast call 0xdEd74F7E06efc76455C07418b8b74Cc2bc009DB4 "totalAssets()(uint256)" \
  --rpc-url https://base-rpc.publicnode.com

The kUSD MINTER_ROLE holders are readable the same way. Today the role is held by the live KerneVault and the live mint PSM at 0xaBDE1138aa1Ce88d1dF06422C0c3b05D70569803. The retired mint PSM at 0x07eBb486e11BD217e6085eb5ab663e4517595993 returns false for that role since 2026-07-10.

# Live mint PSM holds kUSD MINTER_ROLE (should return true)
cast call 0x5C2EfdF0D8D286959b42308966bc2B97f5680AA3 \
  "hasRole(bytes32,address)(bool)" \
  $(cast keccak "MINTER_ROLE") \
  0xaBDE1138aa1Ce88d1dF06422C0c3b05D70569803 \
  --rpc-url https://base-rpc.publicnode.com

# Retired mint PSM no longer holds kUSD MINTER_ROLE (should return false)
cast call 0x5C2EfdF0D8D286959b42308966bc2B97f5680AA3 \
  "hasRole(bytes32,address)(bool)" \
  $(cast keccak "MINTER_ROLE") \
  0x07eBb486e11BD217e6085eb5ab663e4517595993 \
  --rpc-url https://base-rpc.publicnode.com

Deployment context (for the timeline)

skUSD was deployed on Base mainnet on 2026-05-11 (tx 0x03270583, block 45,876,491) by Scofield's Trezor, which received both DEFAULT_ADMIN_ROLE and STRATEGIST_ROLE at construction time. The MintWidget on app.kerne.fi exposed a live skUSD deposit path on 2026-05-14, carrying an inline disclosure that pointed here for the full context. Rotation landed on 2026-05-18; the inline disclosure dropped the same day. skUSD is now a contract whose authority is split across a 2-of-3 hardware-wallet multisig and an operational bot key, neither of which can drain depositors and both of which are subject to the structural defenses cited above.

Audit references

skUSD was covered by the 2026-05-08 adversarial audit. The math hardening commit c082246f switched skUSD's totalAssets() from balanceOf(this) to the internal _trackedAssets ledger and added sweepDonations() so donated assets land outside the share-pricing surface. Two source-only findings remain open on skUSD: the _decimalsOffset = 6 is still attackable at approximately $1M cost (partly mitigated by the ledger), and distributeYield() has no upper bound and no event emission. Both are tracked at /security/findings-tracker.