Repository navigation
[Feature] Automatic protocol-yield routing via migrate_adapter keeper #469
Description
Summary
The vault's active adapter is static, changed only manually via set_adapter/migrate_adapter. Existing routing logic (selectBestVault in packages/stellar-sdk-helpers/src/routing.ts, recommendedVaultId in packages/api-core/src/vaults.ts) only steers new deposits toward the best available option at deposit time; it never moves capital already deposited. migrate_adapter (packages/contracts/vault/src/lib.rs, shipped in #464/#507) already exists and atomically moves the vault's entire position to a new adapter in one transaction, with a slippage-bounded value-preservation check, but nothing calls it automatically. Today an admin has to notice a rate change and trigger it by hand.
Motivation
Automatic routing to the best available yield, without requiring a human to notice a rate change and manually trigger a migration, is the core value proposition of a yield aggregator. Without it, Meridian's yield allocation is manual, capital doesn't actually move to wherever yield is best unless someone is watching and acting.
Proposed Solution
An off-chain keeper (same shape as the existing Blend accrue keeper in #466/PR #484: a scheduled cron endpoint with retry handling and structured failure reporting) that periodically compares live rates across the protocols this vault's adapters can target (Blend, DeFindex), and calls migrate_adapter when the improvement clears a configured threshold, avoiding needless churn from chasing tiny, fee-losing differences.
This does not need any new authorization mechanism. migrate_adapter already exists, is already admin-gated, and, critically, already never touches any individual depositor's mUSDC balance, it operates on the vault's aggregate position, denominated in shares that are priced against total vault value, not reissued per protocol. So triggering it automatically benefits every depositor simultaneously with a single admin/keeper-signed call, no per-user consent, delegation, or signature is needed, because no individual user's tokens are ever moved.
(An earlier version of this issue proposed a MeridianRouter contract with per-user delegated authorization, letting a keeper move an individual depositor's position between separate vault deployments on their behalf. That approach ran into a hard constraint: Stellar's token contracts require the token holder's own signature for any transfer or burn, with no allowance/delegation primitive, so a keeper could never actually act without the depositor present, defeating the point. It also assumed multiple vault deployments would exist per protocol, which was never the intended design. That work was reverted; MeridianRouter no longer exists in the codebase.)
Scope
| Field | Value |
|---|---|
| Area | API |
| Protocol affected | Both |
| Network | testnet |
| Breaking change? | No (additive: a new scheduled job, no contract changes needed) |
Alternatives Considered
Manual admin-triggered migrate_adapter (today's state): works, but depends on a human noticing a rate change and acting, which is exactly the dependency this issue exists to remove.
Acceptance Criteria
- Scheduled job compares live rates across the vault's supported protocols (Blend, DeFindex) on a regular interval
- Triggers
migrate_adapteronly when the improvement clears a configured minimum threshold, to avoid fee-losing churn from marginal differences - Uses a sane, explicit
max_slippage_bpson everymigrate_adaptercall, never10_000(unlimited) in automated operation - Retry handling and structured failure reporting on migration failure, matching the accrue keeper's existing pattern
- Documents the operational trust model: this keeper needs the vault admin's signing capability (or an equivalent trusted key), same as any other admin action, no new trust surface beyond what
migrate_adapteralready documents - Tests cover: triggering on a genuine rate improvement, not triggering below threshold, and failure/retry handling when
migrate_adapteritself reverts (e.g. slippage exceeded)
Activity
apatafamilycompound123-ops commentedon Aug 4, 2026
Hi, I'd like to work on this issue. I've gone through the description and acceptance criteria and I'm confident I can deliver it. I'll make sure every acceptance criterion is met and submit a clean, tested PR. Please assign it to me.
collinsezedike commentedon Aug 4, 2026
Go for it @apatafamilycompound123-ops
grantfox-oss commentedon Aug 4, 2026
🦊 GrantFox — @apatafamilycompound123-ops has been assigned to this issue!
Next steps:
Closes #469)Good luck! Track your progress on GrantFox.
collinsezedike commentedon Aug 9, 2026
Hey @apatafamilycompound123-ops, checking in, it's been a few days since you picked this up. Hope things are going well. Let me know if you're stuck on anything or need help, no pressure either way, just wanted to touch base.
collinsezedike commentedon Aug 12, 2026
Reclaiming this one, it's been quiet for a few days since the check-in with no response. Unassigning so it's open for anyone to pick up. @apatafamilycompound123-ops feel free to grab it again if you're back and still want it, no hard feelings either way.
collinsezedike commentedon Aug 12, 2026
Scoping note: this depends on #508 (router allowlist) landing first, since router.rebalance() currently accepts arbitrary vault addresses with no allowlist, a gap that's been harmless while the router is unused but becomes live risk once delegation wires it into a keeper flow.
After that, this ships in two PRs against this issue: a delegation primitive on the router (grant_delegation/revoke_delegation/delegated_rebalance, keeper-signed, scoped to each user's own allowed vaults) that does not close this issue on its own, since a delegation with nothing automatically triggering it isn't actually automated. The off-chain keeper that reads live rates and triggers delegated_rebalance() when the improvement clears a user's threshold is the piece that closes this issue.
[-][Feature] Fully automated cross-vault yield routing (delegated rebalance)[/-][+][Feature] Automatic protocol-yield routing via migrate_adapter keeper[/+]collinsezedike commentedon Aug 13, 2026
Rewrote this issue's title, summary, and proposed solution. The router-delegation approach in the previous version turned out to be unbuildable: Stellar's token contracts require the holder's own signature for any transfer or burn, with no allowance/delegation primitive, so a keeper could never actually move an individual depositor's position without them present. It also assumed multiple vault deployments per protocol, which isn't the intended design, there's one vault per asset.
MeridianRouter(and the allowlist work in #508/#509) has been reverted from the codebase.The actual fix is much smaller:
migrate_adapter(#464/#507) already moves the vault's entire position between protocols without touching any individual depositor's balance, since shares are priced against total vault value, not reissued per protocol. All this issue needs now is an off-chain keeper that watches rates and calls it automatically, no contract changes, no new authorization mechanism.