-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat: dvmd direct transfer allocated handler #29
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { DonationChangeset, IDonationRepository } from "@grants-stack-indexer/repository"; | ||
|
||
import { ChangesetHandler } from "../types/index.js"; | ||
|
||
/** | ||
* Collection of handlers for application-related operations. | ||
* Each handler corresponds to a specific Application changeset type. | ||
*/ | ||
export type DonationHandlers = { | ||
[K in DonationChangeset["type"]]: ChangesetHandler<K>; | ||
}; | ||
|
||
/** | ||
* Creates handlers for managing application-related operations. | ||
* | ||
* @param repository - The application repository instance used for database operations | ||
* @returns An object containing all application-related handlers | ||
*/ | ||
export const createDonationHandlers = (repository: IDonationRepository): DonationHandlers => ({ | ||
InsertDonation: (async (changeset): Promise<void> => { | ||
await repository.insertDonation(changeset.args.donation); | ||
}) satisfies ChangesetHandler<"InsertDonation">, | ||
|
||
InsertManyDonations: (async (changeset): Promise<void> => { | ||
await repository.insertManyDonations(changeset.args.donations); | ||
}) satisfies ChangesetHandler<"InsertManyDonations">, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from "./application.handlers.js"; | ||
export * from "./project.handlers.js"; | ||
export * from "./round.handlers.js"; | ||
export * from "./donation.handlers.js"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ChainId } from "@grants-stack-indexer/shared"; | ||
|
||
export class ApplicationNotFound extends Error { | ||
constructor(chainId: ChainId, roundId: string, recipientId: string) { | ||
super( | ||
`Application not found on chain ${chainId} for round ${roundId} and recipient ${recipientId}`, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class MetadataParsingFailed extends Error { | ||
constructor(additionalInfo?: string) { | ||
super(`Failed to parse application metadata: ${additionalInfo}`); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { ChainId } from "@grants-stack-indexer/shared"; | ||
|
||
export class UnknownToken extends Error { | ||
constructor(tokenAddress: string, chainId?: ChainId) { | ||
super(`Unknown token: ${tokenAddress} ${chainId ? `on chain ${chainId}` : ""}`); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from "./roles.js"; | ||
export * from "./utils.js"; | ||
export * from "./tokenMath.js"; | ||
export * from "./pricing.js"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { IPricingProvider } from "@grants-stack-indexer/pricing"; | ||
import { Token } from "@grants-stack-indexer/shared"; | ||
|
||
import { TokenPriceNotFoundError } from "../internal.js"; | ||
import { calculateAmountInToken, calculateAmountInUsd } from "./index.js"; | ||
|
||
// sometimes coingecko returns no prices for 1 hour range, 2 hours works better | ||
const TIMESTAMP_DELTA_RANGE = 2 * 60 * 60 * 1000; | ||
|
||
/** | ||
* Get the amount in USD for a given amount in the token | ||
* @param pricingProvider - The pricing provider to use | ||
* @param token - The token to get the amount in | ||
* @param amount - The amount in the token | ||
* @param timestamp - The timestamp to get the price at | ||
* @returns The amount in USD | ||
* @throws TokenPriceNotFoundError if the price is not found | ||
*/ | ||
export const getTokenAmountInUsd = async ( | ||
pricingProvider: IPricingProvider, | ||
token: Token, | ||
amount: bigint, | ||
timestamp: number, | ||
): Promise<{ amountInUsd: string; timestamp: number }> => { | ||
const tokenPrice = await pricingProvider.getTokenPrice( | ||
token.priceSourceCode, | ||
timestamp, | ||
timestamp + TIMESTAMP_DELTA_RANGE, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in addition to prev comment, instead timestamp + TIMESTAMP_DELTA_RANGE as param , just delta |
||
); | ||
|
||
if (!tokenPrice) { | ||
throw new TokenPriceNotFoundError(token.address, timestamp); | ||
} | ||
|
||
return { | ||
amountInUsd: calculateAmountInUsd(amount, tokenPrice.priceUsd, token.decimals), | ||
timestamp: tokenPrice.timestampMs, | ||
}; | ||
}; | ||
|
||
/** | ||
* Get the amount in the token for a given amount in USD | ||
* @param pricingProvider - The pricing provider to use | ||
* @param token - The token to get the amount in | ||
* @param amountInUSD - The amount in USD | ||
* @param timestamp - The timestamp to get the price at | ||
* @returns The amount in the token | ||
* @throws TokenPriceNotFoundError if the price is not found | ||
*/ | ||
export const getUsdInTokenAmount = async ( | ||
pricingProvider: IPricingProvider, | ||
token: Token, | ||
amountInUSD: string, | ||
timestamp: number, | ||
): Promise<{ amount: bigint; price: number; timestamp: Date }> => { | ||
const closestPrice = await pricingProvider.getTokenPrice( | ||
token.priceSourceCode, | ||
timestamp, | ||
timestamp + TIMESTAMP_DELTA_RANGE, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
); | ||
|
||
if (!closestPrice) { | ||
throw new TokenPriceNotFoundError(token.address, timestamp); | ||
} | ||
|
||
return { | ||
amount: calculateAmountInToken(amountInUSD, closestPrice.priceUsd, token.decimals), | ||
timestamp: new Date(closestPrice.timestampMs), | ||
price: 1 / closestPrice.priceUsd, // price is the token price in USD, we return the inverse | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,3 +32,28 @@ export const calculateAmountInUsd = ( | |
|
||
return amountInUsd.toString(); | ||
}; | ||
|
||
/** | ||
* Calculates the amount in token | ||
* @param amountInUSD - The amount in USD | ||
* @param tokenPriceInUsd - The price of the token in USD | ||
* @param tokenDecimals - The number of decimals the token has | ||
* @returns The amount in token | ||
* @throws Error if tokenPriceInUsd is 0 (division by zero) | ||
*/ | ||
export const calculateAmountInToken = ( | ||
amountInUSD: string, | ||
tokenPriceInUsd: string | number, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this always be string ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i left it open because pricing provider returns number because coingecko returns a number (anyways, it doesn't affect the purpose of the function) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, got it , now i remember |
||
tokenDecimals: number, | ||
): bigint => { | ||
const amountInUsdBN = new BigNumber(amountInUSD); | ||
const tokenPriceInUsdBN = new BigNumber(tokenPriceInUsd); | ||
const scaleFactor = new BigNumber(10).pow(tokenDecimals); | ||
|
||
return BigInt( | ||
amountInUsdBN | ||
.multipliedBy(scaleFactor) | ||
.dividedBy(tokenPriceInUsdBN) | ||
.toFixed(0, BigNumber.ROUND_FLOOR), | ||
); | ||
}; |
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.
do you think this might be better as an env variable to avoid a code change if their api behavior changes?
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.
there isn't a specific api behaviour here actually, this comment is smth Gitcoin team found out experimenting,
i think for now is not needed an env variable but now im thinking if maybe this can be the default value on Coingecko provider instead of API caller passing
timestamp.now() + DELTA_RANGE
everytime. i leave it for reviewing 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.
agree with you nigiri, lets have this value as default ( within the CoingeckoProvider) and make the param optional , wdyt ?
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.
will add a task in linear to refactor in another PR
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.
https://linear.app/defi-wonderland/issue/GIT-144/refactor-pricingprovider-coingecko-to-make-optional-the-to-timestamp