Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions docs/apr-oracle-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,94 @@ The external API response structure for vaults remains unchanged to ensure backw
}
}
```

---

## V2 Vault Estimated APR Integration (Kong)

As of the implementation of [issue #560](https://github.com/yearn/ydaemon/issues/560), V2 vaults with Curve, Convex, Velodrome, and Aerodrome strategies now prioritize pre-calculated estimated APRs from Kong over local RPC-based calculations.

### Overview

Previously, `yDaemon` computed forward-looking APRs for V2 Curve-like vaults (Curve, Convex, Velodrome, Aerodrome) by making multiple on-chain RPC calls to calculate:
- Gauge boost values
- Pool APY from Curve subgraph
- Reward rates and token prices
- Various fee components

This approach was:
- **RPC-intensive**: Required multiple multicalls per vault
- **Computation-heavy**: Performed complex calculations locally
- **Slower**: Dependent on RPC response times

### New Data Source

The `performance.estimated` field from Kong's GraphQL API is now the **primary** source for V2 forward APR data:
- **Estimated APR**: Pre-computed annualized rate
- **Estimated APY**: Pre-computed annualized yield (compounded)
- **Type**: Strategy type identifier (e.g., `"crv"`, `"v2:velo"`)
- **Components**: Detailed breakdown including:
- `boost`: Gauge boost multiplier
- `poolAPY`: Pool swap fee APY
- `boostedAPR`: Boosted reward APR
- `baseAPR`: Base reward APR
- `rewardsAPR`: Additional rewards APR
- `rewardsAPY`: Additional rewards APY (compounded)
- `cvxAPR`: Convex-specific APR
- `keepCRV`: Percentage of CRV kept vs. swapped
- `keepVelo`: Percentage of VELO kept vs. swapped

### Supported Chains

The estimated APR integration applies to V2 vaults on:
- **Ethereum (chainId: 1)**: Curve, Convex
- **Optimism (chainId: 10)**: Velodrome
- **Fantom (chainId: 250)**: Curve
- **Arbitrum (chainId: 42161)**: Curve
- **Base (chainId: 8453)**: Aerodrome

### Implementation Details

#### Priority System
1. **Primary**: Check for Kong `performance.estimated` data
2. **Fallback**: If Kong data unavailable, perform local RPC-based calculation
3. **Graceful degradation**: System continues to function even if Kong is temporarily unavailable

#### Internal Logic
- **Kong Indexer** (`internal/indexer/indexer.kong.go`): Fetches and stores estimated APR data from Kong GraphQL API
- **Storage Accessor** (`internal/storage/elem.vaults.go`): Provides `GetKongEstimatedAPY()` function to retrieve cached Kong data
- **APR Calculation** (`processes/apr/main.go`): Checks Kong data first before falling back to local computation
- **Helper Function** (`processes/apr/forward.curve.helpers.go`): `convertKongEstimatedAprToForwardAPY()` converts Kong format to internal `TForwardAPY` structure

#### Benefits
- **Reduced RPC Calls**: Eliminates multiple multicalls for gauge data, prices, and boost calculations
- **Faster Response**: Pre-computed data from Kong cache vs. real-time RPC queries
- **Consistency**: Kong serves as single source of truth for APR calculations
- **Maintained Components**: All APR component breakdowns preserved in API responses

### API Response

The external API response structure remains **fully backward compatible**. The `apr.forwardAPR` object structure is unchanged:

```json
"forwardAPR": {
"type": "crv", // From Kong estimated.type
"netAPY": 0.156, // From Kong estimated.apy
"composite": {
"boost": 2.5, // From Kong estimated.components.boost
"poolAPY": 0.023, // From Kong estimated.components.poolAPY
"boostedAPR": 0.187, // From Kong estimated.components.boostedAPR
"baseAPR": 0.075, // From Kong estimated.components.baseAPR
"cvxAPR": 0.045, // From Kong estimated.components.cvxAPR (Convex only)
"rewardsAPY": 0.034, // From Kong estimated.components.rewardsAPY
"keepCRV": 0.1 // From Kong estimated.components.keepCRV
}
}
```

### Migration Notes

- **No Breaking Changes**: API consumers experience no differences in response structure
- **Automatic Fallback**: If Kong estimated data is unavailable, yDaemon seamlessly falls back to local computation
- **Strategy Support**: Works for all Curve, Convex, Velodrome, and Aerodrome V2 strategies

34 changes: 32 additions & 2 deletions internal/indexer/indexer.kong.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ func IndexNewVaults(chainID uint64) map[common.Address]models.TVaultsFromRegistr
// Convert KongDebt to TKongDebt
var debts []models.TKongDebt
for _, debt := range data.Debts {
totalDebt := debt.TotalDebt
if totalDebt == nil {
zero := "0"
totalDebt = &zero
}
debts = append(debts, models.TKongDebt{
Strategy: debt.Strategy,
PerformanceFee: debt.PerformanceFee,
Expand All @@ -52,7 +57,7 @@ func IndexNewVaults(chainID uint64) map[common.Address]models.TVaultsFromRegistr
MinDebtPerHarvest: debt.MinDebtPerHarvest,
MaxDebtPerHarvest: debt.MaxDebtPerHarvest,
LastReport: debt.LastReport,
TotalDebt: debt.TotalDebt,
TotalDebt: totalDebt,
TotalDebtUsd: debt.TotalDebtUsd,
TotalGain: debt.TotalGain,
TotalGainUsd: debt.TotalGainUsd,
Expand All @@ -67,13 +72,38 @@ func IndexNewVaults(chainID uint64) map[common.Address]models.TVaultsFromRegistr
})
}

// Extract performance data from Kong response (oracle APR/APY)
// Extract performance data from Kong response (oracle APR/APY and estimated APR)
performance := models.TKongPerformance{}
if data.Vault.Performance != nil {
performance.Oracle = models.TKongOracle{
Apr: data.Vault.Performance.Oracle.Apr,
Apy: data.Vault.Performance.Oracle.Apy,
}

// Extract estimated APR if available
if data.Vault.Performance.Estimated != nil {
components := &models.TKongEstimatedAprComponents{}
if data.Vault.Performance.Estimated.Components != nil {
components = &models.TKongEstimatedAprComponents{
Boost: data.Vault.Performance.Estimated.Components.Boost,
PoolAPY: data.Vault.Performance.Estimated.Components.PoolAPY,
BoostedAPR: data.Vault.Performance.Estimated.Components.BoostedAPR,
BaseAPR: data.Vault.Performance.Estimated.Components.BaseAPR,
RewardsAPR: data.Vault.Performance.Estimated.Components.RewardsAPR,
RewardsAPY: data.Vault.Performance.Estimated.Components.RewardsAPY,
CvxAPR: data.Vault.Performance.Estimated.Components.CvxAPR,
KeepCRV: data.Vault.Performance.Estimated.Components.KeepCRV,
KeepVelo: data.Vault.Performance.Estimated.Components.KeepVelo,
}
}

performance.Estimated = &models.TKongEstimatedApr{
Apr: data.Vault.Performance.Estimated.Apr,
Apy: data.Vault.Performance.Estimated.Apy,
Type: data.Vault.Performance.Estimated.Type,
Components: components,
}
}
}

kongSchema := models.TKongVaultSchema{
Expand Down
54 changes: 53 additions & 1 deletion internal/kong/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,28 @@ type KongOracle struct {
Apy *float64 `json:"apy"` // Float or null
}

type KongEstimatedAprComponents struct {
Boost *float64 `json:"boost"`
PoolAPY *float64 `json:"poolAPY"`
BoostedAPR *float64 `json:"boostedAPR"`
BaseAPR *float64 `json:"baseAPR"`
RewardsAPR *float64 `json:"rewardsAPR"`
RewardsAPY *float64 `json:"rewardsAPY"`
CvxAPR *float64 `json:"cvxAPR"`
KeepCRV *float64 `json:"keepCRV"`
KeepVelo *float64 `json:"keepVelo"`
}

type KongEstimatedApr struct {
Apr *float64 `json:"apr"`
Apy *float64 `json:"apy"`
Type string `json:"type"`
Components *KongEstimatedAprComponents `json:"components"`
}

type KongPerformance struct {
Oracle KongOracle `json:"oracle"`
Oracle KongOracle `json:"oracle"`
Estimated *KongEstimatedApr `json:"estimated"` // Estimated APR from Kong
}

type KongVault struct {
Expand Down Expand Up @@ -215,6 +235,22 @@ func (c *Client) FetchVaultsForChain(ctx context.Context, chainID uint64) ([]Kon
apr
apy
}
estimated {
apr
apy
type
components {
boost
poolAPY
boostedAPR
baseAPR
rewardsAPR
rewardsAPY
cvxAPR
keepCRV
keepVelo
}
}
}
apy {
pricePerShare
Expand Down Expand Up @@ -302,6 +338,22 @@ func (c *Client) FetchAllVaults(ctx context.Context) (map[uint64][]KongVault, er
apr
apy
}
estimated {
apr
apy
type
components {
boost
poolAPY
boostedAPR
baseAPR
rewardsAPR
rewardsAPY
cvxAPR
keepCRV
keepVelo
}
}
}
apy {
pricePerShare
Expand Down
22 changes: 21 additions & 1 deletion internal/models/vaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,28 @@ type TKongOracle struct {
Apy *float64 `json:"apy"` // Float or null
}

type TKongEstimatedAprComponents struct {
Boost *float64 `json:"boost"`
PoolAPY *float64 `json:"poolAPY"`
BoostedAPR *float64 `json:"boostedAPR"`
BaseAPR *float64 `json:"baseAPR"`
RewardsAPR *float64 `json:"rewardsAPR"`
RewardsAPY *float64 `json:"rewardsAPY"`
CvxAPR *float64 `json:"cvxAPR"`
KeepCRV *float64 `json:"keepCRV"`
KeepVelo *float64 `json:"keepVelo"`
}

type TKongEstimatedApr struct {
Apr *float64 `json:"apr"`
Apy *float64 `json:"apy"`
Type string `json:"type"`
Components *TKongEstimatedAprComponents `json:"components"`
}

type TKongPerformance struct {
Oracle TKongOracle `json:"oracle"`
Oracle TKongOracle `json:"oracle"`
Estimated *TKongEstimatedApr `json:"estimated"` // Estimated APR from Kong
}

type TKongVaultSchema struct {
Expand Down
15 changes: 15 additions & 0 deletions internal/storage/elem.vaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,4 +472,19 @@ func GetKongOracleAPY(chainID uint64, vaultAddress common.Address) (*float64, *f
return nil, nil, false
}
return data.Performance.Oracle.Apr, data.Performance.Oracle.Apy, true
}

/**************************************************************************************************
** GetKongEstimatedAPY retrieves estimated APR data from Kong for a vault
** Returns the estimated APR struct and a boolean indicating if data was found
**************************************************************************************************/
func GetKongEstimatedAPY(chainID uint64, vaultAddress common.Address) (*models.TKongEstimatedApr, bool) {
data, ok := GetKongVaultData(chainID, vaultAddress)
if !ok {
return nil, false
}
if data.Performance.Estimated == nil {
return nil, false
}
return data.Performance.Estimated, true
}
56 changes: 56 additions & 0 deletions processes/apr/forward.curve.helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,59 @@ func computeCurveLikeForwardAPY(
},
}
}

/**************************************************************************************************
** convertKongEstimatedAprToForwardAPY converts Kong estimated APR data to TForwardAPY format
** Returns (forwardAPY, hasData) where hasData indicates if valid Kong data was found
**************************************************************************************************/
func convertKongEstimatedAprToForwardAPY(chainID uint64, vaultAddress common.Address) (TForwardAPY, bool) {
estimatedApr, ok := storage.GetKongEstimatedAPY(chainID, vaultAddress)
if !ok || estimatedApr == nil || estimatedApr.Apy == nil {
return TForwardAPY{}, false
}

// Convert to float64 values, defaulting to 0 if nil
var boost, poolAPY, boostedAPR, baseAPR, rewardsAPY, cvxAPR, keepCRV, keepVelo float64

if estimatedApr.Components != nil {
if estimatedApr.Components.Boost != nil {
boost = *estimatedApr.Components.Boost
}
if estimatedApr.Components.PoolAPY != nil {
poolAPY = *estimatedApr.Components.PoolAPY
}
if estimatedApr.Components.BoostedAPR != nil {
boostedAPR = *estimatedApr.Components.BoostedAPR
}
if estimatedApr.Components.BaseAPR != nil {
baseAPR = *estimatedApr.Components.BaseAPR
}
if estimatedApr.Components.RewardsAPY != nil {
rewardsAPY = *estimatedApr.Components.RewardsAPY
}
if estimatedApr.Components.CvxAPR != nil {
cvxAPR = *estimatedApr.Components.CvxAPR
}
if estimatedApr.Components.KeepCRV != nil {
keepCRV = *estimatedApr.Components.KeepCRV
}
if estimatedApr.Components.KeepVelo != nil {
keepVelo = *estimatedApr.Components.KeepVelo
}
}

return TForwardAPY{
Type: estimatedApr.Type,
NetAPY: bigNumber.NewFloat(*estimatedApr.Apy),
Composite: TCompositeData{
Boost: bigNumber.NewFloat(boost),
PoolAPY: bigNumber.NewFloat(poolAPY),
BoostedAPR: bigNumber.NewFloat(boostedAPR),
BaseAPR: bigNumber.NewFloat(baseAPR),
CvxAPR: bigNumber.NewFloat(cvxAPR),
RewardsAPY: bigNumber.NewFloat(rewardsAPY),
KeepCRV: bigNumber.NewFloat(keepCRV),
KeepVelo: bigNumber.NewFloat(keepVelo),
},
}, true
}
Loading