-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Luke/dpe 1861 jito api changes #67
Draft
lukecaan
wants to merge
3
commits into
master
Choose a base branch
from
luke/dpe-1861-jito-api-changes
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
import { BN, DepositRecord, LAMPORTS_PRECISION, User, ZERO, isVariant } from '@drift-labs/sdk'; | ||
import { | ||
fetchAndParsePricesCsv, | ||
getPriceRangeFromPeriod, | ||
calcYield, | ||
DATA_SOURCE, | ||
PERIOD, | ||
PriceRecord, | ||
} from '@glitchful-dev/sol-apy-sdk'; | ||
import { LAMPORTS_PER_SOL } from '@solana/web3.js'; | ||
|
||
const parsedPricesResultCache = new Map<string, PriceRecord[]>(); | ||
|
||
/** | ||
* Utility method to fetch cached prices if previously fetched | ||
* @param dataSource | ||
* @returns | ||
*/ | ||
const getPrices = async (dataSource: string) => { | ||
if (parsedPricesResultCache.has(dataSource)) { | ||
return parsedPricesResultCache.get(dataSource); | ||
} else { | ||
const prices = await fetchAndParsePricesCsv(dataSource); | ||
parsedPricesResultCache.set(dataSource, prices); | ||
|
||
return prices; | ||
} | ||
}; | ||
|
||
type LST = 'jitosol' | 'bsol' | 'msol'; | ||
|
||
const getDatasourceForLst = (lst: LST) => { | ||
let dataSource: string; | ||
|
||
switch (lst) { | ||
case 'bsol': | ||
dataSource = DATA_SOURCE.SOLBLAZE_CSV; | ||
break; | ||
case 'jitosol': | ||
dataSource = DATA_SOURCE.JITO_CSV; | ||
break; | ||
case 'msol': | ||
dataSource = DATA_SOURCE.MARINADE_CSV; | ||
break; | ||
default: { | ||
const exhaustiveCheck: never = lst; | ||
throw new Error(exhaustiveCheck); | ||
} | ||
} | ||
|
||
return dataSource; | ||
}; | ||
|
||
/** | ||
* Maps timestamps to milliseconds | ||
* @param timestamp | ||
*/ | ||
const toMsecs = (timestamp: number) => { | ||
if (timestamp.toString().length === 13) { | ||
return timestamp; | ||
} | ||
// Make the number 13 digits long by adding zeros if necessary | ||
const timestampStr = timestamp.toString(); | ||
const timestampStrPadded = timestampStr.padEnd(13, '0'); | ||
return parseInt(timestampStrPadded); | ||
}; | ||
|
||
/** | ||
* Maps timestamps to seconds | ||
* @param timestamp | ||
*/ | ||
const _toSecs = (timestamp: number) => { | ||
if (timestamp.toString().length === 10) { | ||
return timestamp; | ||
} | ||
// Make the number 10 digits long by removing zeros if necessary | ||
const timestampStr = timestamp.toString(); | ||
const timestampStrTrimmed = timestampStr.slice(0, 10); | ||
return parseInt(timestampStrTrimmed); | ||
}; | ||
|
||
/** | ||
* Fetches the yield for a given LST and period. Fetches results from a CSV stored in github in the @glitchful-dev/sol-apy-sdk repo. | ||
* | ||
* See: | ||
* https://github.com/glitchful-dev/sol-stake-pool-apy/blob/master/packages/sol-apy-sdk/index.ts | ||
* | ||
* @param lst | ||
* @param periodDays | ||
* @returns | ||
*/ | ||
export const getLstYield = async ( | ||
lst: LST, | ||
periodDays: 7 | 14 | 30 | 90 | 365 | ||
) => { | ||
const dataSource = getDatasourceForLst(lst); | ||
|
||
let period: PERIOD; | ||
|
||
switch (periodDays) { | ||
case 7: | ||
period = PERIOD.DAYS_7; | ||
break; | ||
case 14: | ||
period = PERIOD.DAYS_14; | ||
break; | ||
case 30: | ||
period = PERIOD.DAYS_30; | ||
break; | ||
case 90: | ||
period = PERIOD.DAYS_90; | ||
break; | ||
case 365: | ||
period = PERIOD.DAYS_365; | ||
break; | ||
default: { | ||
const exhaustiveCheck: never = periodDays; | ||
throw new Error(exhaustiveCheck); | ||
} | ||
} | ||
|
||
const prices = await getPrices(dataSource); | ||
const priceRange = getPriceRangeFromPeriod(prices, period); | ||
|
||
const result = calcYield(priceRange); | ||
|
||
return { | ||
apr: result?.apr * 100, | ||
apy: result?.apy * 100, | ||
}; | ||
}; | ||
|
||
/** | ||
* Fetches the current (latest) price for a given LST, measured in SOL. | ||
* @param lst | ||
* @returns | ||
*/ | ||
export const getLstSolPrice = async (lst: LST) => { | ||
const dataSource = getDatasourceForLst(lst); | ||
|
||
return (await getPrices(dataSource))?.[0]?.price; | ||
}; | ||
|
||
/** | ||
* Creates a "price map" for the lst based on target timestamps. Where there is no exact timestamp match for the target timestamps and the recorded prices, it uses the most recent price measurement for the target timestamp. | ||
* @param lst | ||
* @param timestamps | ||
* @returns | ||
*/ | ||
export const getLstPriceMap = async (lst: LST, timestamps: number[]) => { | ||
|
||
const targetTimestampSet = new Set<number>(timestamps); | ||
const descSortedPrices = (await getPrices(getDatasourceForLst(lst))).sort((a, b) => b.timestamp - a.timestamp); | ||
const priceMap = new Map<number, number>(); | ||
|
||
// Need to match and add prices to the price map | ||
targetTimestampSet.forEach((targetTimestamp) => { | ||
// The matching price is the first price that is less than or equal to the target timestamp when going through the price entries in descending order .. aka the most recent price measurement for the target timestamp | ||
const match = descSortedPrices.find((price) => price.timestamp <= targetTimestamp); | ||
if (match) { | ||
priceMap.set(targetTimestamp, match.price); | ||
} | ||
}); | ||
|
||
return priceMap; | ||
}; | ||
|
||
/** | ||
* Returns a timestamp in seconds, for the start of the day in UTC format .. strips the hours, minutes and seconds | ||
* @param timestamp | ||
*/ | ||
const getNormalisedTimestamp = (timestamp: number) => { | ||
const date = new Date(Math.round(toMsecs(timestamp))); | ||
date.setUTCHours(0, 0, 0, 0); | ||
return date.getTime() / 1000; | ||
}; | ||
|
||
export async function calculateSolEarned({ | ||
marketIndex, | ||
user, | ||
depositRecords, | ||
}: { | ||
marketIndex: number; | ||
user: User; | ||
depositRecords: DepositRecord[]; | ||
}): Promise<BN> { | ||
const normalisedTimestamps: number[] = [ | ||
Date.now(), | ||
...depositRecords | ||
.filter((r) => r.marketIndex === marketIndex) | ||
.map((r) => ((r.ts.toNumber()))), | ||
].map(getNormalisedTimestamp); | ||
|
||
const lst : LST = marketIndex === 2 ? 'msol' : marketIndex === 6 ? 'jitosol' : 'bsol'; | ||
|
||
const lstRatios = await getLstPriceMap(lst, normalisedTimestamps); | ||
|
||
let solEarned = ZERO; | ||
for (const record of depositRecords) { | ||
if (record.marketIndex === 1) { | ||
if (isVariant(record.direction, 'deposit')) { | ||
solEarned = solEarned.sub(record.amount); | ||
} else { | ||
solEarned = solEarned.add(record.amount); | ||
} | ||
} else if ( | ||
record.marketIndex === 2 || | ||
record.marketIndex === 6 || | ||
record.marketIndex === 8 | ||
) { | ||
const normalisedTimestamp = getNormalisedTimestamp(record.ts.toNumber()); | ||
const lstRatio = lstRatios.get(normalisedTimestamp); | ||
const lstRatioBN = new BN(lstRatio * LAMPORTS_PER_SOL); | ||
|
||
const solAmount = record.amount.mul(lstRatioBN).div(LAMPORTS_PRECISION); | ||
if (isVariant(record.direction, 'deposit')) { | ||
solEarned = solEarned.sub(solAmount); | ||
} else { | ||
solEarned = solEarned.add(solAmount); | ||
} | ||
} | ||
} | ||
|
||
const currentLstTokenAmount = await user.getTokenAmount(marketIndex); | ||
const currentLstRatio = await getLstSolPrice(lst); | ||
const currentLstRatioBN = new BN(currentLstRatio * LAMPORTS_PER_SOL); | ||
|
||
solEarned = solEarned.add( | ||
currentLstTokenAmount.mul(currentLstRatioBN).div(LAMPORTS_PRECISION) | ||
); | ||
|
||
const currentSOLTokenAmount = await user.getTokenAmount(1); | ||
solEarned = solEarned.add(currentSOLTokenAmount); | ||
|
||
return solEarned; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also not strictly specific to your PR, but I was also wondering if we should do the "sol earned" feature differently from a UX point of view.
I had a friend sort of get confused about what that even means, because there is no breakdown about where the sol earned is coming from or where it ends up. He thought he was getting sol that was claimable or added to his drift account or something lol.
I think we should work towards not showing a "sol earned" number necessarily, and instead showing a performance comparison of the user's current account value against the value of their initial deposit. That way it would factor out the "sol earned" that would have already been there if they had not been super staking at all.