Skip to content
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(indexer): add coingecko price collection from tokens against usd #138

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

daywiss
Copy link
Contributor

@daywiss daywiss commented Dec 18, 2024

motivation

we want to start collecting price data in the indexer

changes

This dynamically looks up prices based on fill events, passing them to a worker to then fetch and/or cache the price at the deposit time

Copy link

linear bot commented Dec 18, 2024

Comment on lines 1 to 14
import { MigrationInterface, QueryRunner } from "typeorm";

export class HistoricPrice1733941169940 implements MigrationInterface {
name = 'HistoricPrice1733941169940'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "historic_market_price" ("id" SERIAL NOT NULL, "baseCurrency" character varying NOT NULL, "quoteCurrency" character varying NOT NULL DEFAULT 'usd', "date" date NOT NULL, "price" numeric NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UK_historic_price_baseCurrency_quoteCurrency_date" UNIQUE ("baseCurrency", "quoteCurrency", "date"), CONSTRAINT "PK_b0a22436b47e742187aa7408561" PRIMARY KEY ("id"))`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "historic_market_price"`);
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if these 2 migration files are different, but I guess one of them is not needed. The second migration will fail because the table will already be created by the first one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea this was a mistake. wil fix

Comment on lines 27 to 28
@Column()
date: string;
Copy link
Contributor

@amateima amateima Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should definitely be a typed Date property and the DB column should be date. This way we can take advantage of special queries/functionalities that SQL provides for such column type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the complexity is that we only care about 1 price per day, which means deterministically calculating the "day" based on a js Date or timestamp becomes more involved. Ill add the logic and u can see if thats what you want

return;
}
await historicPriceRepository.insert({
date: dbFormattedDate,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to not leak here the weird way that CoinGecko handles date format. Here we should pass a simple date instance as value

}
await historicPriceRepository.insert({
date: dbFormattedDate,
baseCurrency: symbol,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the past I experienced inconsistencies with the symbols returned by CoinGecko API, for example different letter capitalisation for the same token, so I propose to do a toLowerCase() on the symbol

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is currently an enum, so if anything is wrong we can fix it in the enum

@@ -193,6 +196,9 @@ export function envToConfig(env: Env): Config {
enabledWebhookRequestWorkers: true,
clients: parseWebhookClientsFromString(env.WEBHOOK_CLIENTS ?? "[]"),
};
const coingeckoSymbols = parseArray(env.COINGECKO_SYMBOLS).map((symbol) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document this ENV value in the README and specify the format of it (list, object, etc)

@daywiss daywiss requested a review from amateima December 19, 2024 14:31
@daywiss daywiss force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch from 4bb3196 to bc2bf3d Compare December 24, 2024 16:51
@daywiss daywiss force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch from bc2bf3d to a3283e9 Compare December 30, 2024 13:37
try {
return assertModule.ok(value, message);
return assertModule.ok(value !== null && value !== undefined, message);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the edge case that was not handled gracefully by the current implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i probably dont need to do this

Comment on lines 19 to 30
/**
* This worker listens to the `IntegratorId` queue and processes each job by:
* - Retrieving the deposit information from the database based on the provided relay hash.
* - Checking if the deposit record already has an integrator ID.
* - If the integrator ID is not set, the worker fetches it from the transaction data.
* - If found, the integrator ID is saved back into the deposit record in the database.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments should be updated or removed

* - If found, the integrator ID is saved back into the deposit record in the database.
*/
export class PriceWorker {
public worker: Worker;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this property needed to be public?

Comment on lines 59 to 126
const relayHashInfoRepository = this.postgres.getRepository(
entities.RelayHashInfo,
);
const depositRepository = this.postgres.getRepository(
entities.V3FundsDeposited,
);
const historicPriceRepository = this.postgres.getRepository(
entities.HistoricPrice,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would make sense to make the repository variables to be class properties and set them in the constructor? This way we would reduce the scope of run() function by not having it to deal with instantiating dependencies

Comment on lines 96 to 176
const baseTokenInfo = findTokenByAddress(
relayHashInfo.fillEvent.outputToken,
relayHashInfo.destinationChainId,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that findTokenByAddress() function works on a list of tokens that filters out the tokens for which we don't have the CoinGecko symbol defined in the indexer. I don't think this is necessary.
The only condition we need to check is if the token is part of the supported tokens specified in the @across-protocol/constants. If we do this, we also don't need the CoinGecko env anymore

}
const priceTime = yesterday(blockTime);
const quoteCurrency = "usd";
const baseTokenInfo = findTokenByAddress(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to fetch price for input token too

* - If not, fetching the historic price from Coingecko and inserting it into the database.
* - Logging errors and information at various stages of the process.
*/
export class PriceWorker {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way errors are handled in this worker should be reviewed and changed accordingly. The only situation in which the message should be discarded and execution should stop is the case when the deposit with a particular depositId and originChainId doesn't exist. Otherwise, we should log the error (as we do) and throw an error, so that the worker retries the message

@daywiss daywiss force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch 2 times, most recently from deecf87 to ae571cf Compare December 30, 2024 19:08
@daywiss daywiss requested a review from amateima December 30, 2024 19:10
@amateima amateima force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch from ae571cf to 8302d4a Compare January 3, 2025 13:30
Copy link
Contributor

@amateima amateima left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at the code yet. I tried to test it locally, but I hit this error
Screenshot 2025-01-03 at 15 34 19

@daywiss daywiss force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch from 8183e68 to 4e3756e Compare January 3, 2025 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants