Skip to content

Commit

Permalink
(feat): perform order querying when fetching orders through graphql a…
Browse files Browse the repository at this point in the history
…pi (#116)

* (feat): perform order querying when fetching orders through graphql api

* (feat): do not do a roundtrip to api from api, implement feedback
  • Loading branch information
Jipperism authored Jul 23, 2024
1 parent 0ce5dce commit d676d47
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 68 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@tsoa/runtime": "^6.2.1",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/lodash": "^4.17.7",
"@types/node": "20.10.6",
"@ucanto/core": "^9.0.1",
"@ucanto/principal": "^9.0.0",
Expand All @@ -54,6 +55,7 @@
"graphql-middleware": "^6.1.35",
"graphql-scalars": "^1.23.0",
"graphql-yoga": "^5.1.1",
"lodash": "^4.17.21",
"reflect-metadata": "^0.2.2",
"rollup": "^4.12.0",
"swagger-ui-express": "^5.0.0",
Expand Down
19 changes: 15 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 14 additions & 54 deletions src/controllers/MarketplaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { ApiResponse } from "../types/api.js";
import {
addressesByNetwork,
HypercertExchangeClient,
OrderValidatorCode,
utils,
} from "@hypercerts-org/marketplace-sdk";
import { ethers, verifyTypedData } from "ethers";
Expand All @@ -21,7 +20,6 @@ import { SupabaseDataService } from "../services/SupabaseDataService.js";
import { isAddress } from "viem";
import { isParsableToBigInt } from "../utils/isParsableToBigInt.js";
import { getFractionsById } from "../utils/getFractionsById.js";
import { getRpcUrl } from "../utils/getRpcUrl.js";

export interface CreateOrderRequest {
signature: string;
Expand Down Expand Up @@ -361,65 +359,27 @@ export class MarketplaceController extends Controller {
const { tokenIds, chainId } = parsedQuery.data;
const supabase = new SupabaseDataService();

const ordersToUpdate: {
id: string;
invalidated: boolean;
validator_codes: OrderValidatorCode[];
}[] = [];
for (const tokenId of tokenIds) {
// Fetch all orders for token ID from database
const { data: matchingOrders, error } = await supabase.getOrdersByTokenId(
{
tokenId,
chainId,
},
);

try {
const ordersToUpdate = await supabase.validateOrdersByTokenIds({
tokenIds,
chainId,
});
this.setStatus(200);
return {
success: true,
message: "Orders have been validated",
data: ordersToUpdate,
};
} catch (error) {
console.error(error);
if (error) {
this.setStatus(500);
return {
success: false,
message: error.message,
data: null,
};
}

if (!matchingOrders) {
this.setStatus(404);
return {
success: false,
message: "Orders not found",
message: "Could not validate orders",
data: null,
};
}

// Validate orders using logic in the SDK
const hec = new HypercertExchangeClient(
chainId,
// @ts-expect-error Typing issue with provider
new ethers.JsonRpcProvider(getRpcUrl(chainId)),
);
const validationResults = await hec.checkOrdersValidity(matchingOrders);

// Determine which orders to update in DB, and update them
ordersToUpdate.push(
...validationResults
.filter((x) => !x.valid)
.map(({ validatorCodes, id }) => ({
id,
invalidated: true,
validator_codes: validatorCodes,
})),
);
}
console.log("[marketplace-api] Invalidating orders", ordersToUpdate);
await supabase.updateOrders(ordersToUpdate);

this.setStatus(200);
return {
success: true,
message: "Orders have been validated",
data: ordersToUpdate,
};
}
}
56 changes: 49 additions & 7 deletions src/graphql/schemas/resolvers/orderResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { GraphQLBigInt } from "graphql-scalars";
import { getHypercertTokenId } from "../../../utils/tokenIds.js";
import { HypercertBaseType } from "../typeDefs/baseTypes/hypercertBaseType.js";
import { getAddress } from "viem";
import { HypercertExchangeClient } from "@hypercerts-org/marketplace-sdk";
import { ethers } from "ethers";
import { getRpcUrl } from "../../../utils/getRpcUrl.js";
import _ from "lodash";

@ObjectType()
export default class GetOrdersResponse {
Expand Down Expand Up @@ -48,17 +52,55 @@ class OrderResolver {
try {
const res = await this.supabaseService.getOrders(args);

const { data, error, count } = res;
const { data: orders, error, count } = res;

if (error) {
console.warn(
`[ContractResolver::orders] Error fetching orders: `,
error,
);
return { data };
console.warn(`[OrderResolver::orders] Error fetching orders: `, error);
return { orders };
}

return { data, count: count ? count : data?.length };
const groupedOrders = _.groupBy(orders, (order) => order.chainId);

const ordersAfterCheckingValidity = await Promise.all(
Object.entries(groupedOrders).map(async ([chainId, ordersForChain]) => {
const chainIdParsed = parseInt(chainId);
const hypercertExchangeClient = new HypercertExchangeClient(
chainIdParsed,
// @ts-expect-error - TODO: fix these types
new ethers.JsonRpcProvider(getRpcUrl(chainIdParsed)),
);

const validityResults =
await hypercertExchangeClient.checkOrdersValidity(
ordersForChain.filter((order) => !order.invalidated),
);
const tokenIdsWithInvalidOrder = validityResults
.filter((result) => !result.valid)
.map((result) => BigInt(result.order.itemIds[0]));
if (tokenIdsWithInvalidOrder.length) {
console.log(
"[OrderResolver::orders]:: Found invalid orders",
tokenIdsWithInvalidOrder,
);
// Fire off the validation but don't wait for it to finish
this.supabaseService.validateOrdersByTokenIds({
tokenIds: tokenIdsWithInvalidOrder.map((id) => id.toString()),
chainId: chainIdParsed,
});
}
return ordersForChain.map((order) => {
if (tokenIdsWithInvalidOrder.includes(BigInt(order.itemIds[0]))) {
return { ...order, invalidated: true };
}
return order;
});
}),
).then((res) => res.flat());

return {
data: ordersAfterCheckingValidity,
count: count ? count : ordersAfterCheckingValidity?.length,
};
} catch (e) {
throw new Error(
`[ContractResolver::orders] Error fetching orders: ${(e as Error).message}`,
Expand Down
63 changes: 63 additions & 0 deletions src/services/SupabaseDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { applyFilters } from "../graphql/schemas/utils/filters.js";
import { applySorting } from "../graphql/schemas/utils/sorting.js";
import { applyPagination } from "../graphql/schemas/utils/pagination.js";
import { GetOrdersArgs } from "../graphql/schemas/args/orderArgs.js";
import {
HypercertExchangeClient,
OrderValidatorCode,
} from "@hypercerts-org/marketplace-sdk";
import { ethers } from "ethers";
import { getRpcUrl } from "../utils/getRpcUrl.js";

export class SupabaseDataService {
private supabaseData: SupabaseClient<DataDatabase>;
Expand Down Expand Up @@ -124,4 +130,61 @@ export class SupabaseDataService {

return query;
}

async validateOrdersByTokenIds({
tokenIds,
chainId,
}: {
tokenIds: string[];
chainId: number;
}) {
console.log("[marketplace-api] Validating orders by token IDs", tokenIds);
const ordersToUpdate: {
id: string;
invalidated: boolean;
validator_codes: OrderValidatorCode[];
}[] = [];
for (const tokenId of tokenIds) {
// Fetch all orders for token ID from database
const { data: matchingOrders, error } = await this.getOrdersByTokenId({
tokenId,
chainId,
});

if (error) {
throw new Error(
`[SupabaseDataService::validateOrderByTokenId] Error fetching orders: ${error.message}`,
);
}

if (!matchingOrders) {
console.warn(
`[SupabaseDataService::validateOrderByTokenId] No orders found for tokenId: ${tokenId}`,
);
continue;
}

// Validate orders using logic in the SDK
const hec = new HypercertExchangeClient(
chainId,
// @ts-expect-error Typing issue with provider
new ethers.JsonRpcProvider(getRpcUrl(chainId)),
);
const validationResults = await hec.checkOrdersValidity(matchingOrders);

// Determine which orders to update in DB, and update them
ordersToUpdate.push(
...validationResults
.filter((x) => !x.valid)
.map(({ validatorCodes, id }) => ({
id,
invalidated: true,
validator_codes: validatorCodes,
})),
);
}
console.log("[marketplace-api] Invalidating orders", ordersToUpdate);
await this.updateOrders(ordersToUpdate);
return ordersToUpdate;
}
}
6 changes: 3 additions & 3 deletions src/utils/getRpcUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const alchemyUrl = (chainId: number) => {
case 11155111:
return `https://eth-sepolia.g.alchemy.com/v2/${alchemyApiKey}`;
default:
throw new Error(`Unsupported chain ID: ${chainId}`);
throw new Error(`Unsupported chain ID for alchemy: ${chainId}`);
}
};

Expand All @@ -30,7 +30,7 @@ const infuraUrl = (chainId: number) => {
case 11155111:
return `https://sepolia.infura.io/v3/${infuraApiKey}`;
default:
throw new Error(`Unsupported chain ID: ${chainId}`);
throw new Error(`Unsupported chain ID for infura: ${chainId}`);
}
};

Expand All @@ -47,7 +47,7 @@ const drpcUrl = (chainId: number) => {
case 11155111:
return;
default:
throw new Error(`Unsupported chain ID: ${chainId}`);
throw new Error(`Unsupported chain ID for drpc: ${chainId}`);
}
};

Expand Down

0 comments on commit d676d47

Please sign in to comment.