Skip to content

[Feature] Automatic protocol-yield routing via migrate_adapter keeper #469

Closed

Description

@collinsezedike

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_adapter only when the improvement clears a configured minimum threshold, to avoid fee-losing churn from marginal differences
  • Uses a sane, explicit max_slippage_bps on every migrate_adapter call, never 10_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_adapter already documents
  • Tests cover: triggering on a genuine rate improvement, not triggering below threshold, and failure/retry handling when migrate_adapter itself reverts (e.g. slippage exceeded)

Activity

  1. added
    enhancementNew feature or request
    hardComplex implementation spanning multiple packages or involving Soroban contracts
    contractsInvolves writing or testing Rust/Soroban contracts in packages/contracts
    sorobanInvolves Soroban smart contract invocations or Soroban RPC calls
    securitySecurity hardening, vulnerability fixes, or audit-related work
    on Aug 3, 2026
  2. apatafamilycompound123-ops commented on Aug 4, 2026

    @apatafamilycompound123-ops

    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.

  3. collinsezedike commented on Aug 4, 2026

    @collinsezedike
    CollaboratorAuthor
  4. grantfox-oss commented on Aug 4, 2026

    @grantfox-oss

    🦊 GrantFox — @apatafamilycompound123-ops has been assigned to this issue!

    Next steps:

    1. Open a Pull Request referencing this issue (e.g., Closes #469)
    2. Your PR will be reviewed by the drydocs maintainers

    Good luck! Track your progress on GrantFox.

  5. collinsezedike commented on Aug 9, 2026

    @collinsezedike
    CollaboratorAuthor

    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.

  6. collinsezedike commented on Aug 12, 2026

    @collinsezedike
    CollaboratorAuthor

    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.

  7. collinsezedike commented on Aug 12, 2026

    @collinsezedike
    CollaboratorAuthor

    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.

  8. changed the title [-][Feature] Fully automated cross-vault yield routing (delegated rebalance)[/-] [+][Feature] Automatic protocol-yield routing via migrate_adapter keeper[/+] on Aug 13, 2026
  9. collinsezedike commented on Aug 13, 2026

    @collinsezedike
    CollaboratorAuthor

    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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaigncontractsInvolves writing or testing Rust/Soroban contracts in packages/contractsenhancementNew feature or requesthardComplex implementation spanning multiple packages or involving Soroban contractssecuritySecurity hardening, vulnerability fixes, or audit-related worksorobanInvolves Soroban smart contract invocations or Soroban RPC calls

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions