[BIP XXX] Permissions Update Request #9

PR with payloads

Note: This new permission request was created to accelerate the release of the previously reviewed permissions that remained in PUR#8 (ARB and Gnosis Chain). It covers the requests for Ethereum Mainnet, and no changes were made to the requested permissions beyond clarifications and incorporating feedback for transparency.

Abstract

This proposal requests BalancerDAO’s approval for updates and new actions to the permissions policy governing the kpk-managed treasuries on Ethereum Mainnet.

The proposed changes expand the existing permissions to include new strategies, protocols, swaps, and bridging routes, while removing obsolete or deprecated contracts and interactions. They also streamline kpk’s fee-collection process and revoke the delegation’s permissions from the Zodiac Roles Modifier, as outlined in BIP-890.

The additional permissions focus on strategies that aim to increase the treasury’s risk-adjusted yield without introducing material new risk vectors to the current allocation. These include allocations to Aave Umbrella, Ethena, Spark, Resolv, new markets on Compound as well as kpk-curated vaults on Morpho and curated markets on Gearbox, among others.

Motivation

Treasury management must evolve in line with market conditions and protocol developments, including migrations, new pool launches, and improved access to financial instruments. Protocols and pools previously assessed as too immature or risky for BalancerDAO may become viable over time as they demonstrate reliability, liquidity, and operational robustness.

The proposed additions will allow kpk to implement advanced investment strategies such as delta-neutral stablecoin exposure, curated vaults, and the upgraded version of the Aave safety module, while deprecating outdated or higher-risk contracts.

Changes to the Permissions Policy

This proposal outlines the following modifications to the permissions policy:

Ethereum Mainnet:

  • Aave Umbrella: deposit and withdraw USDC, USDT, WETH, GHO
  • Compound markets: deposit and withdraw ETH, USDT, USDS
  • Ethena: stake, unstake and withdrawal of USDE/sUSDE
  • Spark:
    • Savings: deposit and withdraw of USDC, USDT
    • Markets: deposit and withdraw of USDT, USDC
  • Morpho: deposit and withdraw - kpk EURC, kpk USDC, kpkETH
  • Gearbox: deposit and withdraw of wstETH, ETH
  • Balancer v2: unwinding permissions for USDC/WETH, BAL/WETH, 80BAL/20WETH
  • Gyroscope: revoke GYD and sGYD
  • Merkl rewards claim: GHO and stGHO
  • oETH: whitelist “OETH.rebaseOptIn()” function, related to fee accruing.
  • Swaps (Cow Swap):
    • COW, SYRUP <> USDC, ETH, WETH
    • aETHBAL, aBAL, anyBAL, ETH, WETH <> aETHBAL, aBAL, anyBAL, ETH, WETH
    • USR, stUSR, wstUSR, USDE, sUSDE, stUSD, USDA, DAI, USDT, USDC<> USR, stUSR, wstUSR, USDE, sUSDE, stUSD, USDA, DAI, USDT, USDC
    • EURC, AaveEURC, EURA, stEUR <> EURC, AaveEURC, EURA, stEUR
  • Bridge:
    • Omnibridge:
      • Update and addition of the following bridging routes from Mainnet to Gnosis Chain: USDC, USDT, WBTC, WETH, USDS
  • Roles:
    • Removal of stkAAVE’s delegation permissions
    • Allowance for transferring max. 100k USDC/quarter to kpk treasury for fee payment

Zodiac Roles Modifier Permissions Policy

Permissions page

Ethereum: link here

Permissions diff page

Ethereum: link here

Changes & Clarifications Following Forum Feedback (BIP-909 Thread)

Following feedback raised on the PUR#8 forum discussion (BIP-909), we are incorporating the following clarifications and adjustments in PUR#9:

Tx 0 — Fee Allowance Decimal Clarification

The quarterly allowance for fee payments reflects 100,000 USDC (6 decimals). The raw value has been corrected accordingly.


Tx 10 and 21 — sUSDS Deposit

Spark updated the UI to use a different deposit function for exchanging USDS into sUSDS:

  • Old: deposit(uint256 assets, address receiver)
  • New: deposit(uint256 assets, address receiver, uint16 referral)

That’s why the permission changed accordingly.


Revocations Clarification

Revocations included in this PUR (legacy bridge, sGYD, delegation calls, etc.) reflect cleanup of deprecated or no-longer-used infrastructure and are not tied to new risk concerns.

