Skip to content

Improve price fetching#603

Merged
ebma merged 10 commits into
stagingfrom
improve-price-fetching
Apr 30, 2025
Merged

Improve price fetching#603
ebma merged 10 commits into
stagingfrom
improve-price-fetching

Conversation

@ebma
Copy link
Copy Markdown
Member

@ebma ebma commented Apr 28, 2025

This pull request introduces significant enhancements to the pricing API, focusing on error handling, input validation, and support for a new bundled endpoint that aggregates prices from multiple providers. The changes improve robustness, maintainability, and user experience by standardizing error responses, centralizing validation logic, and providing a comprehensive view of pricing data.

New Features

  • Bundled Price Endpoint: Added a new /all route to fetch prices from all providers in a single request. This includes the getAllPricesBundled controller and AllPricesResponse type for consistent response formatting. [1] [2]

Error Handling Improvements

  • Standardized Errors: Introduced custom error types like InvalidAmountError, InvalidParameterError, and ProviderInternalError to handle provider-specific and generic errors consistently across services. [1] [2] [3] [4]
  • Service-Level Enhancements: Updated AlchemyPay, Moonpay, and Transak services to use the new error types, log errors, and handle edge cases like JSON parsing failures and missing fields in responses. [1] [2] [3]

Input Validation

  • Centralized Validation: Added validateBundledPriceInput middleware to validate query parameters for the new bundled endpoint, ensuring proper input before processing.
  • Improved Error Messages: Validation errors now include detailed messages about supported currencies and required parameters.

API Refactoring

  • Simplified Controller Logic: Removed redundant error-catching logic in provider handlers, allowing errors to propagate naturally for centralized handling.
  • Response Consistency: Ensured all endpoints return structured and meaningful error responses, including HTTP status codes and error messages.

Miscellaneous

  • Cache Key Update: Added a new cache key allPrices to support caching for the bundled endpoint in the frontend.

@ebma ebma requested a review from Copilot April 28, 2025 17:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR improves the pricing API by adding a bundled price endpoint and enhancing error handling and input validation across both backend and frontend services.

  • Introduces new types and a bundled endpoint for aggregating prices across providers.
  • Refactors service error handling to use standardized custom error types.
  • Updates input validation logic and front‑end querying to support the new endpoint.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
shared/src/endpoints/price.endpoints.ts Added types for the bundled pricing endpoint.
frontend/src/services/api/price.service.ts Added getAllPricesBundled and deprecated the older getAllPrices method.
frontend/src/sections/FeeComparison/priceProviders.tsx Removed per‑provider query functions in favor of centralized bundled fetching.
frontend/src/sections/FeeComparison/FeeProviderRow/index.tsx Removed onPriceFetched usage and adjusted price extraction from bundled results.
frontend/src/sections/FeeComparison/FeeComparisonTable/index.tsx Replaced individual provider queries with a bundled query and recalculated provider prices.
frontend/src/constants/cache.ts Introduced a new cache key for bundled prices.
api/src/api/services/*.(transak moonpay
api/src/api/routes/v1/price.route.ts Added a new route for the bundled endpoint and adjusted existing routes.
api/src/api/middlewares/validators.ts Added bundled price input validation middleware.
api/src/api/controllers/price.controller.ts Implemented controller logic for aggregating bundled provider prices.
Comments suppressed due to low confidence (2)

frontend/src/sections/FeeComparison/FeeComparisonTable/index.tsx:39

  • The variables 'sourceAssetSymbol' and 'targetAssetSymbol' are used in the queryKey but are not defined in this diff. Confirm if they should be replaced with already available values (such as those from useRampFormStore) or imported from elsewhere.
queryKey: [cacheKeys.allPrices, amount, sourceAssetSymbol, targetAssetSymbol, selectedNetwork],

api/src/api/middlewares/validators.ts:88

  • Ensure that PriceEndpoints is properly imported in this file; otherwise, a ReferenceError may occur when accessing PriceEndpoints.isValidCryptoCurrency.
if (!fromCrypto || !PriceEndpoints.isValidCryptoCurrency(fromCrypto)) {

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 28, 2025

Deploy Preview for pendulum-pay ready!

Name Link
🔨 Latest commit ad6a6c1
🔍 Latest deploy log https://app.netlify.com/sites/pendulum-pay/deploys/68126b2560429e0008b28671
😎 Deploy Preview https://deploy-preview-603--pendulum-pay.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@ebma ebma requested a review from a team April 29, 2025 08:14

const response: PriceEndpoints.AllPricesResponse = {};

results.forEach((result) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What do you think about changing .forEach to .map to indicate that we don't mutate the content of the results array? I think it is clearer and more understandable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm not sure. To me, .forEach doesn't indicate mutation. On the other hand .map indicates to me that I want to map results to something else and assign the new transformed value to a variable. Which we don't do here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes we are changing response not result. Actually I would use a for loop because to me is more explicit and the forEach won't await if there is any async inside, but it is not the case now, so all options should work I guess.

}

let body: AlchemyPayResponse;
try {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm wondering, we use this let/try/catch pattern in many places in our app. I really don't like using let because the type can be reassigned and doesn't impose immutability. I think we should introduce a helper function like

type Success<T> = {
  data: T;
  error: null;
};

type Failure<E> = {
  data: null;
  error: E;
};

type Result<T, E = Error> = Success<T> | Failure<E>;

export async function tryCatch<T, E = Error>(
  promise: Promise<T>,
): Promise<Result<T, E>> {
  try {
    const data = await promise;
    return { data, error: null };
  } catch (error) {
    return { data: null, error: error as E };
  }
}

to make the try/catch handling easier and clearer (maybe in another PR?)

const body = await tryCatch(response.json());

Copy link
Copy Markdown
Member Author

@ebma ebma Apr 30, 2025

Choose a reason for hiding this comment

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

I'm fine with refactoring it. We can refactor it using the neverthrow package but let's not do it on this PR.

// Analyze error message for specific types
const lowerErrorMessage = errorMessage.toLowerCase();
if (
lowerErrorMessage.includes('invalid fiat currency') ||
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could use here an enum instead of directly passing the strings, wdyt?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

True we could. Not sure if this would improve the readability though. We have to assume that all the code in transak.service.ts, moonpay.service.ts and alchemypay.service.ts is completely tailored to each service provider and we can't reuse code anyway. So maybe we just live with these strings in those files for now?

retry: false,
});
// Determine if there's an error from the result
const error = result?.status === 'rejected' ? result.reason : undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There shouldn't be an error because we filter them on the const providerPrices = useMemo(() function right? Still, don't mind if we leave it as redundancy for a future change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, there could be an error. The check/filtering happens on a different object that is used to show the best provider. The row itself gets an 'unchecked' response and it could contain an error.

@ebma ebma merged commit 2a34b54 into staging Apr 30, 2025
5 checks passed
@ebma ebma deleted the improve-price-fetching branch April 30, 2025 18:29
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.

4 participants