Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions src/app/api/purchase/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { nanoid } from "nanoid";
import { FlightLib__factory, FlightOracle__factory, FlightProduct__factory, FlightUSD__factory } from "../../../contracts/flight";
import { IPolicyService__factory } from "../../../contracts/gif";
import { IBundleService__factory, IPoolService__factory } from "../../../contracts/gif/factories/pool";
import { AirportBlacklistedError, AirportNotWhitelistedError, TransactionFailedException } from "../../../types/errors";
import { AirportBlacklistedError, AirportNotWhitelistedError, FlightNotFoundError, InconsistentFlightDataError, TransactionFailedException } from "../../../types/errors";
import { Airport } from "../../../types/flightstats/airport";
import { ApplicationData, PermitData, PurchaseRequest } from "../../../types/purchase_request";
import { LOGGER } from "../../../utils/logger_backend";
Expand Down Expand Up @@ -54,6 +54,16 @@ export async function POST(request: Request) {
error: "AIRPORT_NOT_WHITELISTED",
message: err.message,
}, { status: 400 });
} else if (err instanceof FlightNotFoundError) {
return Response.json({
error: "NO_FLIGHT_FOUND",
message: err.message,
}, { status: 404 });
} else if (err instanceof InconsistentFlightDataError) {
return Response.json({
error: "INCONSISTENT_DATA",
message: err.message,
}, { status: 400 });
} else {
// @ts-expect-error balance error
if (err.message === "BALANCE_ERROR") {
Expand Down Expand Up @@ -141,7 +151,7 @@ async function validateFlightPlan(reqId: string, application: ApplicationData) {
} catch (err) {
// @ts-expect-error error has field message
LOGGER.error(err.message);
throw new Error(`[${reqId}] Flight not found`);
throw new FlightNotFoundError(`[${reqId}] Flight not found`);
}
}

Expand All @@ -157,13 +167,13 @@ async function validateStatistics(reqId: string, application: ApplicationData) {
const fsResponse = await fetch(url);

if (!fsResponse.ok) {
throw new Error(`[${reqId}] Flight not found on flightstats api`);
throw new FlightNotFoundError(`[${reqId}] Flight not found on flightstats api`);
}

const fsData = await fsResponse.json();

if (fsData.ratings === undefined || fsData.ratings.length === 0) {
throw new Error(`[${reqId}] Flight ratings not found`);
throw new FlightNotFoundError(`[${reqId}] Flight ratings not found`);
}

const rating = fsData.ratings[0] as Rating;
Expand All @@ -172,12 +182,12 @@ async function validateStatistics(reqId: string, application: ApplicationData) {

// compare stats vs application statistics
if (stats.length !== application.statistics.length) {
throw new Error(`[${reqId}] Statistics length mismatch`);
throw new InconsistentFlightDataError(`[${reqId}] Statistics length mismatch`);
}

LOGGER.debug(`stats: ${JSON.stringify(stats)}, application statistics: ${JSON.stringify(application.statistics)}`);
if (!stats.every((value, index) => value === application.statistics[index])) {
throw new Error(`[${reqId}] Statistics mismatch`);
throw new InconsistentFlightDataError(`[${reqId}] Statistics mismatch`);
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/hooks/api/use_local_api.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PurchaseErrorCode } from "../../types/errors";
import { ApplicationData, PermitData } from "../../types/purchase_request";
import { PurchaseFailedError, PurchaseNotPossibleError } from "../../utils/error";
import { PurchaseFailedError, PurchaseNotPossibleError, PurchaseValidationError } from "../../utils/error";

// @ts-expect-error BigInt is not defined in the global scope
BigInt.prototype.toJSON = function () {
Expand Down Expand Up @@ -29,8 +30,10 @@ export function useLocalApi() {
throw new PurchaseFailedError(result.transaction, result.decodedError);
} else if (result.error === "BALANCE_ERROR") {
throw new PurchaseNotPossibleError();
} else if (Object.values(PurchaseErrorCode).includes(result.error as PurchaseErrorCode)) {
throw new PurchaseValidationError(result.error as PurchaseErrorCode);
} else {
throw new Error(`Error sending purchase protection request: ${result.statusText}`);
throw new Error(result.message || `Error sending purchase protection request: ${res.statusText}`);
}
}

Expand Down
19 changes: 18 additions & 1 deletion src/hooks/use_application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { resetPurchase, setExecuting, setPolicy, setSigning } from "../redux/sli
import { RootState } from "../redux/store";
import { Erc20PermitSignature } from "../types/erc20permitsignature";
import { ApplicationData, PermitData } from "../types/purchase_request";
import { PurchaseFailedError, PurchaseNotPossibleError } from "../utils/error";
import { PurchaseErrorCode } from "../types/errors";
import { PurchaseFailedError, PurchaseNotPossibleError, PurchaseValidationError } from "../utils/error";
import { useLocalApi } from "./api/use_local_api";
import { useERC20Contract } from "./onchain/use_erc20_contract";
import { useFlightDelayProductContract } from "./onchain/use_flightdelay_product";
Expand Down Expand Up @@ -167,6 +168,22 @@ export default function useApplication() {
dispatch(setError({ message: `${t("error.purchase_failed")} (${err.decodedError?.reason || "unknown error"})`, level: "error" }));
} else if (err instanceof PurchaseNotPossibleError) {
dispatch(setError({ message: t("error.purchase_currently_not_possible"), level: "error" }));
} else if (err instanceof PurchaseValidationError) {
switch (err.code) {
case PurchaseErrorCode.NO_FLIGHT_FOUND:
dispatch(setError({ message: t("error.no_flight_found"), level: "error" }));
break;
case PurchaseErrorCode.INCONSISTENT_DATA:
dispatch(setError({ message: t("error.inconsistent_data"), level: "error" }));
break;
case PurchaseErrorCode.AIRPORT_BLACKLISTED:
case PurchaseErrorCode.AIRPORT_NOT_WHITELISTED:
dispatch(setError({ message: t("error.purchase_currently_not_possible"), level: "error" }));
break;
default:
dispatch(setError({ message: t("error.unknown_error"), level: "error" }));
break;
}
} else {
// @ts-expect-error code is custom field for metamask error
if (err.code !== undefined) {
Expand Down
19 changes: 19 additions & 0 deletions src/types/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ export enum Reason {
NOT_ENOUGH_CAPACITY,
}

export enum PurchaseErrorCode {
NO_FLIGHT_FOUND = "NO_FLIGHT_FOUND",
INCONSISTENT_DATA = "INCONSISTENT_DATA",
AIRPORT_BLACKLISTED = "AIRPORT_BLACKLISTED",
AIRPORT_NOT_WHITELISTED = "AIRPORT_NOT_WHITELISTED",
}

export class FlightNotFoundError extends Error {
constructor(message = 'Flight not found') {
super(message);
}
}

export class InconsistentFlightDataError extends Error {
constructor(message = 'Inconsistent flight data') {
super(message);
}
}

/**
* Exception thrown when a transaction fails. Contains the transaction receipt in field `transaction`.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DecodedError } from "ethers-decode-error";
import { PurchaseErrorCode } from "../types/errors";

export function ensureError(value: unknown): Error {
if (value instanceof Error) return value;
Expand Down Expand Up @@ -51,6 +52,15 @@ export class PurchaseNotPossibleError extends Error {
}
}

export class PurchaseValidationError extends Error {
code: PurchaseErrorCode;

constructor(code: PurchaseErrorCode) {
super(`Purchase validation failed: ${code}`);
this.code = code;
}
}

export class CapacityError extends Error {
constructor() {
super("Capacity error");
Expand Down