1 Like

TL;DR

not mentioned in the proposal but found in the payload:

  • pendle
  • silo
  • sablier
  • cctp
  • sky psm
  • makerdao psm
  • polygon staking revokes
  • lido withdrawal revokes
  • old xdai bridge revokes
  • dai and gno bridging

discrepancies:

  • you mention unwinding balancer 80bal20weth positions but also add permissions to join those pools
  • gyd is not revoked, only sgyd
  • not only stkaave delegation is revoked, also aave
  • swapping pairs are more complex than how you describe it
  • also bridge dai and gno, plus cctp arb1 bridge

furthermore some minor comments, mostly unnecessary transactions, but no security concerns (except for maybe usr swapping, which has been depegged)

TX 0: USDC Allowance

now correctly sets a usdc allowance of 100_000usdc to kpk every 90 days. used in tx 18

setAllowance(USDC_KPK-FEES, bal=100000000000, period=90d)

TX 1-13, 182-188: Revokes

as stated previously, i dont think there is a need to revoke permissions unless there is a security concern. however, i understand it was easier to keep these in the payload for now:

  • revoke delegate permissions for aave and stkaave
  • revoke withdrawal permissions for unsteth
  • revoke sgyd permissions to deposit and redeem
  • revoke susds permissions to deposit
  • revoke xdai foreign bridge permissions (replaced by newer one; see tx 177-181)
AAVE.delegate
AAVE.delegateByType
stkAAVE.delegate
stkAAVE.delegateByType
unstETH.requestWithdrawalsWithPermit
unstETH.requestWithdrawalsWstETHWithPermit
sGYD.[TARGET]
sGYD.deposit
sGYD.redeem
sUSDS.deposit
XDaiForeignBridge.[TARGET]
XDaiForeignBridge.relayTokens
XDaiForeignBridge.executeSignatures

revoke permissions to manage pol delegation

POL.[TARGET]
POL.approve
DelegateRegistry.[TARGET]
DelegateRegistry.setDelegation
DelegateRegistry.clearDelegation
dPOLa2.[TARGET]
dPOLa2.buyVoucherPOL

TX 14, 189: Metadata

n/a; metadata

_post_(removeAnnotations)
_post_(addAnnotations)

TX 15: WETH

approve weth for bridging, gearbox, morpho, compound, aave umbrella, balancer vault and cow relayer

WETH.approve(OR(SwapRouter02, ForeignOmnibridge, kpkWETH, cWETHv3, stkwaEthWETH.v1, Vault, kpk_ETH_PrimeV2, UNI-V3-POS, GPv2VaultRelayer, UmbrellaBatchHelper), *)

TX 16, 44-53, 158-159: Compound

scope the rewards claim to only the relevant reward markets, and not all of them (why i dont know, unscoped should be fine):

CometRewards.claim(OR(cUSDTv3, cUSDSv3, cWETHv3, cUSDCv3), AVATAR, *)

supply and withdraw from usds/usdt/weth compound markets:

cUSDSv3.[TARGET]
cUSDSv3.supply(USDS, *)
cUSDSv3.withdraw(USDS, *)
cUSDTv3.[TARGET]
cUSDTv3.supply(USDT, *)
cUSDTv3.withdraw(USDT, *)
cWETHv3.[TARGET]
cWETHv3.supply(WETH, *)
cWETHv3.withdraw(WETH, *)
cWETHv3.allow(MainnetBulker, *)

eth<>weth helper for cwethv3

MainnetBulker.[TARGET]
MainnetBulker.invoke(EVERY(...), EVERY(...)) [Send]

TX 17-21: Stablecoin Approvals

  • approve usdc and usdt for spark, spark savings, kpk vault, (batch) aave umbrella, gnosis bridge
  • approve usds for compound and gnosis bridge
  • allow transfer of usdc to kpk within allowance defined in tx 0
  • stake usds into susds
USDC.approve(OR(spUSDC, kpk_USDC_PrimeV2, PSMVariant1Actions, SwapRouter02, stkwaEthUSDC.v1, PoolInstance, ForeignOmnibridge, UsdsPsmWrapper, Vault, Pool, cUSDCv3, GPv2VaultRelayer, UmbrellaBatchHelper, PSMVariant1Actions), *)
USDC.transfer(kpk-fees-safe, <=USDC_KPK-FEES)
USDT.approve(OR(cUSDTv3, SwapRouter02, ForeignOmnibridge, stkwaEthUSDT.v1, Vault, Pool, GPv2VaultRelayer, UmbrellaBatchHelper, spUSDT), *)
USDS.approve(OR(cUSDSv3, PoolInstance, BridgeRouter, UsdsPsmWrapper, sUSDS, GPv2VaultRelayer, MigrationActions), *)
sUSDS.deposit(*, AVATAR, *)

