Skip to content
Closed
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
4 changes: 2 additions & 2 deletions keeper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"@hashpower/portfolio-margin": "github:Lumerin-protocol/collateral-margin#c34b4a360d6616d017b157a4a9e27e1a8e60079c&path:/portfolio-margin",
"amaro": "^1.1.9",
"collateral-margin-abi": "github:Lumerin-protocol/collateral-margin#c34b4a360d6616d017b157a4a9e27e1a8e60079c&path:/contracts/abi",
"derivatives-marketplace-abi": "github:Lumerin-protocol/derivatives-marketplace#f7e219f704646ab654a2a8d0286c0f477148e299&path:/contracts/abi",
"futures-marketplace-abi": "github:Lumerin-protocol/futures-marketplace#547ade13eb944b0e86bbef325749ed627cc886b4&path:/contracts/abi",
"derivatives-marketplace-abi": "github:Lumerin-protocol/derivatives-marketplace#8b7ed0f3572d0ea8039a11757b7c1b963be75535&path:/contracts/abi",
"futures-marketplace-abi": "github:Lumerin-protocol/futures-marketplace#05e97deb86a59e6be45fa0aa2ac10b8cb070c303&path:/contracts/abi",
"pino": "^10.3.1",
"viem": "^2.48.8"
},
Expand Down
20 changes: 10 additions & 10 deletions keeper/pnpm-lock.yaml

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

8 changes: 4 additions & 4 deletions keeper/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ export interface Config {
*/
sweepIntervalMs: number;
/**
* Maximum number of `removeOutdatedOrder` calls bundled into a
* single `Futures.multicall(bytes[])` tx. Each call is roughly
* Maximum number of ids passed to one
* `Futures.removeOutdatedOrders(bytes32[])` tx. Each cleanup is roughly
* 50-80k gas (one `_closeOrder` traversal); 50 keeps us well under
* Base's 30M block-gas limit (~4M worst case). Larger user-side
* fan-outs split across multiple sequential txs.
Expand Down Expand Up @@ -221,8 +221,8 @@ export interface Config {
*/
bootstrapUsers: readonly Address[];
/**
* Maximum number of `settlePosition` calls bundled into a single
* `Futures.multicall(bytes[])` transaction. Trades a single nonce per
* Maximum number of position pairs passed to a single
* `Futures.settlePositions(address[],uint256[])` transaction. Trades a single nonce per
* sweep tick (no replacement-underpriced races) for one bigger tx.
* Capped to keep gas usage well under the block limit — Base has 30M
* block gas, each `settlePosition` is roughly 200-300k gas, so 50 is
Expand Down
38 changes: 10 additions & 28 deletions keeper/src/delivery/coordinator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
BaseError,
ContractFunctionRevertedError,
encodeFunctionData,
getAddress,
type Address,
type Hex,
Expand Down Expand Up @@ -431,31 +430,14 @@ export class DeliveryCoordinator {
if (this.config.keeper.dryRun) {
this.logger.info(
{ batchSize: settleable.length },
"[dryRun] would call Futures.multicall(settlePosition × N)",
"[dryRun] would call Futures.settlePositions",
);
for (const pos of settleable) this.dropTracked(pos.user, pos.expirationAt);
return;
}

const calldatas: Hex[] = [];
const encodable: TrackedPosition[] = [];
for (const pos of settleable) {
try {
const data = encodeFunctionData({
abi: FuturesAbi,
functionName: "settlePosition",
args: [pos.user, pos.expirationAt],
});
calldatas.push(data);
encodable.push(pos);
} catch (err) {
this.logger.error(
{ err, user: pos.user, expirationAt: pos.expirationAt.toString() },
"delivery: encodeFunctionData threw — dropping malformed entry from batch",
);
}
}
if (calldatas.length === 0) return;
const users = settleable.map((pos) => pos.user);
const expirationAts = settleable.map((pos) => pos.expirationAt);

type WriteParams = Parameters<
typeof this.chain.walletClient.writeContract
Expand All @@ -466,8 +448,8 @@ export class DeliveryCoordinator {
this.chain.walletClient.writeContract({
address: this.config.futures.address,
abi: FuturesAbi,
functionName: "multicall",
args: [calldatas],
functionName: "settlePositions",
args: [users, expirationAts],
account: this.chain.account,
chain: this.chain.walletClient.chain ?? null,
} as unknown as WriteParams),
Expand All @@ -481,10 +463,10 @@ export class DeliveryCoordinator {
return;
}
this.logger.warn(
{ err, batchSize: encodable.length },
{ err, batchSize: settleable.length },
"delivery batch: write reverted — falling back to per-position retries",
);
for (const pos of encodable) {
for (const pos of settleable) {
try {
await this.attemptSettle(pos);
} catch (innerErr) {
Expand All @@ -505,13 +487,13 @@ export class DeliveryCoordinator {
{
hash,
blockNumber: receipt.blockNumber.toString(),
batchSize: encodable.length,
batchSize: settleable.length,
...formatGasCost(receipt, this.ethUsdFeed),
},
"delivery batch: multicall confirmed",
"delivery batch: settlePositions confirmed",
);

for (const pos of encodable) {
for (const pos of settleable) {
this.dropTracked(pos.user, pos.expirationAt);
}
}
Expand Down
24 changes: 12 additions & 12 deletions keeper/src/predict/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export async function readMMParams(
* Read everything needed to evaluate `mmSurplus(P)` for a single user as a
* function of price. Two RPC round-trips:
*
* 1. Bulk multicall: balance, both venues' `getRiskView` / `getOrderValues`,
* 1. Bulk multicall: balance, both venues' `getRiskView` / `getOrderAggregate`,
* the perp position, futures activeExpirationAts.
* 2. Per-expiry multicall: hydrate each aggregate via `getUserPosition`, plus
* its `settlementPrice` — an expiry that has settled but not yet been swept
Expand All @@ -75,7 +75,7 @@ export async function readMMParams(
*
* `getRiskView` carries the per-side order delta but reports fill loss only at the
* current mark, and the clamp makes that non-invertible once it reads zero — so the
* per-side limit-price totals come from `getOrderValues` and the predictor derives
* per-side limit-price totals come from `getOrderAggregate` and the predictor derives
* fill loss at whatever price it is evaluating. Pending funding also rides in
* `getRiskView`, replacing the separate `getPendingFunding` read.
*/
Expand All @@ -88,9 +88,9 @@ export async function readAccountSnapshot(
balance,
perpPosition,
perpRisk,
perpOrderValues,
perpOrderAggregate,
futuresRisk,
futuresOrderValues,
futuresOrderAggregate,
activeExpirationAts,
] = await chain.publicClient.multicall({
contracts: [
Expand All @@ -115,7 +115,7 @@ export async function readAccountSnapshot(
{
address: config.perps.address,
abi: HashPowerPerpsDEXAbi,
functionName: "getOrderValues" as const,
functionName: "getOrderAggregate" as const,
args: [user] as const,
},
{
Expand All @@ -127,7 +127,7 @@ export async function readAccountSnapshot(
{
address: config.futures.address,
abi: FuturesAbi,
functionName: "getOrderValues" as const,
functionName: "getOrderAggregate" as const,
args: [user] as const,
},
{
Expand Down Expand Up @@ -182,26 +182,26 @@ export async function readAccountSnapshot(
perp: {
netQty: perpPosition.netQuantity,
entryPrice: perpPosition.aggregatedEntryPrice,
orders: restingOrders(perpRisk, perpOrderValues),
orders: restingOrders(perpRisk, perpOrderAggregate),
// PME uses `max(0, pendingFunding)` — only what the user owes.
fundingOwed: funding > 0n ? funding : 0n,
},
futures: {
positions: futuresPositions,
orders: restingOrders(futuresRisk, futuresOrderValues),
orders: restingOrders(futuresRisk, futuresOrderAggregate),
},
};
}

/** Pair a venue's `getRiskView` deltas with its `getOrderValues` limit-price totals. */
/** Pair a venue's risk deltas with its cached order aggregate. */
function restingOrders(
risk: { buyOrderDelta: bigint; sellOrderDelta: bigint },
values: readonly [bigint, bigint],
aggregate: { buyValue: bigint; sellValue: bigint },
): AccountSnapshot["perp"]["orders"] {
return {
buyDelta: risk.buyOrderDelta,
sellDelta: risk.sellOrderDelta,
buyValue: values[0],
sellValue: values[1],
buyValue: aggregate.buyValue,
sellValue: aggregate.sellValue,
};
}
Loading
Loading