Skip to content

Commit

Permalink
Merge pull request #43 from QuantWealth/nova-orderbook
Browse files Browse the repository at this point in the history
Nova orderbook
  • Loading branch information
sanchaymittal authored Jun 1, 2024
2 parents f74a301 + 61f7c0e commit 135bd46
Show file tree
Hide file tree
Showing 20 changed files with 748 additions and 99 deletions.
48 changes: 26 additions & 22 deletions packages/adapters/orderbook-db/src/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,27 @@ import { IOrder, IUser, OrderModel, UserModel } from "./schema";
* @param userWallet - The wallet address of the user to fetch orders for.
* @returns A promise resolving to an array of orders linked to the given wallet address.
*/
function getUserOrders(userWallet: string) {
return OrderModel.find({ wallet: userWallet });
async function getUserOrders(userWallet: string): Promise<IOrder[]> {
return await OrderModel.find({ wallet: userWallet }).exec();
}

/**
* Retrieves all orders associated with a specific signer.
* @param userSigner - The signer identifier to fetch orders for.
* @returns A promise resolving to an array of orders linked to the given signer.
*/
function getUserOrdersBySigner(userSigner: string) {
return OrderModel.find({ signer: userSigner });
async function getUserOrdersBySigner(userSigner: string): Promise<IOrder[]> {
return await OrderModel.find({ signer: userSigner }).exec();
}

/**
* Submits a new order to the database.
* @param orderData - The order data to be saved.
* @returns A promise that resolves with the saved order document.
*/
function submitOrder(orderData: IOrder) {
async function submitOrder(orderData: IOrder): Promise<IOrder> {
const order = new OrderModel(orderData);
return order.save();
return await order.save();
}

/**
Expand All @@ -34,21 +34,23 @@ function submitOrder(orderData: IOrder) {
* @param hashes - The array of transaction hashes related to the execution.
* @returns A promise resolving to the result of the update operation.
*/
async function executeOrder(orderId: string, hashes: string[]) {
async function executeOrder(orderId: string, hashes: string[]): Promise<IOrder | null> {
// Ensure the order exists.
const order = await OrderModel.findOne({ id: orderId });
const order = await OrderModel.findOne({ id: orderId }).exec();
if (!order) {
throw new Error("Order does not exist.");
}

return OrderModel.updateOne(
await OrderModel.updateOne(
{ id: orderId },
{
status: "E",
"timestamps.executed": Date.now(),
hashes: hashes
}
);
).exec();

return await OrderModel.findOne({ id: orderId }).exec();
}

/**
Expand All @@ -57,32 +59,34 @@ async function executeOrder(orderId: string, hashes: string[]) {
* @returns A promise resolving to the result of the update operation.
* @throws Error if the order is already executed.
*/
async function cancelOrder(orderId: string) {
async function cancelOrder(orderId: string): Promise<IOrder | null> {
// Ensure the order exists and is not executed.
const order = await OrderModel.findOne({ id: orderId });
const order = await OrderModel.findOne({ id: orderId }).exec();
if (!order) {
throw new Error("Order does not exist.");
}
if (order.status === "E") {
throw new Error("Cannot cancel executed order.");
}

return OrderModel.updateOne(
await OrderModel.updateOne(
{ id: orderId },
{
status: "C",
"timestamps.cancelled": Date.now()
}
);
).exec();

return await OrderModel.findOne({ id: orderId }).exec();
}

/**
* Retrieves a single order by its ID.
* @param orderId - The ID of the order to retrieve.
* @returns A promise resolving to the order document or null if not found.
*/
function getOrder(orderId: string) {
return OrderModel.findOne({ id: orderId });
async function getOrder(orderId: string): Promise<IOrder | null> {
return await OrderModel.findOne({ id: orderId }).exec();
}

/**
Expand All @@ -93,22 +97,22 @@ function getOrder(orderId: string) {
* @param distribution - Optional. The distribution flag of the orders to filter (true for credit, false for debit).
* @returns A promise resolving to an array of orders that match the given criteria.
*/
function getOrders(start: number, end: number, status?: string, distribution?: boolean) {
async function getOrders(start: number, end: number, status?: string, distribution?: boolean): Promise<IOrder[]> {
const query: any = {
"timestamps.placed": { $gte: start, $lte: end }
};
if (status) query.status = status;
if (distribution !== undefined) query.distribution = distribution;

return OrderModel.find(query);
return await OrderModel.find(query).exec();
}

function getUser(id: string) {
return UserModel.findOne({ id });
async function getUser(id: string): Promise<IUser | null> {
return await UserModel.findOne({ id }).exec();
}

function createUser(user: IUser) {
return UserModel.create(user);
async function createUser(user: IUser): Promise<IUser> {
return await UserModel.create(user);
}

export { getUserOrders, getUserOrdersBySigner, submitOrder, executeOrder, cancelOrder, getOrder, getOrders, getUser, createUser };
9 changes: 6 additions & 3 deletions packages/adapters/orderbook-db/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mongoose from "mongoose";

/// Order Schema
/// TODO: amounts, dapps, signatures, stratrgyType need to be in a object[]
interface IOrder {
id: string;
signer: string;
Expand All @@ -11,11 +12,12 @@ interface IOrder {
cancelled?: number;
};
dapps: string[];
distribution: boolean;
distribution?: boolean;
amounts: string[];
signatures: string[];
signatures?: string[];
status: "P" | "E" | "C"; // Pending, Executed, Canceled
hashes?: string[]; // Optional array of transaction hashes
strategyType: "FLEXI" | "FIXED";
}

const OrderSchema = new mongoose.Schema<IOrder>({
Expand All @@ -32,7 +34,8 @@ const OrderSchema = new mongoose.Schema<IOrder>({
amounts: [{ type: String, required: true }],
signatures: [{ type: String, required: true }],
status: { type: String, enum: ["P", "E", "C"], required: true },
hashes: [{ type: String }]
hashes: [{ type: String }],
strategyType: { type: String, enum: ["FLEXY", "FIXED"], required: true },
});

// Pre-save middleware to set the order ID.
Expand Down
25 changes: 25 additions & 0 deletions packages/agents/nova/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"logLevel": "debug",
"chains": {
"11155111": {
"providers": ["https://1rpc.io/sepolia"],
"contractAddresses": {
"QWManager": {
"contractName": "QWManager",
"address": "0x13A1A7e98E0235FCFB034452C13c1B7548fa1651"
},
"QWRegistry": {
"contractName": "QWRegistry",
"address": "0x57dbaE44a6a9F14294E4545cbaC5CBf9a9AD9b9a"
},
"QWAaveV3": {
"contractName": "QWAaveV3",
"address": "0xA52a11Be28bEA0d559370eCbE2f1CB8B1e8e3EcA"
}
}
}
},
"gelatoApiKey": "",
"privateKey": "",
"environment": "staging"
}
7 changes: 6 additions & 1 deletion packages/agents/nova/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@
"@nestjs/swagger": "^7.3.1",
"@qw/orderbook-db": "workspace:^",
"@qw/utils": "workspace:^",
"ajv": "^8.14.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dotenv": "^16.4.5",
"ethers": "^6.12.1",
"fs": "^0.0.1-security",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"uuid": "^9.0.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand All @@ -42,6 +46,7 @@
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@types/uuid": "^9",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
Expand Down
6 changes: 1 addition & 5 deletions packages/agents/nova/src/common/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
// TOKEN_BALANCE
export * from './token-balance';

//USER_SAVINGS
export * from './savings';

// contract addresses
export * from './address';
export * from './qw';
79 changes: 79 additions & 0 deletions packages/agents/nova/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as fs from 'fs';
import Ajv from 'ajv';
import { config as dotEnvConfig } from 'dotenv';
import { NovaConfig, NovaConfigSchema } from './schema';

// Load environment variables
dotEnvConfig();

export const getEnvConfig = (): NovaConfig => {
let configJson: Record<string, any> = {};
let configFile: any = {};

try {
configJson = JSON.parse(process.env.NOVA_CONFIG || '');
} catch (e: unknown) {
console.info(
'No NOVA_CONFIG exists; using config file and individual env vars.',
);
}

try {
const path = process.env.NOVA_CONFIG_FILE ?? 'config.json';
if (fs.existsSync(path)) {
const json = fs.readFileSync(path, { encoding: 'utf-8' });
configFile = JSON.parse(json);
}
} catch (e: unknown) {
console.error('Error reading config file!');
process.exit(1);
}

const novaConfig: NovaConfig = {
chains: process.env.NOVA_CHAIN_CONFIG
? JSON.parse(process.env.NOVA_CHAIN_CONFIG)
: configJson.chains || configFile.chains,
logLevel:
process.env.NOVA_LOG_LEVEL ||
configJson.logLevel ||
configFile.logLevel ||
'info',
environment:
process.env.NOVA_ENVIRONMENT ||
configJson.environment ||
configFile.environment ||
'staging',
gelatoApiKey:
process.env.GELATO_API_KEY ||
configJson.gelatoApiKey ||
configFile.gelatoApiKey,
privateKey:
process.env.PRIVATE_KEY || configJson.privateKey || configFile.privateKey,
};

const ajv = new Ajv();
const validate = ajv.compile(NovaConfigSchema);
const valid = validate(novaConfig);
if (!valid) {
throw new Error(
validate.errors
?.map((err: any) => JSON.stringify(err, null, 2))
.join(','),
);
}

return novaConfig;
};

export let novaConfig: NovaConfig | undefined;

/**
* Gets and validates the nova config from the environment.
* @returns The nova config with sensible defaults
*/
export const getConfig = async (): Promise<NovaConfig> => {
if (!novaConfig) {
novaConfig = getEnvConfig();
}
return novaConfig;
};
40 changes: 40 additions & 0 deletions packages/agents/nova/src/config/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Type, Static } from '@sinclair/typebox';
import { TAddress, TLogLevel } from '@qw/utils';

// Define the TChainConfig schema
export const TChainConfig = Type.Object({
providers: Type.Array(Type.String()),
contractAddresses: Type.Object({
QWManager: Type.Object({
contractName: Type.String(),
address: TAddress,
}),
QWRegistry: Type.Object({
contractName: Type.String(),
address: TAddress,
}),
QWAaveV3: Type.Object({
contractName: Type.String(),
address: TAddress,
}),
}),
});

// Define the TRPCConfig schema
export const TRPCConfig = Type.Object({
url: Type.String(),
});

// Define the NovaConfigSchema
export const NovaConfigSchema = Type.Object({
chains: Type.Record(Type.String(), TChainConfig),
logLevel: TLogLevel,
gelatoApiKey: Type.String(),
privateKey: Type.String(),
environment: Type.Union([
Type.Literal('staging'),
Type.Literal('production'),
]),
});

export type NovaConfig = Static<typeof NovaConfigSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, Length } from 'class-validator';

export enum Sort {
ASCENDING = 'asc',
DESCENDING = 'desc',
}

export class DefiApyQueryDto {
@IsString()
@IsNotEmpty()
@Length(42, 42)
@ApiProperty({
description: 'The asset address',
example: '0x0617b72940f105811F251967EE4fdD5E38f159d5',
})
assetAddress: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, Length } from 'class-validator';

export enum Sort {
ASCENDING = 'asc',
DESCENDING = 'desc',
}

export class DefiApyQueryDto {
@IsString()
@IsNotEmpty()
@Length(42, 42)
@ApiProperty({
description: 'The asset address',
example: '0x0617b72940f105811F251967EE4fdD5E38f159d5',
})
assetAddress: string;
}
Loading

0 comments on commit 135bd46

Please sign in to comment.