Improve precision of Jupiter Perps borrowFeesDue calculation #27

Open
opened 2026-07-07 01:39:07 +02:00 by minimons · 1 comment
Owner

Problem

This is a continuation from #22.

borrowFeesDue is currently calculated from:

  • custody.fundingRateState.cumulativeInterestRate
  • position.cumulativeInterestSnapshot
  • position.sizeUsd

The formula currently used is approximately:

positionInterest =
        cumulativeInterestRate.subtract(cumulativeInterestSnapshot);

borrowFeesDue =
        ceil(positionInterest * sizeUsd / RATE_POWER);

This is close to the Jupiter UI, but not always exact.

Example observed:

App: 0.008150
UI:  0.00839

The values are close enough for now, but the app is slightly behind the UI.

Likely cause

The current implementation uses the last stored on-chain cumulativeInterestRate.

Jupiter UI appears to calculate an effective/live cumulative interest value by adding the interest accrued since fundingRateState.lastUpdate.

So the more precise formula is likely:

effectiveCumulativeInterest =
    custody.fundingRateState.cumulativeInterestRate
    + current accrued funding/borrow interest since lastUpdate

Then:

positionInterest =
    effectiveCumulativeInterest - position.cumulativeInterestSnapshot

And finally:

borrowFeesDue =
    ceil(positionInterest * position.sizeUsd / RATE_POWER)

Details to investigate

The custody account contains additional fields needed for the more precise calculation, likely under fundingRateState, including:

cumulativeInterestRate
lastUpdate
hourlyFundingDbps

The more precise logic should check whether current Unix time is greater than fundingRateState.lastUpdate.

If so, calculate the extra accumulated interest since lastUpdate, probably something like:

extraInterest =
    currentFundingRate * (currentUnixTimestamp - lastUpdate) / 3600

Then add this extra interest to the stored cumulativeInterestRate.

Important context

The current account choice appears to be correct:

position.collateralCustody
→ used for current cumulative interest / borrow fee calculation

position.custody
→ used for traded token mint lookup

Do not “fix” this by switching back to position.custody for borrow-fee calculation. That caused SHORT positions to become wildly wrong.

The current implementation is close for both LONG and SHORT positions, so the remaining problem is probably precision/freshness, not the wrong account.

Suggested implementation steps

  1. Decode more fields from the Jupiter Perps custody account:

    • fundingRateState.cumulativeInterestRate
    • fundingRateState.lastUpdate
    • fundingRateState.hourlyFundingDbps
  2. Add suitable ValueTags for the new decoded values, for example:

    • ΩJupiterLastUpdateTimestampΩ
    • ΩJupiterHourlyFundingDbpsΩ
  3. Extend JupiterCustodyAccountInfo with the new fields.

  4. Implement an effective cumulative interest calculation:

effectiveCumulativeInterestRate =
    cumulativeInterestRate + accruedInterestSinceLastUpdate
  1. Use effectiveCumulativeInterestRate instead of raw cumulativeInterestRate when calculating borrowFeesDue.

  2. Compare app output against Jupiter UI for at least:

    • SOL LONG
    • SOL SHORT
    • BTC LONG
    • BTC SHORT
    • ETH LONG
    • ETH SHORT

Acceptance criteria

  • borrowFeesDue still works for both LONG and SHORT positions.
  • The implementation still uses collateralCustodyAccountAddress for borrow-fee interest.
  • App values match Jupiter UI more closely than the current raw cumulativeInterestRate calculation.
  • The code remains compile-safe through ValueTags, so custody/traded-token lookup and collateral/borrow-fee lookup cannot easily be mixed up.