TX 22: OETH

opt in to oeth rebasing; doesnt pose a problem for safe contracts

OETH.rebaseOptIn()

TX 23: Merkl

allow claiming of rewards via merkl. not sure why this needs to be scoped; what is the security concern here?

Distributor.claim(OR(..., ..., ..., ..., ...), *, *, *)

TX 24-26: Bridging

allow bridging of wbtc, gno, weth, usdt and usdc

ForeignOmnibridge.relayTokens(OR(WBTC, GNO, WETH, USDT), AVATAR, *)
ForeignOmnibridge.relayTokensAndCall(USDC, 0x0392a2f5, *, ...)
ForeignAMB.safeExecuteSignaturesWithAutoGasLimit(OR(AND(0x), AND(0x), AND(0x), AND(0x), AND(0x)), *)

TX 27-31

new approvals here are:

  • gho: (batch) aave umbrella
  • dai: redundant!
  • sdai: redundant!
  • wbtc: bridge
  • wsteth: kpk gearbox vault
GHO.approve(OR(stkGHO, stkGHO.v1, PoolInstance, Vault, GPv2VaultRelayer, UmbrellaBatchHelper), *)
DAI.approve(OR(DsrManager, SwapRouter02, sDAI, PoolInstance, BridgeRouter, GPv2VaultRelayer, MigrationActions), *)
sDAI.approve(OR(PSMVariant1Actions, PoolInstance, Vault, GPv2VaultRelayer), *)
WBTC.approve(OR(SwapRouter02, PoolInstance, ForeignOmnibridge, UNI-V3-POS, GPv2VaultRelayer), *)
wstETH.approve(OR(SwapRouter02, PoolInstance, unstETH, kpkwstETH, Vault, GPv2VaultRelayer), *)

TX 32-33, 54-71, 75-92: CoW Swap

allow signing of the following swapping pairs:

  • gho staking/unstaking via swap: stkgho<>gho
  • dust liquidations: fjo, mta, gtc > usdc, weth
  • oeth exit: oeth > wsteth, reth, steth, weth, eth
  • bal token rebalance: aethbal, abal, weth, anybal > aethbal, abal, weth, anybal, eth
  • main rebalance: wbtc, swise, stkaave, syrup, dai, wsteth, aave, usdc, reth, steth, comp, weth, usdt, cow > wbtc, dai, wsteth, usdc, reth, steth, weth, usdt
  • stablecoin rebalance: usda, stusd, steur, wstusr, eura, eurc, gho, usde, usr, dai, sdai, susde, usdc, susds, aetheurc, usdt, usds, gyd <> usda, stusd, steur, wstusr, eura, eurc, gho, usde, usr, dai, sdai, susde, usdc, susds, aetheurc, usdt, gyd

looks like you missed usds as a buy token, or was this by design? i guess there is always the psm as a fallback

CowswapOrderSigner.signOrder([...], *, [...])

approve cow relayer:

GYD.approve(OR(Vault, GPv2VaultRelayer), *)
COW.[TARGET]
COW.approve(GPv2VaultRelayer, *)
SYRUP.[TARGET]
SYRUP.approve(GPv2VaultRelayer, *)
aEthEURC.[TARGET]
aEthEURC.approve(GPv2VaultRelayer, *)
EURA.[TARGET]
EURA.approve(GPv2VaultRelayer, *)
EURC.[TARGET]
EURC.approve(OR(kpk_EURC_YieldV2, GPv2VaultRelayer), *)
stEUR.[TARGET]
stEUR.approve(GPv2VaultRelayer, *)
stUSD.[TARGET]
stUSD.approve(GPv2VaultRelayer, *)
USR.[TARGET]
USR.approve(GPv2VaultRelayer, *)
sUSDe.[TARGET]
sUSDe.approve(GPv2VaultRelayer, *)
USDA.[TARGET]
USDA.approve(GPv2VaultRelayer, *)
USDe.[TARGET]
USDe.approve(OR(PendleRouterV4, sUSDe, GPv2VaultRelayer), *)
wstUSR.[TARGET]
wstUSR.approve(GPv2VaultRelayer, *)
FJO.[TARGET]
FJO.approve(GPv2VaultRelayer, *)
GTC.[TARGET]
GTC.approve(GPv2VaultRelayer, *)
MTA.[TARGET]
MTA.approve(GPv2VaultRelayer, *)
aBAL.[TARGET]
aBAL.approve(GPv2VaultRelayer, *)
aEthBAL.[TARGET]
aEthBAL.approve(GPv2VaultRelayer, *)
anyBAL.[TARGET]
anyBAL.approve(GPv2VaultRelayer, *)

