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 9 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 36
/**
* 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 143
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 221
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
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 am not able to run the workers successfully, see screenshot for error.

Screenshot 2025-01-06 at 13 29 49

I think the problem is at line 154 in the worker:

const deposit = await this.depositRepository.findOne({
      where: { depositId, originChainId },
    });

Is very possible for fill events to be fetched before the deposit events on the origin chain, so maybe in this case we should retry messages until the corresponding deposit event is found in the DB?

@daywiss daywiss requested a review from amateima January 6, 2025 18:28
@daywiss daywiss force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch from 58703e7 to fe7f7de Compare January 6, 2025 18:49
Copy link
Contributor

@james-a-morris james-a-morris left a comment

Choose a reason for hiding this comment

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

Could be worth reviewing: https://github.com/across-protocol/sdk/tree/master/src/coingecko

We could probably leverage this heavily to avoid code duplication. This will be helpful when we have to worry about SVM.

packages/error-handling/src/utils/assert.ts Show resolved Hide resolved
packages/indexer-database/src/entities/HistoricPrice.ts Outdated Show resolved Hide resolved
Comment on lines +19 to +24
// bear in mind we are using coingecko symbols directly here, for all intents and purposes this is coingecko historic market price
@Column()
baseCurrency: string;

@Column({ default: "usd" })
quoteCurrency: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be worth enumerated the expected values of these. We're unlikely to deviate from usd or eth or azero or matic as a base currency and the quote currency is unlikely to not be usd

packages/indexer-database/src/entities/RelayHashInfo.ts Outdated Show resolved Hide resolved
packages/indexer/src/messaging/priceWorker.ts Outdated Show resolved Hide resolved
packages/indexer/src/messaging/priceWorker.ts Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

OOC why was this updated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

originally there was some env changes made for this pr, which are no longer needed, but this was accidentally left in

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 stylistically this is slightly better than before but i can revert if you like the other way better

Comment on lines +37 to +50
// given an address and chain id, return the token data
export function findTokenByAddress(address: string, chainId: number): Token {
const result = tokensList.find(
(token) =>
token.address.toLowerCase() === address.toLowerCase() &&
token.chainId === chainId,
);
if (!result) {
throw new Error(
`Token info not found for address: ${address} on chainId: ${chainId}`,
);
}
return result;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We have a similar function in the SDK - maybe we should defer to this

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 if such function exists

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will need to tackle this when i get back, but was able to remove the coingecko library

David and others added 8 commits January 7, 2025 13:28
Signed-off-by: david <david@umaproject.org>
Signed-off-by: david <david@umaproject.org>
… prices

Signed-off-by: david <david@umaproject.org>
Signed-off-by: david <david@umaproject.org>
Signed-off-by: david <david@umaproject.org>
…output price

Signed-off-by: david <david@umaproject.org>
Signed-off-by: david <david@umaproject.org>
@daywiss daywiss force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch from fe7f7de to f40359a Compare January 7, 2025 18:28
Signed-off-by: david <david@umaproject.org>
@daywiss daywiss force-pushed the david/acx-3505-identify-tokens-to-start-indexing-prices branch from f40359a to 975f323 Compare January 7, 2025 18:33
@daywiss daywiss requested a review from james-a-morris January 7, 2025 18:33
Copy link
Contributor

@james-a-morris james-a-morris left a comment

Choose a reason for hiding this comment

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

Curious about #138 (comment)

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.

3 participants