## Problem This is a continuation from #22. `borrowFeesDue` is currently calculated from: * `custody.fundingRateState.cumulativeInterestRate` * `position.cumulativeInterestSnapshot` * `position.sizeUsd` The formula currently used is approximately: ```java positionInterest = cumulativeInterestRate.subtract(cumulativeInterestSnapshot); borrowFeesDue = ceil(positionInterest * sizeUsd / RATE_POWER); ``` This is close to the Jupiter UI, but not always exact. Example observed: ```text App: 0.008150 UI: 0.00839 ``` The values are close enough for now, but the app is slightly behind the UI. ## Likely cause The current implementation uses the last stored on-chain `cumulativeInterestRate`. Jupiter UI appears to calculate an effective/live cumulative interest value by adding the interest accrued since `fundingRateState.lastUpdate`. So the more precise formula is likely: ```text effectiveCumulativeInterest = custody.fundingRateState.cumulativeInterestRate + current accrued funding/borrow interest since lastUpdate ``` Then: ```text positionInterest = effectiveCumulativeInterest - position.cumulativeInterestSnapshot ``` And finally: ```text borrowFeesDue = ceil(positionInterest * position.sizeUsd / RATE_POWER) ``` ## Details to investigate The custody account contains additional fields needed for the more precise calculation, likely under `fundingRateState`, including: ```text cumulativeInterestRate lastUpdate hourlyFundingDbps ``` The more precise logic should check whether current Unix time is greater than `fundingRateState.lastUpdate`. If so, calculate the extra accumulated interest since `lastUpdate`, probably something like: ```text extraInterest = currentFundingRate * (currentUnixTimestamp - lastUpdate) / 3600 ``` Then add this extra interest to the stored `cumulativeInterestRate`. ## Important context The current account choice appears to be correct: ```text position.collateralCustody → used for current cumulative interest / borrow fee calculation position.custody → used for traded token mint lookup ``` Do not “fix” this by switching back to `position.custody` for borrow-fee calculation. That caused SHORT positions to become wildly wrong. The current implementation is close for both LONG and SHORT positions, so the remaining problem is probably precision/freshness, not the wrong account. ## Suggested implementation steps 1. Decode more fields from the Jupiter Perps custody account: * `fundingRateState.cumulativeInterestRate` * `fundingRateState.lastUpdate` * `fundingRateState.hourlyFundingDbps` 2. Add suitable ValueTags for the new decoded values, for example: * `ΩJupiterLastUpdateTimestampΩ` * `ΩJupiterHourlyFundingDbpsΩ` 3. Extend `JupiterCustodyAccountInfo` with the new fields. 4. Implement an effective cumulative interest calculation: ```text effectiveCumulativeInterestRate = cumulativeInterestRate + accruedInterestSinceLastUpdate ``` 5. Use `effectiveCumulativeInterestRate` instead of raw `cumulativeInterestRate` when calculating `borrowFeesDue`. 6. Compare app output against Jupiter UI for at least: * SOL LONG * SOL SHORT * BTC LONG * BTC SHORT * ETH LONG * ETH SHORT ## Acceptance criteria * `borrowFeesDue` still works for both LONG and SHORT positions. * The implementation still uses `collateralCustodyAccountAddress` for borrow-fee interest. * App values match Jupiter UI more closely than the current raw `cumulativeInterestRate` calculation. * The code remains compile-safe through ValueTags, so custody/traded-token lookup and collateral/borrow-fee lookup cannot easily be mixed up.
minimons added the enhancement label 2026-07-07 10:22:29 +02:00
minimons added this to the AssetAZ project 2026-07-07 10:22:33 +02:00
minimons self-assigned this 2026-07-07 10:22:38 +02:00
Author
Owner

liquidationPrice now matches Jupiter UI closely after using collateralCustody for borrow fee calculation. Remaining mismatch appears to be caused by borrowFeesDue precision, likely because we do not yet include accrued interest since custody fundingRateState.lastUpdate.

liquidationPrice now matches Jupiter UI closely after using collateralCustody for borrow fee calculation. Remaining mismatch appears to be caused by borrowFeesDue precision, likely because we do not yet include accrued interest since custody fundingRateState.lastUpdate.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r35157/com_r35157_nenjim-hubd-impl_ref#27