-
Notifications
You must be signed in to change notification settings - Fork 4
feat: bootstrap scripts & metadata and pricing improvements #63
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
Changes from 24 commits
4d47c1e
49ae81c
fe17f98
9992af7
2d114f0
5491be8
50ef5b4
adf1bf2
ad6c803
04d481c
f0c25ec
37c63ae
1f07ae8
227e2a7
632cb94
2f7c02c
dbac0f9
a413df9
45ca767
34d7f96
74a3bb5
17bcc8b
230e42c
f07d951
5b4612d
218e933
d41c4fb
d5a42c0
43534b3
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 |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ const chainSchema = z.object({ | |
| }); | ||
|
|
||
| const baseSchema = z.object({ | ||
| NODE_ENV: z.enum(["development", "staging", "production"]).default("development"), | ||
| CHAINS: stringToJSONSchema.pipe(z.array(chainSchema).nonempty()).refine((chains) => { | ||
| const ids = chains.map((chain) => chain.id); | ||
| const uniqueIds = new Set(ids); | ||
|
|
@@ -31,14 +32,12 @@ const baseSchema = z.object({ | |
| DATABASE_URL: z.string(), | ||
| DATABASE_SCHEMA: z.string().default("public"), | ||
| INDEXER_GRAPHQL_URL: z.string().url(), | ||
| INDEXER_ADMIN_SECRET: z.string(), | ||
| INDEXER_ADMIN_SECRET: z.string().optional(), | ||
| PRICING_SOURCE: z.enum(["dummy", "coingecko"]).default("coingecko"), | ||
| IPFS_GATEWAYS_URL: stringToJSONSchema | ||
| .pipe(z.array(z.string().url())) | ||
| .default('["https://ipfs.io"]'), | ||
| METADATA_SOURCE: z.enum(["dummy", "public-gateway"]).default("dummy"), | ||
| RETRY_MAX_ATTEMPTS: z.coerce.number().int().min(1).default(3), | ||
| RETRY_BASE_DELAY_MS: z.coerce.number().int().min(1).default(3000), // 3 seconds | ||
| RETRY_FACTOR: z.coerce.number().int().min(1).default(2), | ||
| RETRY_FACTOR: z.coerce.number().min(1).default(2), | ||
| RETRY_MAX_DELAY_MS: z.coerce.number().int().min(1).optional(), // 5 minute | ||
| }); | ||
|
|
||
|
|
@@ -53,6 +52,15 @@ const coingeckoPricingSchema = baseSchema.extend({ | |
| COINGECKO_API_TYPE: z.enum(["demo", "pro"]).default("pro"), | ||
| }); | ||
|
|
||
| const dummyMetadataSchema = baseSchema.extend({ | ||
| METADATA_SOURCE: z.literal("dummy"), | ||
| }); | ||
|
|
||
| const publicGatewayMetadataSchema = baseSchema.extend({ | ||
| METADATA_SOURCE: z.literal("public-gateway"), | ||
| PUBLIC_GATEWAY_URLS: stringToJSONSchema.pipe(z.array(z.string().url())), | ||
| }); | ||
|
|
||
| const validationSchema = z | ||
| .discriminatedUnion("PRICING_SOURCE", [dummyPricingSchema, coingeckoPricingSchema]) | ||
| .transform((val) => { | ||
|
|
@@ -66,7 +74,27 @@ const validationSchema = z | |
| apiType: val.COINGECKO_API_TYPE, | ||
| ...val, | ||
| }; | ||
| }); | ||
| }) | ||
| .and( | ||
| z | ||
| .discriminatedUnion("METADATA_SOURCE", [ | ||
| dummyMetadataSchema, | ||
| publicGatewayMetadataSchema, | ||
| ]) | ||
| .transform((val) => { | ||
| if (val.METADATA_SOURCE === "dummy") { | ||
| return { metadataSource: val.METADATA_SOURCE, ...val }; | ||
| } | ||
| if (val.METADATA_SOURCE === "public-gateway") { | ||
| return { | ||
| metadataSource: val.METADATA_SOURCE, | ||
| gateways: val.PUBLIC_GATEWAY_URLS, | ||
| ...val, | ||
| }; | ||
| } | ||
| throw new Error("Invalid metadata source"); | ||
| }), | ||
| ); | ||
|
Comment on lines
+83
to
+97
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. 🛠️ Refactor suggestion Simplify chained discriminated unions for maintainability. The combination of two
Collaborator
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. would be interesting to see what solution or apporach you suggest on this? 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. @0xnigir1 Here's a suggested approach to simplify the schema using a single discriminated union: // Define all possible combinations of sources
const sourceSchema = z.object({
sourceType: z.enum([
'dummy-all',
'dummy-pricing-coingecko-metadata',
'coingecko-pricing-dummy-metadata',
'coingecko-pricing-pinata-metadata',
'coingecko-pricing-public-gateway-metadata',
]),
});
// Define source-specific configurations
const sourcesConfig = {
'dummy-all': baseSchema.extend({
sourceType: z.literal('dummy-all'),
DUMMY_PRICE: z.coerce.number().optional().default(1),
}),
'dummy-pricing-coingecko-metadata': baseSchema.extend({
sourceType: z.literal('dummy-pricing-coingecko-metadata'),
DUMMY_PRICE: z.coerce.number().optional().default(1),
COINGECKO_API_KEY: z.string().min(1),
COINGECKO_API_TYPE: z.enum(['demo', 'pro']).default('pro'),
}),
// ... other combinations
};
const validationSchema = z
.discriminatedUnion('sourceType', Object.values(sourcesConfig))
.transform((val) => {
const config: Record<string, unknown> = {
pricingSource: val.sourceType.includes('dummy-pricing') ? 'dummy' : 'coingecko',
metadataSource: val.sourceType.includes('dummy-metadata') ? 'dummy' :
val.sourceType.includes('pinata') ? 'pinata' :
val.sourceType.includes('public-gateway') ? 'public-gateway' : 'dummy',
};
// Add source-specific configurations
if (config.pricingSource === 'dummy') {
config.dummyPrice = val.DUMMY_PRICE;
}
if (config.pricingSource === 'coingecko') {
config.apiKey = val.COINGECKO_API_KEY;
config.apiType = val.COINGECKO_API_TYPE;
}
// ... handle metadata sources similarly
return { ...val, ...config };
});This approach:
Let me know if you'd like me to explain any part in more detail! |
||
|
|
||
| const env = validationSchema.safeParse(process.env); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| /** | ||
| * Maximum number of retries for bulk fetching metadata. | ||
| */ | ||
| export const MAX_BULK_FETCH_METADATA_RETRIES = 10; | ||
|
|
||
| /** | ||
| * Base delay in milliseconds for bulk fetching metadata retries. | ||
| */ | ||
| export const METADATA_BULK_FETCH_BASE_DELAY_MS = 1000; | ||
|
|
||
| /** | ||
| * Backoff factor for bulk fetching metadata retries. | ||
| */ | ||
| export const METADATA_BULK_FETCH_BACKOFF_FACTOR = 1.5; | ||
|
|
||
| /** | ||
| * Maximum concurrency for bulk fetching metadata. | ||
| */ | ||
| export const MAX_BULK_FETCH_METADATA_CONCURRENCY = 10; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { | ||
| decodeDGApplicationData, | ||
| decodeDVMDApplicationData, | ||
| decodeDVMDExtendedApplicationData, | ||
| } from "@grants-stack-indexer/processors"; | ||
| import { AnyIndexerFetchedEvent, ILogger } from "@grants-stack-indexer/shared"; | ||
|
|
||
| /** | ||
| * Extracts unique metadata ids from the events batch. | ||
| * @param events - Array of indexer fetched events to process | ||
| * @returns Array of unique metadata ids found in the events | ||
| */ | ||
| export const getMetadataCidsFromEvents = ( | ||
| events: AnyIndexerFetchedEvent[], | ||
| logger: ILogger, | ||
| ): string[] => { | ||
| const ids = new Set<string>(); | ||
|
|
||
| for (const event of events) { | ||
| if ("metadata" in event.params) { | ||
| ids.add(event.params.metadata[1]); | ||
| } else if ("data" in event.params) { | ||
| try { | ||
| const decoded = decodeDGApplicationData(event.params.data); | ||
| ids.add(decoded.metadata.pointer); | ||
| } catch (error) {} | ||
|
Comment on lines
+23
to
+26
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. Add error logging in catch blocks. Empty catch blocks silently swallow errors, making it difficult to debug issues with metadata decoding. Consider logging these errors or implementing proper error handling. try {
const decoded = decodeDGApplicationData(event.params.data);
ids.add(decoded.metadata.pointer);
-} catch (error) {}
+} catch (error) {
+ console.warn(`Failed to decode DG application data: ${error}`);
+}Also applies to: 24-27, 28-31 |
||
| try { | ||
| const decoded = decodeDVMDApplicationData(event.params.data); | ||
| ids.add(decoded.metadata.pointer); | ||
| } catch (error) {} | ||
| try { | ||
| const decoded = decodeDVMDExtendedApplicationData(event.params.data); | ||
| ids.add(decoded.metadata.pointer); | ||
| } catch (error) { | ||
| logger.warn("Failed to decode Metadata CID from event data", { | ||
| error, | ||
| event, | ||
| }); | ||
| } | ||
| } | ||
|
Comment on lines
+23
to
+40
Collaborator
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. now im thinking, we could write a decode method that doesn't throw but instead returns [boolean, result | undefined] so we don't have this nested try/catch blocks. i think it would make it a bit cleaner
Contributor
Author
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 would leave it as it is, it doesn't bother imo |
||
| } | ||
|
|
||
| return Array.from(ids); | ||
| }; | ||
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.
Something to have in mind that could avoid throwing errors:
https://github.com/colinhacks/zod?tab=readme-ov-file#validating-during-transform