Skip to content

Redeem & Bridge OETHb fees to FeeSplitter - #3035

Open
shahthepro wants to merge 3 commits into
masterfrom
shah/oethb-fee-bridging
Open

shahthepro wants to merge 3 commits into
masterfrom
shah/oethb-fee-bridging

Conversation

@shahthepro

@shahthepro shahthepro commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Base OETHb fees still mint to the 1-of-3 fee Safe 0xBB077E71… (the Base vault's trusteeAddress, 20% fee). Today we swap and bridge them manually. This automates it: the fees are redeemed 1:1 for WETH and bridged to the mainnet FeeSplitter, where they follow the OUSD/OETH fees to xOGN.

  • BaseFeeBridgeModule is a Safe module on the fee Safe, operated by Talos:
    • requestWithdrawal(amount) opens a vault async withdrawal (1:1 burn).
    • claimAndBridge() claims it and sends only the WETH the claim paid out over CCIP to the FeeSplitter (0xbbE5…0464).
    • clearPendingRequest() (Safe only) is the recovery path if an owner claims the request directly on the vault.
  • Guardrails:
    • The vault, the CCIP router and the receiver are fixed at construction. WETH is read from vault.asset().
    • At most one request is open at a time. The request id is checked against the vault after the call instead of being trusted.
    • Redeeming is 1:1, so there's no price exposure and no slippage setting.
  • Why redeem and not swap: the Curve AMO pool costs about 9 bps. The redeem is free, and the cost is waiting out withdrawalClaimDelay (600s) plus queue liquidity.
  • The bridgeBaseFees action is checked daily and sends a tx only when there's work:
    • If a request is pending, it claims and bridges once the request is claimable. Claimability is checked by simulation. While waiting it logs the vault's reason (e.g. Queue pending liquidity).
    • It fails loudly if the vault claim would succeed but the bridge leg would not (e.g. the Safe can't pay the CCIP fee).
    • With nothing pending, it opens a new request at most every --min-inted only when bridgeFee()≤--max-fee-bps` (default 100) of the amount.The CCIP fee is flat at about 0.019 ETH per message, so that threshold is about 2 OETHb today.
    • Every write is simulated first, and --dryrun is supported. The sched (45 10 * * *).
  • Mainnet side: no FeeSplitter, harvester or setXOGNRewardRate change. The OGN CoW harvester already sells WETH. The OGN it buys arrives through CoW settlement, so the rate
    setter counts it as buyback income, not as a donation.
  • Deploy base/007 needs no governance. No Vault or OToken change. The fee Safe already holds the fees, so a module on it was enough. Re-pointing a role or adding a
    peripheral contract wasn't needed.

Required ops, in this order

  1. Mainnet Strategist: FeeSplitter.addAsset(WETH, 0.125e18). Without it, bridged WETH is not distributed and only a Governor rescue gets it out.
  2. Base fee Safe: enableModule(<module>), and keep some ETH there for CC
  3. Enable bridge_base_fees in Talos.

Talos impact
007 deploys only the new BaseFeeBridgeModule, recorded as BaseFeeBrid using it is the new bridgeBaseFees, which reads the address from thedescriptor make deploy-basewrites and uses an inline ABI. No pinned addresses orabi/*.jsonchange, and no existing action is affected.talos-actions-inventory.mdis regenerated, which also adds theousdSusdeV4Rebalance` entry missing sinc

Testing

  • Unit: 34 tests. They cover the one-request rule, tracking the vault's request id, claims before the delay or without liquidity, only claimed WETH being bridged, the fixed
    receiver, fee payment, recovery after a direct claim, and access control. ithdrawalVault` that mirrors VaultCore's queue structs and liquidity logic.
  • Fork (Base): 3 tests against the live vault and the live CCIP router. They show the Base→Ethereum route accepts a WETH-only message addressed to the FeeSplitter.
  • Anvil end to end with the action:
    1. Opened a request.
    2. Waited for the delay.
    3. Waited on Queue pending liquidity.
    4. Failed loudly when the Safe was short of ETH.
    5. Claimed and bridged.
    6. Stopped at the weekly gate.
    • No transaction was sent on any waiting run.
  • Mainnet fork: addAsset(WETH) then distribute() moved the WETH to the
  • Slither is clean on the module. pnpm test:tasks, typecheck and lint pass.

@shahthepro shahthepro changed the title Swap & Bridge OETHb fees to FeeSplitter Redeem & Bridge OETHb fees to FeeSplitter Oct 20, 2026
// slither-disable-next-line reentrancy-events
function requestWithdrawal(uint256 oethbAmount) external onlyOperator {
require(!hasPendingRequest, "Request pending");
require(oethbAmount > 0, "Invalid amount");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we enforce a minimum withdrawal of 1 ether (1 OETHb) so each batch is well above the CCIP fee? This would prevent a compromised operator from repeatedly bridging dust and draining the Safe’s ETH through fees.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, addressed in: c7d4c7f

vault.withdrawalRequests(requestId),
vault.withdrawalClaimDelay(),
]);
const claimableAt = Number(request.timestamp) + delay.toNumber();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we check request.claimed here and throw an error instructing the fee Safe to call clearPendingRequest()? Otherwise, a request claimed directly on the vault is reported as “waiting” indefinitely, and automation silently stops opening new requests.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c7d4c7f

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants