diff --git a/market-maker/configs/futures.dev.yml b/market-maker/configs/futures.dev.yml index 48d23e8..2d93e7f 100644 --- a/market-maker/configs/futures.dev.yml +++ b/market-maker/configs/futures.dev.yml @@ -88,7 +88,6 @@ oracle: health: port: ${MAKER_HEALTH_PORT:-3001} -readBatchSize: 3 +readBatchSize: 30 # Per-operation batch sizes for writes (futures: closeOrder limit / createOrders limit). -cancelBatchSize: 20 -createBatchSize: 10 +writeBatchSize: 20 diff --git a/market-maker/configs/futures.local.yml b/market-maker/configs/futures.local.yml index 6d1d90e..012c4a4 100644 --- a/market-maker/configs/futures.local.yml +++ b/market-maker/configs/futures.local.yml @@ -75,7 +75,6 @@ oracle: health: port: 3001 -readBatchSize: 10 +readBatchSize: 100 # Per-operation batch sizes for writes. -cancelBatchSize: 20 -createBatchSize: 10 +writeBatchSize: 20 diff --git a/market-maker/configs/futures.prd.yml b/market-maker/configs/futures.prd.yml index 7278fb2..f1c6019 100644 --- a/market-maker/configs/futures.prd.yml +++ b/market-maker/configs/futures.prd.yml @@ -81,7 +81,6 @@ oracle: health: port: ${MAKER_HEALTH_PORT:-3001} -readBatchSize: 10 +readBatchSize: 100 # Per-operation batch sizes for writes. -cancelBatchSize: 20 -createBatchSize: 10 +writeBatchSize: 20 diff --git a/market-maker/configs/futures.stg.yml b/market-maker/configs/futures.stg.yml index 281b474..24abac4 100644 --- a/market-maker/configs/futures.stg.yml +++ b/market-maker/configs/futures.stg.yml @@ -81,7 +81,6 @@ oracle: health: port: ${MAKER_HEALTH_PORT:-3001} -readBatchSize: 10 +readBatchSize: 100 # Per-operation batch sizes for writes. -cancelBatchSize: 20 -createBatchSize: 10 +writeBatchSize: 20 diff --git a/market-maker/configs/perps.dev.yml b/market-maker/configs/perps.dev.yml index 1b6cd06..4701e76 100644 --- a/market-maker/configs/perps.dev.yml +++ b/market-maker/configs/perps.dev.yml @@ -91,7 +91,6 @@ oracle: health: port: ${MAKER_HEALTH_PORT:-3002} -readBatchSize: 10 +readBatchSize: 100 # Per-operation batch sizes for writes (perps: individual cancelOrder / createOrder). -cancelBatchSize: 30 -createBatchSize: 30 +writeBatchSize: 20 diff --git a/market-maker/configs/perps.local.yml b/market-maker/configs/perps.local.yml index 614f074..e2bf88a 100644 --- a/market-maker/configs/perps.local.yml +++ b/market-maker/configs/perps.local.yml @@ -81,7 +81,6 @@ oracle: health: port: ${MAKER_HEALTH_PORT:-3001} -readBatchSize: 10 +readBatchSize: 100 # Per-operation batch sizes for writes. -cancelBatchSize: 30 -createBatchSize: 30 +writeBatchSize: 20 diff --git a/market-maker/configs/perps.prd.yml b/market-maker/configs/perps.prd.yml index 087132b..d34611d 100644 --- a/market-maker/configs/perps.prd.yml +++ b/market-maker/configs/perps.prd.yml @@ -83,7 +83,6 @@ oracle: health: port: ${MAKER_HEALTH_PORT:-3001} -readBatchSize: 10 +readBatchSize: 100 # Per-operation batch sizes for writes. -cancelBatchSize: 30 -createBatchSize: 30 +writeBatchSize: 20 diff --git a/market-maker/configs/perps.stg.yml b/market-maker/configs/perps.stg.yml index 483a726..7b17b9e 100644 --- a/market-maker/configs/perps.stg.yml +++ b/market-maker/configs/perps.stg.yml @@ -82,7 +82,6 @@ oracle: health: port: ${MAKER_HEALTH_PORT:-3001} -readBatchSize: 10 +readBatchSize: 100 # Per-operation batch sizes for writes. -cancelBatchSize: 30 -createBatchSize: 30 +writeBatchSize: 20 diff --git a/market-maker/schemas/futures.json b/market-maker/schemas/futures.json index aa9c03b..1cac2ab 100644 --- a/market-maker/schemas/futures.json +++ b/market-maker/schemas/futures.json @@ -22,8 +22,7 @@ "timing", "health", "readBatchSize", - "cancelBatchSize", - "createBatchSize" + "writeBatchSize" ], "properties": { "nodeEnv": { @@ -924,7 +923,7 @@ "description": "Maximum number of contract calls bundled into a single Multicall3 read. Calls are chunked transparently; lower values reduce RPC timeouts on busy providers at the cost of more round-trips.", "default": 10 }, - "cancelBatchSize": { + "writeBatchSize": { "anyOf": [ { "minimum": 1, @@ -936,23 +935,8 @@ "description": "Environment variable interpolation (resolved at startup)" } ], - "description": "Maximum closeOrder calls per cancellation batch. The adapter groups cancels into chunks of this size before sending.", + "description": "Maximum qty per write batch. The adapter groups cancels into chunks of this size before sending.", "default": 20 - }, - "createBatchSize": { - "anyOf": [ - { - "minimum": 1, - "type": "number" - }, - { - "type": "string", - "pattern": "^\\$\\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\\}$", - "description": "Environment variable interpolation (resolved at startup)" - } - ], - "description": "Maximum orders per createOrders call. The adapter packs creates into batches of this size.", - "default": 10 } } } diff --git a/market-maker/schemas/perps.json b/market-maker/schemas/perps.json index 9fc3ef9..a074c22 100644 --- a/market-maker/schemas/perps.json +++ b/market-maker/schemas/perps.json @@ -22,8 +22,7 @@ "timing", "health", "readBatchSize", - "cancelBatchSize", - "createBatchSize" + "writeBatchSize" ], "properties": { "nodeEnv": { @@ -901,7 +900,7 @@ "description": "Maximum number of contract calls bundled into a single Multicall3 read. Calls are chunked transparently; lower values reduce RPC timeouts.", "default": 10 }, - "cancelBatchSize": { + "writeBatchSize": { "anyOf": [ { "minimum": 1, @@ -913,23 +912,8 @@ "description": "Environment variable interpolation (resolved at startup)" } ], - "description": "Maximum cancelOrder calls per cancellation batch. Perps has no batch cancel — each cancel is one call.", - "default": 30 - }, - "createBatchSize": { - "anyOf": [ - { - "minimum": 1, - "type": "number" - }, - { - "type": "string", - "pattern": "^\\$\\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\\}$", - "description": "Environment variable interpolation (resolved at startup)" - } - ], - "description": "Maximum createOrder calls per creation batch. Perps has no batch create — each create is one call.", - "default": 30 + "description": "Maximum qty per write batch. The adapter groups cancels into chunks of this size before sending.", + "default": 20 } } } diff --git a/market-maker/src/adapters/futures/index.ts b/market-maker/src/adapters/futures/index.ts index fe6a272..a65c0a1 100644 --- a/market-maker/src/adapters/futures/index.ts +++ b/market-maker/src/adapters/futures/index.ts @@ -8,12 +8,8 @@ export interface CreateFuturesVenueOpts { wallet: WalletContext; address: `0x${string}`; multicall3Address?: `0x${string}`; - /** Max calls per Multicall3 read batch. Default 100. */ readBatchSize: number; - /** Max closeOrder calls per cancellation batch. Default 20. */ - cancelBatchSize: number; - /** Max orders per createOrders call. Default 10. */ - createBatchSize: number; + writeBatchSize: number; logger: pino.Logger; } diff --git a/market-maker/src/adapters/futures/instrument.ts b/market-maker/src/adapters/futures/instrument.ts index 919e7e8..0a6a984 100644 --- a/market-maker/src/adapters/futures/instrument.ts +++ b/market-maker/src/adapters/futures/instrument.ts @@ -19,9 +19,6 @@ import { FuturesOwnOrders } from "./ownOrders.ts"; const FUTURES_INSTRUMENT_ID = "futures"; -/** Maximum encoded calls per multicall write tx (safety net for block gas limit). */ -const WRITE_BATCH_SIZE = 50; - export class FuturesInstrumentAdapter implements InstrumentAdapter { readonly id = FUTURES_INSTRUMENT_ID; readonly venue: FuturesVenueAdapter; @@ -34,9 +31,8 @@ export class FuturesInstrumentAdapter implements InstrumentAdapter { constructor(venue: FuturesVenueAdapter, logger: pino.Logger) { this.venue = venue; - const batchSize = venue.readBatchSize; - this.book = new FuturesBook(this, batchSize); - this.ownOrders = new FuturesOwnOrders(venue, logger, batchSize); + this.book = new FuturesBook(this, venue.readBatchSize); + this.ownOrders = new FuturesOwnOrders(venue, logger, venue.readBatchSize); } async getIndexPrice(): Promise { @@ -147,18 +143,12 @@ export class FuturesInstrumentAdapter implements InstrumentAdapter { ): Promise { // 1. Build the ordered call list: cancels first, then creates. const calls = this.buildCallList(intent); - if (calls.length === 0) { - return { receipts: [], errors: [] }; - } if (intent.dryRun) { - const totalBatches = Math.ceil(calls.length / WRITE_BATCH_SIZE); logger.info( { cancels: intent.cancels.length, creates: intent.creates.length, - calls: calls.length, - batches: totalBatches, }, "DRY RUN: would send multicall batches", ); @@ -168,12 +158,11 @@ export class FuturesInstrumentAdapter implements InstrumentAdapter { // 2. Chunk into tx-sized groups and broadcast sequentially. const receipts: { gasUsed: bigint; effectiveGasPrice: bigint }[] = []; const errors: Error[] = []; - const totalBatches = Math.ceil(calls.length / WRITE_BATCH_SIZE); - - for (let offset = 0; offset < calls.length; offset += WRITE_BATCH_SIZE) { - const chunk = calls.slice(offset, offset + WRITE_BATCH_SIZE); - const batchNum = Math.floor(offset / WRITE_BATCH_SIZE) + 1; + const totalBatches = calls.length; + console.log(calls); + for (let batchNum = 0; batchNum < totalBatches; batchNum++) { + const chunk = calls[batchNum]; try { const hash = await this.venue.multicall(chunk, { maxFeePerGas: intent.maxFeePerGas, @@ -211,26 +200,33 @@ export class FuturesInstrumentAdapter implements InstrumentAdapter { } /** Build the ordered call list: cancels (individual closeOrder) then creates (createOrders). */ - private buildCallList(intent: ExecuteOrdersIntent): `0x${string}`[] { - const cancelSize = this.venue.cancelBatchSize; - const createSize = this.venue.createBatchSize; - const calls: `0x${string}`[] = []; - - // Cancels: chunk by cancelBatchSize, each = one closeOrder call. - for (let i = 0; i < intent.cancels.length; i += cancelSize) { - const batch = intent.cancels.slice(i, i + cancelSize); - for (const c of batch) { - calls.push(this.encodeCancel(c)); + private buildCallList(intent: ExecuteOrdersIntent): `0x${string}`[][] { + const batchSize = this.venue.writeBatchSize; + + let batchN = 0; + let qtyCount = 0; // we limit batch by qty count, since gas cost of one cancel approx eq one create order qty=1 + const batch: `0x${string}`[][] = []; + function addTx(tx: `0x${string}`, qty: number) { + if (!batch[batchN]) { + batch[batchN] = new Array(); } + batch[batchN].push(tx); + qtyCount += qty; + if (batch[batchN].length >= batchSize) { + batchN++; + qtyCount = 0; + } + } + + for (const c of intent.cancels) { + addTx(this.encodeCancel(c), 1); } - // Creates: chunk by createBatchSize, each chunk = one createOrders call. - for (let i = 0; i < intent.creates.length; i += createSize) { - const batch = intent.creates.slice(i, i + createSize); - calls.push(this.encodeCreateOrders(batch)); + for (const c of intent.creates) { + addTx(this.encodeCreate(c), Number(c.size)); } - return calls; + return batch; } /** Encode a batch of creates via the `createOrders` contract function. */ diff --git a/market-maker/src/adapters/futures/venue.ts b/market-maker/src/adapters/futures/venue.ts index 02022f8..5bc3795 100644 --- a/market-maker/src/adapters/futures/venue.ts +++ b/market-maker/src/adapters/futures/venue.ts @@ -28,9 +28,7 @@ export interface FuturesVenueOptions { /** Max calls per Multicall3 read batch. Default 100. */ readBatchSize: number; /** Max closeOrder calls per cancellation batch. Default 20. */ - cancelBatchSize: number; - /** Max orders per createOrders call. Default 10. */ - createBatchSize: number; + writeBatchSize: number; logger: pino.Logger; } @@ -61,8 +59,7 @@ export class FuturesVenueAdapter implements VenueAdapter { private readonly logger: pino.Logger; private readonly multicall3Address: `0x${string}`; readonly readBatchSize: number; - readonly cancelBatchSize: number; - readonly createBatchSize: number; + readonly writeBatchSize: number; private instrumentSingleton: FuturesInstrumentAdapter | null = null; private vaultAddressCache: `0x${string}` | null = null; @@ -87,8 +84,7 @@ export class FuturesVenueAdapter implements VenueAdapter { throw new Error(`chain ${this.chain.name} has no multicall3 address`); this.multicall3Address = mc3; this.readBatchSize = opts.readBatchSize; - this.cancelBatchSize = opts.cancelBatchSize; - this.createBatchSize = opts.createBatchSize; + this.writeBatchSize = opts.writeBatchSize; this.events = new FuturesVenueEvents(this.publicClient, this.address); this.account = new FuturesCollateralAccount(this); diff --git a/market-maker/src/apps/futures/config.ts b/market-maker/src/apps/futures/config.ts index 45de014..d497e77 100644 --- a/market-maker/src/apps/futures/config.ts +++ b/market-maker/src/apps/futures/config.ts @@ -168,20 +168,13 @@ export const futuresRootSchema = Type.Object( "Calls are chunked transparently; lower values reduce RPC timeouts on busy providers " + "at the cost of more round-trips.", }), - cancelBatchSize: Type.Number({ + writeBatchSize: Type.Number({ minimum: 1, default: 20, description: - "Maximum closeOrder calls per cancellation batch. " + + "Maximum qty per write batch. " + "The adapter groups cancels into chunks of this size before sending.", }), - createBatchSize: Type.Number({ - minimum: 1, - default: 10, - description: - "Maximum orders per createOrders call. " + - "The adapter packs creates into batches of this size.", - }), }, { ...Closed, description: "Titan Market Maker — Futures app config." }, ); diff --git a/market-maker/src/apps/futures/main.ts b/market-maker/src/apps/futures/main.ts index b0218b9..967cde7 100644 --- a/market-maker/src/apps/futures/main.ts +++ b/market-maker/src/apps/futures/main.ts @@ -46,8 +46,7 @@ async function main(): Promise { wallet, address: config.venue.address, readBatchSize: config.readBatchSize, - cancelBatchSize: config.cancelBatchSize, - createBatchSize: config.createBatchSize, + writeBatchSize: config.writeBatchSize, logger, }); const instrument = await venue.getInstrument(); diff --git a/market-maker/src/apps/perps/config.ts b/market-maker/src/apps/perps/config.ts index 3777ac6..4957fc5 100644 --- a/market-maker/src/apps/perps/config.ts +++ b/market-maker/src/apps/perps/config.ts @@ -155,21 +155,12 @@ export const perpsRootSchema = Type.Object( "Maximum number of contract calls bundled into a single Multicall3 read. " + "Calls are chunked transparently; lower values reduce RPC timeouts.", }), - - cancelBatchSize: Type.Number({ - minimum: 1, - default: 30, - description: - "Maximum cancelOrder calls per cancellation batch. " + - "Perps has no batch cancel — each cancel is one call.", - }), - - createBatchSize: Type.Number({ + writeBatchSize: Type.Number({ minimum: 1, - default: 30, + default: 20, description: - "Maximum createOrder calls per creation batch. " + - "Perps has no batch create — each create is one call.", + "Maximum qty per write batch. " + + "The adapter groups cancels into chunks of this size before sending.", }), }, { ...Closed, description: "Titan Market Maker — Perps app config." }, diff --git a/market-maker/src/apps/perps/main.ts b/market-maker/src/apps/perps/main.ts index 2c6c64d..89a2950 100644 --- a/market-maker/src/apps/perps/main.ts +++ b/market-maker/src/apps/perps/main.ts @@ -46,8 +46,8 @@ async function main(): Promise { wallet, address: config.venue.address, readBatchSize: config.readBatchSize, - cancelBatchSize: config.cancelBatchSize, - createBatchSize: config.createBatchSize, + cancelBatchSize: config.writeBatchSize, + createBatchSize: config.writeBatchSize, logger, }); const instrument = await venue.getInstrument();