Skip to content

Commit

Permalink
Merge pull request #50 from QuantWealth/fix/batch-order
Browse files Browse the repository at this point in the history
Fix/batch order
  • Loading branch information
sanchaymittal authored Jun 18, 2024
2 parents 3bbf190 + e7175db commit 281db04
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 163 deletions.
4 changes: 2 additions & 2 deletions packages/adapters/orderbook-db/src/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async function executeOrder(orderId: string, hashes: string[]): Promise<IOrder |
{ id: orderId },
{
status: "E",
"timestamps.executed": Date.now(),
"timestamps.executed": new Date(),
hashes: hashes
}
).exec();
Expand Down Expand Up @@ -90,7 +90,7 @@ async function cancelOrder(orderId: string): Promise<IOrder | null> {
{ id: orderId },
{
status: "C",
"timestamps.cancelled": Date.now()
"timestamps.cancelled": new Date()
}
).exec();

Expand Down
8 changes: 4 additions & 4 deletions packages/adapters/orderbook-db/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ interface IOrder {
signer: string; // signer address
wallet: string; // SCW wallet address
timestamps: {
placed: number;
executed?: number;
cancelled?: number;
placed: Date;
executed?: Date;
cancelled?: Date;
};
dapps: string[];
distribution?: boolean;
Expand Down Expand Up @@ -57,7 +57,7 @@ OrderSchema.pre("save", function (next) {
this.status = "P";
// TODO: Include once utils are ready:
// this.id = keccak256(`${this.timestamp}-${this.wallet}`);
this.timestamps.placed = Date.now();
this.timestamps.placed = new Date();
}
next();
});
Expand Down
251 changes: 127 additions & 124 deletions packages/agents/nova/src/core/resources/orderbook/orderbook.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class OrderbookService {
this.logger.log('Creating approve transaction:', query);
const { assetAddress, walletAddress, amount } = query;
const qwManagerAddress = Object.values(this.config.chains)[0]
.contractAddresses.QWManager.address;
.contractAddresses['QWManager'].address;

const txData = approve({
contractAddress: assetAddress,
Expand Down Expand Up @@ -262,7 +262,7 @@ export class OrderbookService {
throw new Error('Invalid strategy type');
}

const currentTimestamp = Date.now();
const currentTimestamp = new Date();

this.orderModel.create({
id: uuidv4(),
Expand All @@ -285,136 +285,139 @@ export class OrderbookService {
* @returns A promise that resolves when all transactions have been executed.
*/
async handleBatchExecuteOrders() {
const rpc = Object.values(this.config.chains)[0].providers[0];
const chainId = Object.keys(this.config.chains)[0];
const qwManagerAddress =
this.config.chains[0].contractAddresses.QWManager.address;
const erc20TokenAddress = USDT_SEPOLIA;
const gelatoApiKey = this.config.gelatoApiKey;
const qwScwAddress = await getSCW({ rpc, address: this.signer.address });
// const signer = this.signer;

// Get the start and end dates for a period of 1 month into the past to now.
const start = new Date();
start.setMonth(start.getMonth() - 1);
const end = new Date();
end.setSeconds(end.getSeconds() + 10);

// Get pending orders.
const pendingOrders: IOrder[] = await getOrders(start, end, 'P', false);
const provider = new ethers.JsonRpcProvider(rpc, chainId);

const receiveFundsRequests: MetaTransactionData[] = [];
let totalSum = BigInt(0);

for (const order of pendingOrders) {
// TODO: Orderbook IOrder schema should have token address(es).
// User is supplying multiple dapps, but we will combine the amounts and we are assuming the same token address for now.
const sum = order.amounts.reduce(
(acc, val) => acc + BigInt(val),
BigInt(0),
);

totalSum = BigInt(totalSum) + BigInt(sum);

// Derive the receive funds transaction request, push to batch.

const txReq = receiveFunds({
try {
const rpc = Object.values(this.config.chains)[0].providers[0];
const qwManagerAddress = Object.values(this.config.chains)[0]
.contractAddresses['QWManager'].address;
const erc20TokenAddress = USDT_SEPOLIA;
const gelatoApiKey = this.config.gelatoApiKey;
const qwScwAddress = '0xC22E238cbAb8B34Dc0014379E00B38D15D806115'; // Address of the safe
// const signer = this.signer;

// Get the start and end dates for a period of 1 month into the past to now.
const start = new Date();
start.setMonth(start.getMonth() - 1);

const end = new Date();
end.setSeconds(end.getSeconds() + 10);

// Get pending orders.
const pendingOrders: IOrder[] = await getOrders(start, end, 'P');
const provider = new ethers.JsonRpcProvider(rpc);

const receiveFundsRequests: MetaTransactionData[] = [];
let totalSum = BigInt(0);

for (const order of pendingOrders) {
// TODO: Orderbook IOrder schema should have token address(es).
// User is supplying multiple dapps, but we will combine the amounts and we are assuming the same token address for now.
const sum = order.amounts.reduce(
(acc, val) => acc + BigInt(val),
BigInt(0),
);
totalSum = BigInt(totalSum) + BigInt(sum);

// Derive the receive funds transaction request, push to batch.

const txReq = receiveFunds({
contractAddress: qwManagerAddress,
provider,
user: order.wallet,
token: erc20TokenAddress,
amount: sum,
});

receiveFundsRequests.push({
to: String(await txReq.to!),
value: String(txReq.value!),
data: txReq.data,
});
}

// target array should be present at orders
const qwUniswapAddress = Object.values(this.config.chains)[0]
.contractAddresses['QWUniswapV3Stable'].address;
const _target = [qwUniswapAddress]; // Uniswap V3 child contract address
const target = _target; /// Addresses of the child contracts
const tokens = USDT_SEPOLIA; /// Token addresses for each child contract
const amount = totalSum;
// execute request preparation

const callData = ['0x']; /// Calldata for each child contract
const _executeRequests: ethers.TransactionRequest = execute({
contractAddress: qwManagerAddress,
provider,
user: order.signer,
token: erc20TokenAddress,
amount: sum,
});

receiveFundsRequests.push({
to: String(await txReq.to!),
value: String(txReq.value!),
data: txReq.data,
target,
callData,
tokens,
amount,
});
}

// target array should be present at orders
const qwUniswapAddress = Object.values(this.config.chains)[0]
.contractAddresses['QwUniswapV3Stable'];
const _target = [qwUniswapAddress]; // Uniswap V3 child contract address
const target = _target; /// Addresses of the child contracts
const tokens = [USDT_SEPOLIA]; /// Token addresses for each child contract
const amount = [totalSum];
// execute request preparation

const callData = ['']; /// Calldata for each child contract

const _executeRequests: ethers.TransactionRequest = execute({
contractAddress: qwManagerAddress,
provider,
target,
callData,
tokens,
amount,
});

const executeRequests = {
to: String(await _executeRequests.to!),
value: String(_executeRequests.value!),
data: _executeRequests.data,
};

const relayRequests: MetaTransactionData[] =
receiveFundsRequests.concat(executeRequests);

// Init the QW safe for signing/wrapping relayed batch transactions below.
const safe = await initQW({
rpc,
address: qwScwAddress,
signer: this.signer.privateKey,
});

// First, we relay receiveFunds.
{
// Create the MetaTransactionData.
const transactions = await createTransactions({
transactions: relayRequests,
});

// Create the gelato relay pack using an initialized SCW.
const gelatoRelayPack = await createGelatoRelayPack({
gelatoApiKey,
protocolKit: safe,
});

// This will derive from MetaTransactionData and the gelato relay pack a SafeTransaction.
let safeTransaction = await relayTransaction({
transactions,
gelatoRelayPack,
});
const executeRequests = {
to: String(await _executeRequests.to!),
value: String(_executeRequests.value!),
data: _executeRequests.data,
};

// Use protocol kit to sign the safe transaction, enabling it to be relayed.
safeTransaction = await signSafeTransaction({
protocolKit: safe,
safeTransaction,
});
const relayRequests: MetaTransactionData[] =
receiveFundsRequests.concat(executeRequests);

// Execute the relay transaction using gelato.
await executeRelayTransaction({
gelatoRelayPack,
signedSafeTransaction: safeTransaction,
// Init the QW safe for signing/wrapping relayed batch transactions below.
const qwSafe = await getDeployedSCW({
rpc: rpc,
safeAddress: qwScwAddress,
signer: this.signer.privateKey,
});

// update the status of the orderbook.
// TODO: update order status from pending to executed, optionally record hashes of transactions in order.hashes?
// Order schema should really record the gelato relay ID in this case...
await Promise.all(
pendingOrders.map((order) =>
OrderModel.updateOne(
{ id: order.id },
{
status: 'E',
'timestamps.executed': Date.now(),
},
).exec(),
),
);
// First, we relay receiveFunds.
{
// Create the MetaTransactionData.
const transactions = await createTransactions({
transactions: relayRequests,
});

// Create the gelato relay pack using an initialized SCW.
const gelatoRelayPack = await createGelatoRelayPack({
gelatoApiKey,
protocolKit: qwSafe,
});

// This will derive from MetaTransactionData and the gelato relay pack a SafeTransaction.
let safeTransaction = await relayTransaction({
transactions,
gelatoRelayPack,
});

// Use protocol kit to sign the safe transaction, enabling it to be relayed.
safeTransaction = await signSafeTransaction({
protocolKit: qwSafe,
safeTransaction,
});

// Execute the relay transaction using gelato.
await executeRelayTransaction({
gelatoRelayPack,
signedSafeTransaction: safeTransaction,
});

// update the status of the orderbook.
// TODO: update order status from pending to executed, optionally record hashes of transactions in order.hashes?
// Order schema should really record the gelato relay ID in this case...
await Promise.all(
pendingOrders.map((order) =>
OrderModel.updateOne(
{ id: order.id },
{
status: 'E',
'timestamps.executed': Date.now(),
},
).exec(),
),
);
}
} catch (err) {
console.log(err);
this.logger.error('Error batch executing tx:', err);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';

export class UserSignatureQueryDto {
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The private key of the user',
})
privateKey: string;

@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'Typed data',
})
typedData: string;
}
22 changes: 21 additions & 1 deletion packages/agents/nova/src/core/resources/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ import { UserBalanceQueryDto } from './dto/user-balance-query.dto';
import { UserBalanceResponseDto } from './dto/user-balance-response.dto';
import { UserDataQueryDto } from './dto/user-data-query.dto';
import { UserInitBodyDto } from './dto/user-init-body.dto';
import { UserInitResponseDto, UserResponseDto } from './dto/user-init-response.dto';
import {
UserInitResponseDto,
UserResponseDto,
} from './dto/user-init-response.dto';
import { UserSendTxBodyDto } from './dto/user-send-tx-body.dto';
import { UserService } from './user.service';
import { UserSignatureQueryDto } from './dto/user-signature-query.dto';

@ApiTags('user')
@Controller('user')
Expand Down Expand Up @@ -88,4 +92,20 @@ export class UserController {
async sendTx(@Body() body: UserSendTxBodyDto): Promise<void> {
return await this.userService.sendTx(body);
}

/**
* Retrieves user sign tx
* @param {UserSignatureQueryDto} query - query params containing user wallet address and the way returned data should be arranged
* @returns userTokenBalance array
*/
@HttpCode(HttpStatus.OK)
@ApiResponse({
status: HttpStatus.OK,
description: 'Get Signature',
type: UserSignatureQueryDto,
})
@Get('signature')
getSignature(@Query() query: UserSignatureQueryDto): Promise<string> {
return this.userService.getSignature(query);
}
}
Loading

0 comments on commit 281db04

Please sign in to comment.