diff --git a/packages/processors/src/allo/handlers/poolCreated.handler.ts b/packages/processors/src/allo/handlers/poolCreated.handler.ts index 392b587..de6a6a8 100644 --- a/packages/processors/src/allo/handlers/poolCreated.handler.ts +++ b/packages/processors/src/allo/handlers/poolCreated.handler.ts @@ -1,8 +1,9 @@ -import { Address, getAddress, parseUnits, zeroAddress } from "viem"; +import { getAddress, parseUnits, zeroAddress } from "viem"; import type { Changeset, NewRound, PendingRoundRole } from "@grants-stack-indexer/repository"; -import type { ChainId, ProtocolEvent } from "@grants-stack-indexer/shared"; +import type { ChainId, ProtocolEvent, Token } from "@grants-stack-indexer/shared"; import { isAlloNativeToken } from "@grants-stack-indexer/shared"; +import { getToken } from "@grants-stack-indexer/shared/dist/src/internal.js"; import type { IEventHandler, ProcessorDependencies, StrategyTimings } from "../../internal.js"; import { getRoundRoles } from "../../helpers/roles.js"; @@ -17,7 +18,7 @@ type Dependencies = Pick< >; // sometimes coingecko returns no prices for 1 hour range, 2 hours works better -const TIMESTAMP_DELTA_RANGE = 2 * 60 * 60 * 1000; +export const TIMESTAMP_DELTA_RANGE = 2 * 60 * 60 * 1000; /** /** @@ -62,13 +63,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> const strategy = extractStrategyFromId(strategyId); - // TODO: get token for the chain - const token = { - address: matchTokenAddress, - decimals: 18, //TODO: get decimals from token - symbol: "USDC", //TODO: get symbol from token - name: "USDC", //TODO: get name from token - }; + const token = getToken(this.chainId, matchTokenAddress); let strategyTimings: StrategyTimings = { applicationsStartTime: null, @@ -87,7 +82,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> if ( strategy.name === "allov2.DonationVotingMerkleDistributionDirectTransferStrategy" && parsedRoundMetadata.success && - token !== null + token ) { matchAmount = parseUnits( parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable.toString(), @@ -104,7 +99,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> let fundedAmountInUsd = "0"; - if (token !== null && fundedAmount > 0n) { + if (token && fundedAmount > 0n) { fundedAmountInUsd = await this.getTokenAmountInUsd( token, fundedAmount, @@ -206,14 +201,13 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> } private async getTokenAmountInUsd( - token: { address: Address; decimals: number }, + token: Token, amount: bigint, timestamp: number, ): Promise { const { pricingProvider } = this.dependencies; const tokenPrice = await pricingProvider.getTokenPrice( - this.chainId, - token.address, + token.priceSourceCode, timestamp, timestamp + TIMESTAMP_DELTA_RANGE, ); diff --git a/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts b/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts index 49b3d43..26acf4c 100644 --- a/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts +++ b/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts @@ -5,10 +5,13 @@ import type { EvmProvider } from "@grants-stack-indexer/chain-providers"; import type { IMetadataProvider } from "@grants-stack-indexer/metadata"; import type { IPricingProvider } from "@grants-stack-indexer/pricing"; import type { IRoundReadRepository, Round } from "@grants-stack-indexer/repository"; -import type { ChainId, DeepPartial, ProtocolEvent } from "@grants-stack-indexer/shared"; +import type { ChainId, DeepPartial, ProtocolEvent, TokenCode } from "@grants-stack-indexer/shared"; import { mergeDeep } from "@grants-stack-indexer/shared"; -import { PoolCreatedHandler } from "../../../src/allo/handlers/poolCreated.handler.js"; +import { + PoolCreatedHandler, + TIMESTAMP_DELTA_RANGE, +} from "../../../src/allo/handlers/poolCreated.handler.js"; // Function to create a mock event with optional overrides function createMockEvent( @@ -419,6 +422,79 @@ describe("PoolCreatedHandler", () => { ); }); - it.skip("handles a native token"); - it.skip("handles an unknown token"); + it("handles a native token", async () => { + const mockEvent = createMockEvent({ + params: { token: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" }, + }); + + vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue({ + round: { + name: "Test Round", + roundType: "private", + quadraticFundingConfig: { + matchingFundsAvailable: 1, + }, + }, + application: { + version: "1.0.0", + }, + }); + vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue({ + priceUsd: 2500, + timestampMs: 1708369911, + }); + vi.spyOn(mockEvmProvider, "multicall").mockResolvedValue([ + 1609459200n, + 1609459200n, + 1609459200n, + 1609459200n, + ]); + + vi.spyOn(mockRoundRepository, "getPendingRoundRoles").mockResolvedValue([]); + + const handler = new PoolCreatedHandler(mockEvent, 10 as ChainId, { + evmProvider: mockEvmProvider, + pricingProvider: mockPricingProvider, + metadataProvider: mockMetadataProvider, + roundRepository: mockRoundRepository, + }); + + await handler.handle(); + + expect(mockPricingProvider.getTokenPrice).toHaveBeenCalledWith( + "ETH" as TokenCode, + 1708369911, + 1708369911 + TIMESTAMP_DELTA_RANGE, + ); + }); + + it("handles an unknown token", async () => { + const fundedAmount = parseUnits("10", 18); + const mockEvent = createMockEvent({ + params: { + amount: fundedAmount, + token: "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE", + strategyId: "0xunknown", + }, + }); + + vi.spyOn(mockRoundRepository, "getPendingRoundRoles").mockResolvedValue([]); + + const handler = new PoolCreatedHandler(mockEvent, 10 as ChainId, { + evmProvider: mockEvmProvider, + pricingProvider: mockPricingProvider, + metadataProvider: mockMetadataProvider, + roundRepository: mockRoundRepository, + }); + + const result = await handler.handle(); + + const changeset = result[0] as { type: "InsertRound"; args: { round: Round } }; + expect(changeset.type).toBe("InsertRound"); + expect(changeset.args.round).toMatchObject({ + fundedAmount: fundedAmount, + fundedAmountInUsd: "0", //since it's an unknown token + }); + expect(mockPricingProvider.getTokenPrice).not.toHaveBeenCalled(); + }); }); diff --git a/packages/shared/src/tokens/tokens.ts b/packages/shared/src/tokens/tokens.ts index 25509d1..3167001 100644 --- a/packages/shared/src/tokens/tokens.ts +++ b/packages/shared/src/tokens/tokens.ts @@ -602,3 +602,7 @@ export const TOKENS: { }, }, } as const; + +export const getToken = (chainId: number, tokenAddress: Address): Token | undefined => { + return TOKENS[chainId]?.[tokenAddress]; +};