TX 34-35, 38-41: Balancer

join (why?) and exit 80bal20weth pools:

Vault.joinPool(OR(0x5c6ee304399d..., 0xc5c91aea7551...), AVATAR, AVATAR, *) [Send]
Vault.exitPool(OR(0x5c6ee304399d..., 0xc5c91aea7551...), AVATAR, AVATAR, *)
B-80BAL-20WETH.[TARGET]
B-80BAL-20WETH.approve(Vault, *)
BAL.[TARGET]
BAL.approve(Vault, *)

TX 36-37: AAVE V3

aave v3 reward claiming:

RewardsController.[TARGET]
RewardsController.claimRewards(*, *, AVATAR, *)

TX 42-43: CCTP

receive cctp messages from arb1 to eth; usdc burning from treasury on arb1 that result in minting of usdc on eth treasury

MessageTransmitterV2.[TARGET]
MessageTransmitterV2.receiveMessage(AND(...), *)

TX 72-74: Ethena

stake and unstake (s)usde

sUSDe.deposit(*, AVATAR)
sUSDe.cooldownShares(*)
sUSDe.unstake(AVATAR)

TX 93-104: Morpho

deposit, withdraw and redeem from eth, eurc and usdc kpk vaults:

kpk_ETH_PrimeV2.[TARGET]
kpk_ETH_PrimeV2.deposit(*, AVATAR)
kpk_ETH_PrimeV2.withdraw(*, AVATAR, AVATAR)
kpk_ETH_PrimeV2.redeem(*, AVATAR, AVATAR)
kpk_EURC_YieldV2.[TARGET]
kpk_EURC_YieldV2.deposit(*, AVATAR)
kpk_EURC_YieldV2.withdraw(*, AVATAR, AVATAR)
kpk_EURC_YieldV2.redeem(*, AVATAR, AVATAR)
kpk_USDC_PrimeV2.[TARGET]
kpk_USDC_PrimeV2.deposit(*, AVATAR)
kpk_USDC_PrimeV2.withdraw(*, AVATAR, AVATAR)
kpk_USDC_PrimeV2.redeem(*, AVATAR, AVATAR)

TX 160-167: Gearbox

kpkWETH.[TARGET]
kpkWETH.deposit(*, AVATAR)
kpkWETH.depositWithReferral(*, AVATAR, *)
kpkWETH.redeem(*, AVATAR, AVATAR)
kpkwstETH.[TARGET]
kpkwstETH.deposit(*, AVATAR)
kpkwstETH.depositWithReferral(*, AVATAR, *)
kpkwstETH.redeem(*, AVATAR, AVATAR)

TX 105-111, 116-119, 127-132: Spark

supply and withdraw from usdc and usdt markets:

Pool.[TARGET]
Pool.supply(OR(USDC, USDT), *, AVATAR, *)
Pool.withdraw(OR(USDC, USDT), *, AVATAR)
Pool.setUserUseReserveAsCollateral(OR(USDC, USDT), *)

spark reward claiming (fork of aave v3, so same pattern as tx 36-37)

RewardsController.[TARGET]
RewardsController.claimRewards(*, *, AVATAR, *)
RewardsController.claimAllRewards(*, AVATAR)

deposit/withdraw/redeem spark savings usdc:

spUSDC.[TARGET]
spUSDC.deposit(*, AVATAR, *)
spUSDC.withdraw(*, AVATAR, AVATAR)
spUSDC.redeem(*, AVATAR, AVATAR)

not clear why susdc needs approval? i think you confuse it for susds or usdc here

sUSDC.[TARGET]
sUSDC.approve(PSMVariant1Actions, *)

deposit/withdraw/redeem spark savings usdt:

