-
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(indexer): add coingecko price collection from tokens against usd #138
base: master
Are you sure you want to change the base?
feat(indexer): add coingecko price collection from tokens against usd #138
Conversation
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"`); | ||
} | ||
|
||
} |
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.
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
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.
yea this was a mistake. wil fix
@Column() | ||
date: string; |
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.
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.
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 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, |
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 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, |
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.
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
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.
this is currently an enum, so if anything is wrong we can fix it in the enum
packages/indexer/src/parseEnv.ts
Outdated
@@ -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) => |
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.
We should document this ENV value in the README and specify the format of it (list, object, etc)
4bb3196
to
bc2bf3d
Compare
bc2bf3d
to
a3283e9
Compare
try { | ||
return assertModule.ok(value, message); | ||
return assertModule.ok(value !== null && value !== undefined, message); |
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.
What was the edge case that was not handled gracefully by the current implementation?
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 probably dont need to do this
/** | ||
* 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. | ||
*/ |
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.
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; |
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.
Is this property needed to be public?
const relayHashInfoRepository = this.postgres.getRepository( | ||
entities.RelayHashInfo, | ||
); | ||
const depositRepository = this.postgres.getRepository( | ||
entities.V3FundsDeposited, | ||
); | ||
const historicPriceRepository = this.postgres.getRepository( | ||
entities.HistoricPrice, | ||
); |
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.
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
const baseTokenInfo = findTokenByAddress( | ||
relayHashInfo.fillEvent.outputToken, | ||
relayHashInfo.destinationChainId, | ||
); |
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 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( |
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.
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 { |
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.
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
deecf87
to
ae571cf
Compare
ae571cf
to
8302d4a
Compare
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.
8183e68
to
4e3756e
Compare
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 am not able to run the workers successfully, see screenshot for error.
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?
58703e7
to
fe7f7de
Compare
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.
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.
// 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; |
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.
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/src/data-indexing/service/SpokePoolIndexerDataHandler.ts
Show resolved
Hide resolved
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.
OOC why was this updated?
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.
originally there was some env changes made for this pr, which are no longer needed, but this was accidentally left in
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 think stylistically this is slightly better than before but i can revert if you like the other way better
// 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; | ||
} |
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.
We have a similar function in the SDK - maybe we should defer to this
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.
+1 if such function exists
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 need to tackle this when i get back, but was able to remove the coingecko library
Signed-off-by: david <david@umaproject.org>
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>
fe7f7de
to
f40359a
Compare
Signed-off-by: david <david@umaproject.org>
f40359a
to
975f323
Compare
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.
Curious about #138 (comment)
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