Skip to content
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

Push Orders filter updates to PRD #199

Merged
merged 4 commits into from
Dec 4, 2024
Merged
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
2 changes: 2 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -813,8 +813,10 @@ input OrderSortOptions {

input OrderWhereInput {
chainId: BigIntSearchOptions
currency: StringSearchOptions
hypercert_id: StringSearchOptions
id: IdSearchOptions
invalidated: BooleanSearchOptions
signer: StringSearchOptions
}

Expand Down
35 changes: 13 additions & 22 deletions src/controllers/MarketplaceController.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import {
addressesByNetwork,
HypercertExchangeClient,
utils,
} from "@hypercerts-org/marketplace-sdk";
import { ethers, verifyTypedData } from "ethers";
import {
Body,
Controller,
Expand All @@ -8,21 +14,15 @@ import {
SuccessResponse,
Tags,
} from "tsoa";
import { ApiResponse } from "../types/api.js";
import {
addressesByNetwork,
HypercertExchangeClient,
utils,
} from "@hypercerts-org/marketplace-sdk";
import { ethers, verifyTypedData } from "ethers";
import { z } from "zod";
import { ApiResponse } from "../types/api.js";

import { SupabaseDataService } from "../services/SupabaseDataService.js";
import { isAddress, verifyMessage } from "viem";
import { isParsableToBigInt } from "../utils/isParsableToBigInt.js";
import { SupabaseDataService } from "../services/SupabaseDataService.js";
import { getFractionsById } from "../utils/getFractionsById.js";
import { getHypercertTokenId } from "../utils/tokenIds.js";
import { getRpcUrl } from "../utils/getRpcUrl.js";
import { isParsableToBigInt } from "../utils/isParsableToBigInt.js";
import { getHypercertTokenId } from "../utils/tokenIds.js";

export interface CreateOrderRequest {
signature: string;
Expand Down Expand Up @@ -435,15 +435,15 @@ export class MarketplaceController extends Controller {
const { orderId, signature } = parsedQuery.data;

const supabase = new SupabaseDataService();
const orderRes = await supabase.getOrders({
const orders = await supabase.getOrders({
where: {
id: {
eq: orderId,
},
},
});

if (!orderRes.data?.length) {
if (!orders.data?.length) {
this.setStatus(404);
return {
success: false,
Expand All @@ -452,16 +452,7 @@ export class MarketplaceController extends Controller {
};
}

if (orderRes.error) {
this.setStatus(500);
return {
success: false,
message: "Could not fetch order",
data: null,
};
}

const signerAddress = orderRes.data[0].signer;
const signerAddress = orders.data[0].signer;

const signatureCorrect = await verifyMessage({
message: `Delete listing ${orderId}`,
Expand Down
5 changes: 5 additions & 0 deletions src/graphql/schemas/inputs/orderInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
IdSearchOptions,
BigIntSearchOptions,
StringSearchOptions,
BooleanSearchOptions,
} from "./searchOptions.js";
import { Order } from "../typeDefs/orderTypeDefs.js";

Expand All @@ -17,4 +18,8 @@ export class BasicOrderWhereInput implements WhereOptions<Order> {
signer?: StringSearchOptions | null;
@Field(() => StringSearchOptions, { nullable: true })
hypercert_id?: StringSearchOptions | null;
@Field(() => BooleanSearchOptions, { nullable: true })
invalidated?: BooleanSearchOptions | null;
@Field(() => StringSearchOptions, { nullable: true })
currency?: StringSearchOptions | null;
}
27 changes: 27 additions & 0 deletions src/graphql/schemas/resolvers/baseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,33 @@ export function createBaseResolver<T extends ClassType>(
}
}

getOrders(args: GetOrdersArgs, single: boolean = false) {
console.debug(`[${entityFieldName}Resolver::getOrders] Fetching orders`);

try {
const queries = this.supabaseDataService.getOrders(args);
if (single) {
return queries.data.executeTakeFirst();
}

return this.supabaseDataService.db
.transaction()
.execute(async (transaction) => {
const dataRes = await transaction.executeQuery(queries.data);
const countRes = await transaction.executeQuery(queries.count);
return {
data: dataRes.rows,
count: countRes.rows[0].count,
};
});
} catch (e) {
const error = e as Error;
throw new Error(
`[${entityFieldName}Resolver::getOrders] Error fetching orders: ${error.message}`,
);
}
}

parseAttestation(item: { [K in keyof T]: T[K] }) {
const decodedData = item?.data;
// TODO cleaner handling of bigints in created attestations
Expand Down
36 changes: 11 additions & 25 deletions src/graphql/schemas/resolvers/hypercertResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class HypercertResolver extends HypercertBaseResolver {
};

try {
console.log("Getting fractions for hypercert", hypercert.hypercert_id);
const { data: fractionsRes } = await this.getFractions({
where: { hypercert_id: { eq: hypercert.hypercert_id } },
});
Expand All @@ -105,39 +104,21 @@ class HypercertResolver extends HypercertBaseResolver {
return defaultValue;
}

console.log(
`[HypercertResolver::orders] Fetching orders for ${hypercert.hypercert_id}`,
);

const ordersRes = await this.supabaseDataService.getOrders({
const orders = await this.getOrders({
where: { hypercert_id: { eq: hypercert.hypercert_id } },
});

if (!ordersRes) {
if (!orders) {
console.warn(
`[HypercertResolver::orders] Error fetching orders for ${hypercert.hypercert_id}`,
ordersRes,
);
return defaultValue;
}

const {
data: ordersData,
error: ordersError,
count: ordersCount,
} = ordersRes;

if (ordersError) {
console.error(
`[HypercertResolver::orders] Error fetching orders for ${hypercert.hypercert_id}: `,
ordersError,
orders,
);
return defaultValue;
}

const validOrders = ordersData.filter((order) => !order.invalidated);
const { data: ordersData, count: ordersCount } = orders;

const ordersByFraction = _.groupBy(validOrders, (order) =>
const ordersByFraction = _.groupBy(ordersData, (order) =>
order.itemIds[0].toString(),
);

Expand All @@ -149,10 +130,15 @@ class HypercertResolver extends HypercertBaseResolver {
priceInUSD: string;
pricePerPercentInUSD: string;
})[] = [];

const activeOrders = ordersData.filter((order) => !order.invalidated);
const activeOrdersByFraction = _.groupBy(activeOrders, (order) =>
order.itemIds[0].toString(),
);
// For each fraction, find all orders and find the max units for sale for that fraction
const totalUnitsForSale = (
await Promise.all(
Object.keys(ordersByFraction).map(async (tokenId) => {
Object.keys(activeOrdersByFraction).map(async (tokenId) => {
const fractionId = `${chainId}-${contractAddress}-${tokenId}`;
const fraction = fractionsRes.find(
(fraction) => fraction.fraction_id === fractionId,
Expand Down
72 changes: 12 additions & 60 deletions src/graphql/schemas/resolvers/orderResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import { Order } from "../typeDefs/orderTypeDefs.js";
import { GetOrdersArgs } from "../args/orderArgs.js";
import { getHypercertTokenId } from "../../../utils/tokenIds.js";
import { getAddress } from "viem";
import { HypercertExchangeClient } from "@hypercerts-org/marketplace-sdk";
import { ethers } from "ethers";
import { getRpcUrl } from "../../../utils/getRpcUrl.js";
import { addPriceInUsdToOrder } from "../../../utils/addPriceInUSDToOrder.js";
import _ from "lodash";
import { createBaseResolver, DataResponse } from "./baseTypes.js";
Expand All @@ -25,29 +22,20 @@ const OrderBaseResolver = createBaseResolver("order");
@Resolver(() => Order)
class OrderResolver extends OrderBaseResolver {
@Query(() => GetOrdersResponse)
async orders(@Args() args: GetOrdersArgs) {
async orders(@Args() args: GetOrdersArgs, single: boolean = false) {
try {
const res = await this.supabaseDataService.getOrders(args);
const ordersRes = await this.getOrders(args, single);

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

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

const groupedOrders = orders.reduce(
(acc, order) => {
if (!acc[order.chainId]) {
acc[order.chainId] = [];
}
acc[order.chainId].push(order);
return acc;
},
{} as Record<string, (typeof orders)[number][]>,
);
const { data, count } = ordersRes;

const allHypercertIds = _.uniq(orders.map((order) => order.hypercert_id));
const allHypercertIds = _.uniq(data.map((order) => order.hypercert_id));
// TODO: Update this once array filters are available
const allHypercerts = await Promise.all(
allHypercertIds.map(async (hypercertId) => {
Expand All @@ -69,44 +57,8 @@ class OrderResolver extends OrderBaseResolver {
),
);

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.supabaseDataService.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());

const ordersWithPrices = await Promise.all(
orders.map(async (order) => {
data.map(async (order) => {
const hypercert = allHypercerts[order.hypercert_id.toLowerCase()];
if (!hypercert?.units) {
console.warn(
Expand All @@ -120,11 +72,11 @@ class OrderResolver extends OrderBaseResolver {

return {
data: ordersWithPrices,
count: count ? count : ordersAfterCheckingValidity?.length,
count: count ? count : ordersWithPrices?.length,
};
} catch (e) {
throw new Error(
`[ContractResolver::orders] Error fetching orders: ${(e as Error).message}`,
`[OrderResolver::orders] Error fetching orders: ${(e as Error).message}`,
);
}
}
Expand Down
41 changes: 23 additions & 18 deletions src/services/SupabaseDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,10 @@ export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase>
}

getOrders(args: GetOrdersArgs) {
let query = this.supabaseData.from("marketplace_orders").select("*");

const { where, sort, offset, first } = args;

query = applyFilters({ query, where });
query = applySorting({ query, sort });
query = applyPagination({ query, pagination: { first, offset } });

return query;
return {
data: this.handleGetData("marketplace_orders", args),
count: this.handleGetCount("marketplace_orders", args),
};
}

getOrdersByTokenId({
Expand Down Expand Up @@ -674,11 +669,14 @@ export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
>(tableName: T, args: BaseArgs<A>) {
switch (tableName) {
case "users":
return this.db.selectFrom("users").selectAll();
case "blueprints_with_admins":
case "blueprints":
return this.db.selectFrom("blueprints_with_admins").selectAll();
case "orders":
case "marketplace_orders":
return this.db.selectFrom("marketplace_orders").selectAll();
case "users":
return this.db.selectFrom("users").selectAll();
default:
throw new Error(`Table ${tableName.toString()} not found`);
}
Expand All @@ -691,21 +689,28 @@ export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
>(tableName: T, args: BaseArgs<A>) {
switch (tableName) {
case "users":
return this.db.selectFrom("users").select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "blueprints_with_admins":
case "blueprints":
return this.db
.selectFrom("blueprints_with_admins")
.select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "hyperboards":
return this.db.selectFrom("hyperboards").select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "blueprints_with_admins":
case "blueprints":
case "orders":
case "marketplace_orders":
return this.db
.selectFrom("blueprints_with_admins")
.selectFrom("marketplace_orders")
.select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "users":
return this.db.selectFrom("users").select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
default:
throw new Error(`Table ${tableName.toString()} not found`);
}
Expand Down
Loading