spUSDT.[TARGET]
spUSDT.deposit(*, AVATAR, *)
spUSDT.withdraw(*, AVATAR, AVATAR)
spUSDT.redeem(*, AVATAR, AVATAR)

TX 112-115: MakerDAO PSM

makerdao psm routing (usdc <> dai <> sdai)

PSMVariant1Actions.[TARGET]
PSMVariant1Actions.swapAndDeposit(AVATAR, *, *)
PSMVariant1Actions.withdrawAndSwap(AVATAR, *, *)
PSMVariant1Actions.redeemAndSwap(AVATAR, *, *)

TX 120-126: Sky PSM

sky psm wrapper (usdc<>usds):

UsdsPsmWrapper.[TARGET]
UsdsPsmWrapper.sellGem(AVATAR, *)
UsdsPsmWrapper.buyGem(AVATAR, *)

sky psm routing (usdc <> usds <> susds)

PSMVariant1Actions.[TARGET]
PSMVariant1Actions.swapAndDeposit(AVATAR, *, *)
PSMVariant1Actions.withdrawAndSwap(AVATAR, *, *)
PSMVariant1Actions.redeemAndSwap(AVATAR, *, *)

TX 133-153: AAVE Umbrella

stake and unstake usdc, usdt, weth and gho into aave umbrella

UmbrellaBatchHelper.[TARGET]
UmbrellaBatchHelper.deposit(OR(MATCH(0x), MATCH(0x), MATCH(0x), MATCH(0x)))
UmbrellaBatchHelper.redeem(OR(MATCH(0x), MATCH(0x), MATCH(0x), MATCH(0x)))
stkGHO.v1.[TARGET]
stkGHO.v1.deposit(*, AVATAR)
stkGHO.v1.cooldown()
stkGHO.v1.redeem(*, AVATAR, AVATAR)
stkwaEthUSDC.v1.[TARGET]
stkwaEthUSDC.v1.deposit(*, AVATAR)
stkwaEthUSDC.v1.cooldown()
stkwaEthUSDC.v1.redeem(*, AVATAR, AVATAR)
stkwaEthUSDT.v1.[TARGET]
stkwaEthUSDT.v1.deposit(*, AVATAR)
stkwaEthUSDT.v1.cooldown()
stkwaEthUSDT.v1.redeem(*, AVATAR, AVATAR)
stkwaEthWETH.v1.[TARGET]
stkwaEthWETH.v1.deposit(*, AVATAR)
stkwaEthWETH.v1.cooldown()
stkwaEthWETH.v1.redeem(*, AVATAR, AVATAR)
RewardsController.[TARGET]
RewardsController.claimSelectedRewards(*, *, AVATAR)

TX 154-157: CoW AMM

unwind weth/usdc and bal/eth positions on cow amm

BCoW-50WETH-50USDC.[TARGET]
BCoW-50WETH-50USDC.exitPool(*, *)
BCoW-50BAL-50ETH.[TARGET]
BCoW-50BAL-50ETH.exitPool(*, *)

TX 168-169: Sablier

withdraw sablier streams:

SAB-V2-LOCKUP-DYN.[TARGET]
SAB-V2-LOCKUP-DYN.withdraw(*, AVATAR, *)

TX 170-171: Silo

withdraw from bal silo

SiloRouterV2.[TARGET]
SiloRouterV2.execute((MATCH))

TX 172-176: Pendle

this particular PENDLE-LPT appears expired already (1770249600; 2026-02-05)

PendleRouterV4.[TARGET]
PendleRouterV4.swapExactTokenForPt(AVATAR, PENDLE-LPT, *, *, (USDe, *, USDe, 0x0, MATCH), (0x0, *, ..., ...))
PendleRouterV4.swapExactPtForToken(AVATAR, PENDLE-LPT, *, (USDe, *, USDe, 0x0, MATCH), (0x0, *, ..., ...))
PENDLE-LPT.[TARGET]
PENDLE-LPT.approve(PendleRouterV4, *)

TX 177-181: Gnosis Chain Bridge

bridge weth, dai or usds to gnosis and receive inbound bridge messages from gnosis

BridgeRouter.[TARGET]
BridgeRouter.relayTokens(OR(DAI, USDS), AVATAR, *)
BridgeRouter.executeSignatures([recipient=AVATAR, bridge=XDaiForeignBridge], *)
WETHOmnibridgeRouter.[TARGET]
WETHOmnibridgeRouter.wrapAndRelayTokens(AVATAR) [Send]