-
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 3 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,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 "./tokenMath.js"; | ||
|
||
// sometimes coingecko returns no prices for 1 hour range, 2 hours works better | ||
const TIMESTAMP_DELTA_RANGE = 2 * 60 * 60 * 1000; | ||
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. 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 commentThe 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, 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. 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
/** | ||
* 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,22 @@ 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 | ||
*/ | ||
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)); | ||
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. might need some protection against division by zero (and possibly invalid/NaN values, not sure all the responses coingecko can return) 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 was relying on the exception that bignumber.js throws for ZeroDivision, i'll clarify in natssec that it throws an exception in that case) |
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { z } from "zod"; | ||
|
||
export const ApplicationMetadataSchema = z | ||
.object({ | ||
application: z.object({ | ||
round: z.string(), | ||
recipient: z.string(), | ||
}), | ||
}) | ||
.transform((data) => ({ type: "application" as const, ...data })); | ||
|
||
export type ApplicationMetadata = z.infer<typeof ApplicationMetadataSchema>; |
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 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.
how to make autoimport work magically with this? maybe it's easier to have one single file called
index.js
with all the code ?)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.
i know that is annoying but, it save us some future headaches 🤣