@depthhq/maker-sdk 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bot/bot.d.ts +5 -0
- package/dist/bot/policy.d.ts +5 -1
- package/dist/bot/store.d.ts +12 -0
- package/dist/client/client.d.ts +1 -0
- package/dist/client/types.d.ts +1 -0
- package/dist/index.js +152 -8
- package/dist/index.node.js +151 -8
- package/dist/node.js +212 -30
- package/dist/quote/quote.d.ts +6 -0
- package/dist/quote/replacement.d.ts +2 -0
- package/package.json +3 -3
package/dist/bot/bot.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class MakerBot {
|
|
|
15
15
|
readonly hedge?: HedgeEngine | undefined;
|
|
16
16
|
private state?;
|
|
17
17
|
private position?;
|
|
18
|
+
private verifiedPosition?;
|
|
18
19
|
private dirty;
|
|
19
20
|
private busy;
|
|
20
21
|
private stopping;
|
|
@@ -24,6 +25,7 @@ export declare class MakerBot {
|
|
|
24
25
|
private lastCleanup;
|
|
25
26
|
private lastFills;
|
|
26
27
|
private inventoryVersion?;
|
|
28
|
+
private reservationView?;
|
|
27
29
|
private constructor();
|
|
28
30
|
static create(options: {
|
|
29
31
|
maker: MakerClient;
|
|
@@ -41,6 +43,9 @@ export declare class MakerBot {
|
|
|
41
43
|
private cancel;
|
|
42
44
|
private replace;
|
|
43
45
|
tick(): Promise<BotSnapshot>;
|
|
46
|
+
private renew;
|
|
47
|
+
private quotePolicy;
|
|
48
|
+
private quoteRisk;
|
|
44
49
|
stop(): void;
|
|
45
50
|
close(): Promise<void>;
|
|
46
51
|
}
|
package/dist/bot/policy.d.ts
CHANGED
|
@@ -36,6 +36,10 @@ export type BotMarket = MarketState & {
|
|
|
36
36
|
fundingIntervalSeconds: number;
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
|
+
export declare function executionCosts(policy: BotPolicy, pricing?: Pick<BotPricing, "takerFeeBps" | "hourlyFundingBps">): {
|
|
40
|
+
bid: number;
|
|
41
|
+
ask: number;
|
|
42
|
+
};
|
|
39
43
|
export declare function validateBotPolicy(policy: BotPolicy): void;
|
|
40
44
|
export declare function quoteHealth(market: BotMarket, now: number): string | undefined;
|
|
41
|
-
export declare function botQuote(policy: BotPolicy, market: BotMarket, position: PositionSnapshot, nonce: bigint, now: number, pricing?: BotPricing): QuoteInput | undefined;
|
|
45
|
+
export declare function botQuote(policy: BotPolicy, market: BotMarket, position: PositionSnapshot, nonce: bigint, now: number, pricing?: BotPricing, reservationCredit?: bigint): QuoteInput | undefined;
|
package/dist/bot/store.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { QuoteInput } from "../client/types.js";
|
|
2
2
|
import type { HedgeState } from "../hedge/types.js";
|
|
3
|
+
export type QuoteRisk = {
|
|
4
|
+
quote: string;
|
|
5
|
+
notional: bigint;
|
|
6
|
+
price: bigint;
|
|
7
|
+
version: bigint;
|
|
8
|
+
balanceNonce?: string;
|
|
9
|
+
externalPosition: bigint;
|
|
10
|
+
hedgeNonce: number;
|
|
11
|
+
};
|
|
3
12
|
export type BotState = {
|
|
4
13
|
genesis: string;
|
|
5
14
|
maker: string;
|
|
@@ -13,11 +22,14 @@ export type BotState = {
|
|
|
13
22
|
version: bigint;
|
|
14
23
|
release: boolean;
|
|
15
24
|
};
|
|
25
|
+
inventoryPendingSince?: number;
|
|
26
|
+
quoteRisk?: QuoteRisk;
|
|
16
27
|
replacement?: {
|
|
17
28
|
prior: string;
|
|
18
29
|
nonce: bigint;
|
|
19
30
|
request?: QuoteInput;
|
|
20
31
|
referencePrice?: bigint;
|
|
32
|
+
risk?: QuoteRisk;
|
|
21
33
|
};
|
|
22
34
|
fills: Record<string, {
|
|
23
35
|
signature: string;
|
package/dist/client/client.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { GrantState, BackstopInput, MakerOptions, ResolvedMakerOptions, Quo
|
|
|
16
16
|
export declare function retryQuote<T>(action: () => Promise<T>, deadline: number): Promise<T>;
|
|
17
17
|
export declare class MakerClient {
|
|
18
18
|
readonly options: ResolvedMakerOptions;
|
|
19
|
+
private marketView?;
|
|
19
20
|
readonly hedges: MakerAccess;
|
|
20
21
|
readonly automation: MakerAutomation;
|
|
21
22
|
readonly provider: anchor.AnchorProvider;
|
package/dist/client/types.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -26396,7 +26396,8 @@ import {
|
|
|
26396
26396
|
quoteQueueAddress,
|
|
26397
26397
|
protocolAddress,
|
|
26398
26398
|
SOL_USD_MARKET_ID as SOL_USD_MARKET_ID2,
|
|
26399
|
-
quoteBounds as
|
|
26399
|
+
quoteBounds as quoteBounds4,
|
|
26400
|
+
quoteSizes as quoteSizes2
|
|
26400
26401
|
} from "@depthhq/bindings";
|
|
26401
26402
|
import { DepthView } from "@depthhq/sdk/views";
|
|
26402
26403
|
import { DepthRecovery } from "@depthhq/sdk/recovery";
|
|
@@ -26603,7 +26604,7 @@ import { PublicKey as PublicKey2 } from "@solana/web3.js";
|
|
|
26603
26604
|
|
|
26604
26605
|
// src/bot/policy.ts
|
|
26605
26606
|
import { positionPnl } from "@depthhq/sdk";
|
|
26606
|
-
import { quoteBounds } from "@depthhq/bindings";
|
|
26607
|
+
import { quoteBounds, quoteSizes } from "@depthhq/bindings";
|
|
26607
26608
|
|
|
26608
26609
|
// src/hedge/policy.ts
|
|
26609
26610
|
function validateHedgePolicy(policy) {
|
|
@@ -27298,6 +27299,7 @@ class QuoteHandle {
|
|
|
27298
27299
|
client;
|
|
27299
27300
|
reserveSignature;
|
|
27300
27301
|
releaseSignature;
|
|
27302
|
+
priorId;
|
|
27301
27303
|
balanceView;
|
|
27302
27304
|
constructor(id, prepareSignature, client) {
|
|
27303
27305
|
this.id = id;
|
|
@@ -27311,9 +27313,36 @@ class QuoteHandle {
|
|
|
27311
27313
|
status: name(state.status),
|
|
27312
27314
|
maxCapacity: BigInt(state.capacity.toString()),
|
|
27313
27315
|
attachedOrders: state.attachedOrders,
|
|
27316
|
+
expiresAt: state.expiry.toNumber(),
|
|
27314
27317
|
...state.replacementNonce == null ? {} : { replacementNonce: BigInt(state.replacementNonce.toString()) }
|
|
27315
27318
|
};
|
|
27316
27319
|
}
|
|
27320
|
+
async renew(input) {
|
|
27321
|
+
const market = await this.client.read("Market", this.client.marketAddress);
|
|
27322
|
+
if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
|
|
27323
|
+
throw new RangeError("Quote duration is outside market policy");
|
|
27324
|
+
const expiry = Math.floor(Date.now() / 1000) + input.validForSeconds;
|
|
27325
|
+
const before = await this.status();
|
|
27326
|
+
if (before.status !== "active")
|
|
27327
|
+
throw new Error("Only active quotes can be renewed");
|
|
27328
|
+
if (before.expiresAt >= expiry)
|
|
27329
|
+
return before;
|
|
27330
|
+
const actor = this.client.options.wallet.publicKey;
|
|
27331
|
+
try {
|
|
27332
|
+
await this.client.program.methods.renewQuote(new anchor4.BN(expiry)).accountsPartial({
|
|
27333
|
+
actor,
|
|
27334
|
+
market: this.client.marketAddress,
|
|
27335
|
+
quote: this.id,
|
|
27336
|
+
grant: actor.equals(this.client.options.maker) ? this.client.program.programId : this.client.grantAddress()
|
|
27337
|
+
}).rpc({ commitment: "confirmed" });
|
|
27338
|
+
} catch (error) {
|
|
27339
|
+
const receipt = await this.status();
|
|
27340
|
+
if (receipt.status !== "active" || receipt.expiresAt < expiry)
|
|
27341
|
+
throw error;
|
|
27342
|
+
return receipt;
|
|
27343
|
+
}
|
|
27344
|
+
return this.status();
|
|
27345
|
+
}
|
|
27317
27346
|
async capacity() {
|
|
27318
27347
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
27319
27348
|
const view = this.balanceView ?? await this.client.requestQuoteView(this.id);
|
|
@@ -27328,6 +27357,17 @@ class QuoteHandle {
|
|
|
27328
27357
|
}
|
|
27329
27358
|
throw new Error("Quote balance changed while the private view was running");
|
|
27330
27359
|
}
|
|
27360
|
+
async reservation() {
|
|
27361
|
+
const view = this.balanceView ?? await this.client.requestQuoteView(this.id);
|
|
27362
|
+
this.balanceView = view;
|
|
27363
|
+
const snapshot = await view.wait({ timeoutMs: 0 });
|
|
27364
|
+
if (snapshot.status === "pending")
|
|
27365
|
+
return;
|
|
27366
|
+
await view.deferClose();
|
|
27367
|
+
this.balanceView = undefined;
|
|
27368
|
+
if (snapshot.status === "ready" && "remainingMargin" in snapshot)
|
|
27369
|
+
return snapshot.remainingMargin;
|
|
27370
|
+
}
|
|
27331
27371
|
async confirm(options = {}) {
|
|
27332
27372
|
const timeoutMs = options.timeoutMs ?? 60000;
|
|
27333
27373
|
const started = Date.now();
|
|
@@ -27376,8 +27416,22 @@ class QuoteHandle {
|
|
|
27376
27416
|
return snapshot;
|
|
27377
27417
|
}
|
|
27378
27418
|
if (snapshot.status === "validated" || snapshot.status === "waiting") {
|
|
27419
|
+
if (this.priorId && snapshot.status === "validated") {
|
|
27420
|
+
const prior = await this.client.options.connection.getAccountInfo(this.priorId, "confirmed");
|
|
27421
|
+
if (prior) {
|
|
27422
|
+
const previous = await this.client.quote.get(this.priorId).status();
|
|
27423
|
+
if (previous.status === "active") {
|
|
27424
|
+
const quote2 = await this.client.quoteState(this.id);
|
|
27425
|
+
return this.client.quote.replacement(this.priorId, BigInt(quote2.nonce.toString())).rotate(this);
|
|
27426
|
+
}
|
|
27427
|
+
}
|
|
27428
|
+
}
|
|
27379
27429
|
const market = await this.client.read("Market", this.client.marketAddress);
|
|
27380
27430
|
const quote = await this.client.quoteState(this.id);
|
|
27431
|
+
const now = Math.floor(Date.now() / 1000);
|
|
27432
|
+
const age = now - market.oracleTimestamp.toNumber();
|
|
27433
|
+
if (age > market.config.maxOracleAgeSeconds - Math.min(Math.floor(market.config.maxOracleAgeSeconds / 2), 60))
|
|
27434
|
+
return snapshot;
|
|
27381
27435
|
const reference = BigInt(quote.oracleReferenceE8.toString());
|
|
27382
27436
|
const price = BigInt(market.oraclePriceE8.toString());
|
|
27383
27437
|
const drift = price > reference ? price - reference : reference - price;
|
|
@@ -27447,6 +27501,15 @@ class QuoteHandle {
|
|
|
27447
27501
|
return snapshot;
|
|
27448
27502
|
const actor = this.client.options.wallet.publicKey;
|
|
27449
27503
|
const grant = actor.equals(this.client.options.maker) ? undefined : this.client.grantAddress();
|
|
27504
|
+
if (snapshot.status === "reserving") {
|
|
27505
|
+
await this.cancelStep(() => this.client.program.methods.cancelQuoteReserve().accountsPartial({
|
|
27506
|
+
actor,
|
|
27507
|
+
grant: grant ?? this.client.program.programId,
|
|
27508
|
+
quote: this.id,
|
|
27509
|
+
marketAccount: this.client.marketAccount()
|
|
27510
|
+
}).rpc({ commitment: "confirmed" }), ["validated", "active", "cancelled"]);
|
|
27511
|
+
return this.cancel();
|
|
27512
|
+
}
|
|
27450
27513
|
if (snapshot.status === "validating" || snapshot.status === "validated") {
|
|
27451
27514
|
return this.cancelStep(() => this.client.program.methods.cancelPreparedQuote().accountsPartial({
|
|
27452
27515
|
actor,
|
|
@@ -27520,6 +27583,8 @@ class QuoteHandle {
|
|
|
27520
27583
|
import * as anchor5 from "@anchor-lang/core";
|
|
27521
27584
|
import { DEPTH_PROGRAM_ID as DEPTH_PROGRAM_ID3 } from "@depthhq/bindings";
|
|
27522
27585
|
import { PublicKey as PublicKey6 } from "@solana/web3.js";
|
|
27586
|
+
import { quoteBounds as quoteBounds3 } from "@depthhq/bindings";
|
|
27587
|
+
|
|
27523
27588
|
class QuoteReplacement {
|
|
27524
27589
|
client;
|
|
27525
27590
|
priorId;
|
|
@@ -27565,10 +27630,12 @@ class QuoteReplacement {
|
|
|
27565
27630
|
if (input.clientNonce !== undefined && input.clientNonce !== this.nonce)
|
|
27566
27631
|
throw new Error("Replacement terms use a different client nonce");
|
|
27567
27632
|
await this.begin();
|
|
27568
|
-
|
|
27633
|
+
const handle = await this.client.quote.publish({
|
|
27569
27634
|
...input,
|
|
27570
27635
|
clientNonce: this.nonce
|
|
27571
27636
|
});
|
|
27637
|
+
handle.priorId = this.priorId;
|
|
27638
|
+
return handle;
|
|
27572
27639
|
}
|
|
27573
27640
|
async begin() {
|
|
27574
27641
|
if (!await this.client.options.connection.getAccountInfo(this.priorId, "confirmed")) {
|
|
@@ -27590,6 +27657,74 @@ class QuoteReplacement {
|
|
|
27590
27657
|
}).rpc({ commitment: "confirmed" });
|
|
27591
27658
|
}
|
|
27592
27659
|
}
|
|
27660
|
+
async rotate(handle = this.client.quote.get(this.nextId)) {
|
|
27661
|
+
const snapshot = await handle.status();
|
|
27662
|
+
if (["reserving", "active", "filled", "cancelled", "rejected"].includes(snapshot.status))
|
|
27663
|
+
return snapshot;
|
|
27664
|
+
if (snapshot.status !== "validated")
|
|
27665
|
+
return snapshot;
|
|
27666
|
+
const [prior, quote, market, account] = await Promise.all([
|
|
27667
|
+
this.client.quote.get(this.priorId).status(),
|
|
27668
|
+
this.client.quoteState(this.nextId),
|
|
27669
|
+
this.client.read("Market", this.client.marketAddress),
|
|
27670
|
+
this.client.read("MarketAccount", this.client.marketAccount())
|
|
27671
|
+
]);
|
|
27672
|
+
if (prior.status !== "active")
|
|
27673
|
+
return handle.activate({ timeoutMs: 0 });
|
|
27674
|
+
const reference = BigInt(quote.oracleReferenceE8.toString());
|
|
27675
|
+
const price = BigInt(market.oraclePriceE8.toString());
|
|
27676
|
+
const drift = price > reference ? price - reference : reference - price;
|
|
27677
|
+
if (!quote.marketRevision.eq(market.revision) || market.oracleTimestamp.lt(quote.oracleTimestamp) || reference <= 0n || drift * 10000n > reference * BigInt(quoteBounds3(market.config.maxQuoteDeviationBps).driftBps) || quote.expiry.toNumber() <= Math.floor(Date.now() / 1000))
|
|
27678
|
+
return handle.cancel();
|
|
27679
|
+
if (market.pendingOrders > 0 || !("ready" in account.status) || prior.attachedOrders > 0)
|
|
27680
|
+
return snapshot;
|
|
27681
|
+
const age = Math.floor(Date.now() / 1000) - market.oracleTimestamp.toNumber();
|
|
27682
|
+
if (age > market.config.maxOracleAgeSeconds - Math.min(60, Math.floor(market.config.maxOracleAgeSeconds / 2)))
|
|
27683
|
+
return snapshot;
|
|
27684
|
+
const offset = new anchor5.BN(randomBytes3(8), "le");
|
|
27685
|
+
const actor = this.client.options.wallet.publicKey;
|
|
27686
|
+
try {
|
|
27687
|
+
handle.reserveSignature = await this.client.program.methods.rotateQuote(offset).accountsPartial({
|
|
27688
|
+
...await this.client.queue("rotate_quote", offset),
|
|
27689
|
+
payer: actor,
|
|
27690
|
+
market: this.client.marketAddress,
|
|
27691
|
+
marketAccount: this.client.marketAccount(),
|
|
27692
|
+
quote: this.nextId,
|
|
27693
|
+
prior: this.priorId,
|
|
27694
|
+
grant: actor.equals(this.client.options.maker) ? this.client.program.programId : this.client.grantAddress()
|
|
27695
|
+
}).rpc({ commitment: "confirmed" });
|
|
27696
|
+
} catch (error) {
|
|
27697
|
+
const receipt = await handle.status();
|
|
27698
|
+
if (["reserving", "active"].includes(receipt.status))
|
|
27699
|
+
return receipt;
|
|
27700
|
+
const code2 = error.error?.errorCode?.code;
|
|
27701
|
+
if (["WrongMarketAccountStatus", "WrongOrderStatus"].includes(code2 ?? ""))
|
|
27702
|
+
return receipt;
|
|
27703
|
+
throw error;
|
|
27704
|
+
}
|
|
27705
|
+
return handle.status();
|
|
27706
|
+
}
|
|
27707
|
+
async discard() {
|
|
27708
|
+
const next = this.client.quote.get(this.nextId);
|
|
27709
|
+
const snapshot = await next.cancel();
|
|
27710
|
+
if (!["cancelled", "rejected"].includes(snapshot.status))
|
|
27711
|
+
throw new Error("Successor cancellation is still pending");
|
|
27712
|
+
const prior = await this.client.quoteState(this.priorId);
|
|
27713
|
+
if (prior.replacementNonce == null)
|
|
27714
|
+
return;
|
|
27715
|
+
const actor = this.client.options.wallet.publicKey;
|
|
27716
|
+
try {
|
|
27717
|
+
await this.client.program.methods.resetQuoteReplacement().accountsPartial({
|
|
27718
|
+
actor,
|
|
27719
|
+
quote: this.priorId,
|
|
27720
|
+
successor: this.nextId,
|
|
27721
|
+
grant: actor.equals(this.client.options.maker) ? this.client.program.programId : this.client.grantAddress()
|
|
27722
|
+
}).rpc({ commitment: "confirmed" });
|
|
27723
|
+
} catch (error) {
|
|
27724
|
+
if ((await this.client.quoteState(this.priorId)).replacementNonce != null)
|
|
27725
|
+
throw error;
|
|
27726
|
+
}
|
|
27727
|
+
}
|
|
27593
27728
|
async resume(input, options = {}) {
|
|
27594
27729
|
if (input.clientNonce !== undefined && input.clientNonce !== this.nonce)
|
|
27595
27730
|
throw new Error("Replacement terms use a different client nonce");
|
|
@@ -27600,6 +27735,7 @@ class QuoteReplacement {
|
|
|
27600
27735
|
const deadline = Date.now() + timeoutMs;
|
|
27601
27736
|
const previous = this.client.quote.get(this.priorId);
|
|
27602
27737
|
const next = await this.prepare(input);
|
|
27738
|
+
next.priorId = this.priorId;
|
|
27603
27739
|
for (;; ) {
|
|
27604
27740
|
const snapshot = await next.status();
|
|
27605
27741
|
if (["validated", "waiting", "reserving", "active", "filled"].includes(snapshot.status))
|
|
@@ -27616,6 +27752,10 @@ class QuoteReplacement {
|
|
|
27616
27752
|
const snapshot = await previous.status();
|
|
27617
27753
|
if (["cancelled", "filled", "rejected"].includes(snapshot.status))
|
|
27618
27754
|
break;
|
|
27755
|
+
if (snapshot.status === "active") {
|
|
27756
|
+
await this.rotate(next);
|
|
27757
|
+
return next;
|
|
27758
|
+
}
|
|
27619
27759
|
if (["active", "closing"].includes(snapshot.status)) {
|
|
27620
27760
|
if (snapshot.status === "active" || snapshot.attachedOrders === 0)
|
|
27621
27761
|
await previous.cancel();
|
|
@@ -27707,6 +27847,7 @@ function stateName(value) {
|
|
|
27707
27847
|
|
|
27708
27848
|
class MakerClient {
|
|
27709
27849
|
options;
|
|
27850
|
+
marketView;
|
|
27710
27851
|
hedges;
|
|
27711
27852
|
automation;
|
|
27712
27853
|
provider;
|
|
@@ -28072,7 +28213,7 @@ class MakerClient {
|
|
|
28072
28213
|
throw new Error("Only SOL-USD is supported");
|
|
28073
28214
|
if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
|
|
28074
28215
|
throw new RangeError("Quote duration is outside market policy");
|
|
28075
|
-
const tiers = market.config.quoteTiers.map((size) => BigInt(size.toString()))
|
|
28216
|
+
const tiers = quoteSizes2(market.config.quoteTiers.map((size) => BigInt(size.toString())), input.maxCapacity).map((size) => ({
|
|
28076
28217
|
size,
|
|
28077
28218
|
bidE8: input.bidE8,
|
|
28078
28219
|
askE8: input.askE8
|
|
@@ -28184,10 +28325,13 @@ class MakerClient {
|
|
|
28184
28325
|
summary: async (options = { timeoutMs: 30000 }) => {
|
|
28185
28326
|
if (this.options.wallet.publicKey.equals(this.options.maker))
|
|
28186
28327
|
return (await this.owned()).position.summary(options);
|
|
28187
|
-
const view = await this.requestMarketView();
|
|
28328
|
+
const view = this.marketView ?? await this.requestMarketView();
|
|
28329
|
+
this.marketView = view;
|
|
28188
28330
|
const result = await view.wait(options);
|
|
28189
|
-
if (result.status !== "pending")
|
|
28331
|
+
if (result.status !== "pending") {
|
|
28332
|
+
this.marketView = undefined;
|
|
28190
28333
|
await view.deferClose();
|
|
28334
|
+
}
|
|
28191
28335
|
return result;
|
|
28192
28336
|
}
|
|
28193
28337
|
};
|
|
@@ -28224,7 +28368,7 @@ class MakerClient {
|
|
|
28224
28368
|
validateQuote(input, market) {
|
|
28225
28369
|
const now = Math.floor(Date.now() / 1000);
|
|
28226
28370
|
const reference = BigInt(market.oraclePriceE8.toString());
|
|
28227
|
-
const { preparationBps } =
|
|
28371
|
+
const { preparationBps } = quoteBounds4(market.config.maxQuoteDeviationBps);
|
|
28228
28372
|
if (!["draft", "active"].includes(stateName(market.status)))
|
|
28229
28373
|
throw new Error("Market does not accept quotes");
|
|
28230
28374
|
if (input.tiers.length === 0 || input.tiers.length > 4) {
|
|
@@ -28244,7 +28388,7 @@ class MakerClient {
|
|
|
28244
28388
|
throw new Error("Quote prices exceed the preparation deviation limit");
|
|
28245
28389
|
});
|
|
28246
28390
|
u642(input.maxCapacity, "maxCapacity");
|
|
28247
|
-
if (input.maxCapacity < input.tiers.
|
|
28391
|
+
if (input.maxCapacity < input.tiers[0].size || input.maxCapacity > BigInt(market.config.maxAccountNotional.toString()) || input.maxCapacity > BigInt(market.config.maxMarketNotional.toString())) {
|
|
28248
28392
|
throw new Error("Quote exceeds market capacity");
|
|
28249
28393
|
}
|
|
28250
28394
|
if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= now || input.expiresAt - now > market.config.maxQuoteLifetimeSeconds) {
|
package/dist/index.node.js
CHANGED
|
@@ -24907,7 +24907,8 @@ import {
|
|
|
24907
24907
|
quoteQueueAddress,
|
|
24908
24908
|
protocolAddress,
|
|
24909
24909
|
SOL_USD_MARKET_ID as SOL_USD_MARKET_ID2,
|
|
24910
|
-
quoteBounds as
|
|
24910
|
+
quoteBounds as quoteBounds4,
|
|
24911
|
+
quoteSizes as quoteSizes2
|
|
24911
24912
|
} from "@depthhq/bindings";
|
|
24912
24913
|
import { DepthView } from "@depthhq/sdk/views";
|
|
24913
24914
|
import { DepthRecovery } from "@depthhq/sdk/recovery";
|
|
@@ -25118,7 +25119,7 @@ import { PublicKey as PublicKey2 } from "@solana/web3.js";
|
|
|
25118
25119
|
|
|
25119
25120
|
// src/bot/policy.ts
|
|
25120
25121
|
import { positionPnl } from "@depthhq/sdk";
|
|
25121
|
-
import { quoteBounds } from "@depthhq/bindings";
|
|
25122
|
+
import { quoteBounds, quoteSizes } from "@depthhq/bindings";
|
|
25122
25123
|
|
|
25123
25124
|
// src/hedge/policy.ts
|
|
25124
25125
|
function validateHedgePolicy(policy) {
|
|
@@ -25817,6 +25818,7 @@ class QuoteHandle {
|
|
|
25817
25818
|
client;
|
|
25818
25819
|
reserveSignature;
|
|
25819
25820
|
releaseSignature;
|
|
25821
|
+
priorId;
|
|
25820
25822
|
balanceView;
|
|
25821
25823
|
constructor(id, prepareSignature, client) {
|
|
25822
25824
|
this.id = id;
|
|
@@ -25830,9 +25832,36 @@ class QuoteHandle {
|
|
|
25830
25832
|
status: name(state.status),
|
|
25831
25833
|
maxCapacity: BigInt(state.capacity.toString()),
|
|
25832
25834
|
attachedOrders: state.attachedOrders,
|
|
25835
|
+
expiresAt: state.expiry.toNumber(),
|
|
25833
25836
|
...state.replacementNonce == null ? {} : { replacementNonce: BigInt(state.replacementNonce.toString()) }
|
|
25834
25837
|
};
|
|
25835
25838
|
}
|
|
25839
|
+
async renew(input) {
|
|
25840
|
+
const market = await this.client.read("Market", this.client.marketAddress);
|
|
25841
|
+
if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
|
|
25842
|
+
throw new RangeError("Quote duration is outside market policy");
|
|
25843
|
+
const expiry = Math.floor(Date.now() / 1000) + input.validForSeconds;
|
|
25844
|
+
const before = await this.status();
|
|
25845
|
+
if (before.status !== "active")
|
|
25846
|
+
throw new Error("Only active quotes can be renewed");
|
|
25847
|
+
if (before.expiresAt >= expiry)
|
|
25848
|
+
return before;
|
|
25849
|
+
const actor = this.client.options.wallet.publicKey;
|
|
25850
|
+
try {
|
|
25851
|
+
await this.client.program.methods.renewQuote(new anchor4.BN(expiry)).accountsPartial({
|
|
25852
|
+
actor,
|
|
25853
|
+
market: this.client.marketAddress,
|
|
25854
|
+
quote: this.id,
|
|
25855
|
+
grant: actor.equals(this.client.options.maker) ? this.client.program.programId : this.client.grantAddress()
|
|
25856
|
+
}).rpc({ commitment: "confirmed" });
|
|
25857
|
+
} catch (error) {
|
|
25858
|
+
const receipt = await this.status();
|
|
25859
|
+
if (receipt.status !== "active" || receipt.expiresAt < expiry)
|
|
25860
|
+
throw error;
|
|
25861
|
+
return receipt;
|
|
25862
|
+
}
|
|
25863
|
+
return this.status();
|
|
25864
|
+
}
|
|
25836
25865
|
async capacity() {
|
|
25837
25866
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
25838
25867
|
const view = this.balanceView ?? await this.client.requestQuoteView(this.id);
|
|
@@ -25847,6 +25876,17 @@ class QuoteHandle {
|
|
|
25847
25876
|
}
|
|
25848
25877
|
throw new Error("Quote balance changed while the private view was running");
|
|
25849
25878
|
}
|
|
25879
|
+
async reservation() {
|
|
25880
|
+
const view = this.balanceView ?? await this.client.requestQuoteView(this.id);
|
|
25881
|
+
this.balanceView = view;
|
|
25882
|
+
const snapshot = await view.wait({ timeoutMs: 0 });
|
|
25883
|
+
if (snapshot.status === "pending")
|
|
25884
|
+
return;
|
|
25885
|
+
await view.deferClose();
|
|
25886
|
+
this.balanceView = undefined;
|
|
25887
|
+
if (snapshot.status === "ready" && "remainingMargin" in snapshot)
|
|
25888
|
+
return snapshot.remainingMargin;
|
|
25889
|
+
}
|
|
25850
25890
|
async confirm(options = {}) {
|
|
25851
25891
|
const timeoutMs = options.timeoutMs ?? 60000;
|
|
25852
25892
|
const started = Date.now();
|
|
@@ -25895,8 +25935,22 @@ class QuoteHandle {
|
|
|
25895
25935
|
return snapshot;
|
|
25896
25936
|
}
|
|
25897
25937
|
if (snapshot.status === "validated" || snapshot.status === "waiting") {
|
|
25938
|
+
if (this.priorId && snapshot.status === "validated") {
|
|
25939
|
+
const prior = await this.client.options.connection.getAccountInfo(this.priorId, "confirmed");
|
|
25940
|
+
if (prior) {
|
|
25941
|
+
const previous = await this.client.quote.get(this.priorId).status();
|
|
25942
|
+
if (previous.status === "active") {
|
|
25943
|
+
const quote2 = await this.client.quoteState(this.id);
|
|
25944
|
+
return this.client.quote.replacement(this.priorId, BigInt(quote2.nonce.toString())).rotate(this);
|
|
25945
|
+
}
|
|
25946
|
+
}
|
|
25947
|
+
}
|
|
25898
25948
|
const market = await this.client.read("Market", this.client.marketAddress);
|
|
25899
25949
|
const quote = await this.client.quoteState(this.id);
|
|
25950
|
+
const now = Math.floor(Date.now() / 1000);
|
|
25951
|
+
const age = now - market.oracleTimestamp.toNumber();
|
|
25952
|
+
if (age > market.config.maxOracleAgeSeconds - Math.min(Math.floor(market.config.maxOracleAgeSeconds / 2), 60))
|
|
25953
|
+
return snapshot;
|
|
25900
25954
|
const reference = BigInt(quote.oracleReferenceE8.toString());
|
|
25901
25955
|
const price = BigInt(market.oraclePriceE8.toString());
|
|
25902
25956
|
const drift = price > reference ? price - reference : reference - price;
|
|
@@ -25966,6 +26020,15 @@ class QuoteHandle {
|
|
|
25966
26020
|
return snapshot;
|
|
25967
26021
|
const actor = this.client.options.wallet.publicKey;
|
|
25968
26022
|
const grant = actor.equals(this.client.options.maker) ? undefined : this.client.grantAddress();
|
|
26023
|
+
if (snapshot.status === "reserving") {
|
|
26024
|
+
await this.cancelStep(() => this.client.program.methods.cancelQuoteReserve().accountsPartial({
|
|
26025
|
+
actor,
|
|
26026
|
+
grant: grant ?? this.client.program.programId,
|
|
26027
|
+
quote: this.id,
|
|
26028
|
+
marketAccount: this.client.marketAccount()
|
|
26029
|
+
}).rpc({ commitment: "confirmed" }), ["validated", "active", "cancelled"]);
|
|
26030
|
+
return this.cancel();
|
|
26031
|
+
}
|
|
25969
26032
|
if (snapshot.status === "validating" || snapshot.status === "validated") {
|
|
25970
26033
|
return this.cancelStep(() => this.client.program.methods.cancelPreparedQuote().accountsPartial({
|
|
25971
26034
|
actor,
|
|
@@ -26040,6 +26103,7 @@ import * as anchor5 from "@anchor-lang/core";
|
|
|
26040
26103
|
import { DEPTH_PROGRAM_ID as DEPTH_PROGRAM_ID3 } from "@depthhq/bindings";
|
|
26041
26104
|
import { PublicKey as PublicKey6 } from "@solana/web3.js";
|
|
26042
26105
|
import { Buffer as Buffer7 } from "buffer";
|
|
26106
|
+
import { quoteBounds as quoteBounds3 } from "@depthhq/bindings";
|
|
26043
26107
|
|
|
26044
26108
|
class QuoteReplacement {
|
|
26045
26109
|
client;
|
|
@@ -26086,10 +26150,12 @@ class QuoteReplacement {
|
|
|
26086
26150
|
if (input.clientNonce !== undefined && input.clientNonce !== this.nonce)
|
|
26087
26151
|
throw new Error("Replacement terms use a different client nonce");
|
|
26088
26152
|
await this.begin();
|
|
26089
|
-
|
|
26153
|
+
const handle = await this.client.quote.publish({
|
|
26090
26154
|
...input,
|
|
26091
26155
|
clientNonce: this.nonce
|
|
26092
26156
|
});
|
|
26157
|
+
handle.priorId = this.priorId;
|
|
26158
|
+
return handle;
|
|
26093
26159
|
}
|
|
26094
26160
|
async begin() {
|
|
26095
26161
|
if (!await this.client.options.connection.getAccountInfo(this.priorId, "confirmed")) {
|
|
@@ -26111,6 +26177,74 @@ class QuoteReplacement {
|
|
|
26111
26177
|
}).rpc({ commitment: "confirmed" });
|
|
26112
26178
|
}
|
|
26113
26179
|
}
|
|
26180
|
+
async rotate(handle = this.client.quote.get(this.nextId)) {
|
|
26181
|
+
const snapshot = await handle.status();
|
|
26182
|
+
if (["reserving", "active", "filled", "cancelled", "rejected"].includes(snapshot.status))
|
|
26183
|
+
return snapshot;
|
|
26184
|
+
if (snapshot.status !== "validated")
|
|
26185
|
+
return snapshot;
|
|
26186
|
+
const [prior, quote, market, account] = await Promise.all([
|
|
26187
|
+
this.client.quote.get(this.priorId).status(),
|
|
26188
|
+
this.client.quoteState(this.nextId),
|
|
26189
|
+
this.client.read("Market", this.client.marketAddress),
|
|
26190
|
+
this.client.read("MarketAccount", this.client.marketAccount())
|
|
26191
|
+
]);
|
|
26192
|
+
if (prior.status !== "active")
|
|
26193
|
+
return handle.activate({ timeoutMs: 0 });
|
|
26194
|
+
const reference = BigInt(quote.oracleReferenceE8.toString());
|
|
26195
|
+
const price = BigInt(market.oraclePriceE8.toString());
|
|
26196
|
+
const drift = price > reference ? price - reference : reference - price;
|
|
26197
|
+
if (!quote.marketRevision.eq(market.revision) || market.oracleTimestamp.lt(quote.oracleTimestamp) || reference <= 0n || drift * 10000n > reference * BigInt(quoteBounds3(market.config.maxQuoteDeviationBps).driftBps) || quote.expiry.toNumber() <= Math.floor(Date.now() / 1000))
|
|
26198
|
+
return handle.cancel();
|
|
26199
|
+
if (market.pendingOrders > 0 || !("ready" in account.status) || prior.attachedOrders > 0)
|
|
26200
|
+
return snapshot;
|
|
26201
|
+
const age = Math.floor(Date.now() / 1000) - market.oracleTimestamp.toNumber();
|
|
26202
|
+
if (age > market.config.maxOracleAgeSeconds - Math.min(60, Math.floor(market.config.maxOracleAgeSeconds / 2)))
|
|
26203
|
+
return snapshot;
|
|
26204
|
+
const offset = new anchor5.BN(randomBytes3(8), "le");
|
|
26205
|
+
const actor = this.client.options.wallet.publicKey;
|
|
26206
|
+
try {
|
|
26207
|
+
handle.reserveSignature = await this.client.program.methods.rotateQuote(offset).accountsPartial({
|
|
26208
|
+
...await this.client.queue("rotate_quote", offset),
|
|
26209
|
+
payer: actor,
|
|
26210
|
+
market: this.client.marketAddress,
|
|
26211
|
+
marketAccount: this.client.marketAccount(),
|
|
26212
|
+
quote: this.nextId,
|
|
26213
|
+
prior: this.priorId,
|
|
26214
|
+
grant: actor.equals(this.client.options.maker) ? this.client.program.programId : this.client.grantAddress()
|
|
26215
|
+
}).rpc({ commitment: "confirmed" });
|
|
26216
|
+
} catch (error) {
|
|
26217
|
+
const receipt = await handle.status();
|
|
26218
|
+
if (["reserving", "active"].includes(receipt.status))
|
|
26219
|
+
return receipt;
|
|
26220
|
+
const code = error.error?.errorCode?.code;
|
|
26221
|
+
if (["WrongMarketAccountStatus", "WrongOrderStatus"].includes(code ?? ""))
|
|
26222
|
+
return receipt;
|
|
26223
|
+
throw error;
|
|
26224
|
+
}
|
|
26225
|
+
return handle.status();
|
|
26226
|
+
}
|
|
26227
|
+
async discard() {
|
|
26228
|
+
const next = this.client.quote.get(this.nextId);
|
|
26229
|
+
const snapshot = await next.cancel();
|
|
26230
|
+
if (!["cancelled", "rejected"].includes(snapshot.status))
|
|
26231
|
+
throw new Error("Successor cancellation is still pending");
|
|
26232
|
+
const prior = await this.client.quoteState(this.priorId);
|
|
26233
|
+
if (prior.replacementNonce == null)
|
|
26234
|
+
return;
|
|
26235
|
+
const actor = this.client.options.wallet.publicKey;
|
|
26236
|
+
try {
|
|
26237
|
+
await this.client.program.methods.resetQuoteReplacement().accountsPartial({
|
|
26238
|
+
actor,
|
|
26239
|
+
quote: this.priorId,
|
|
26240
|
+
successor: this.nextId,
|
|
26241
|
+
grant: actor.equals(this.client.options.maker) ? this.client.program.programId : this.client.grantAddress()
|
|
26242
|
+
}).rpc({ commitment: "confirmed" });
|
|
26243
|
+
} catch (error) {
|
|
26244
|
+
if ((await this.client.quoteState(this.priorId)).replacementNonce != null)
|
|
26245
|
+
throw error;
|
|
26246
|
+
}
|
|
26247
|
+
}
|
|
26114
26248
|
async resume(input, options = {}) {
|
|
26115
26249
|
if (input.clientNonce !== undefined && input.clientNonce !== this.nonce)
|
|
26116
26250
|
throw new Error("Replacement terms use a different client nonce");
|
|
@@ -26121,6 +26255,7 @@ class QuoteReplacement {
|
|
|
26121
26255
|
const deadline = Date.now() + timeoutMs;
|
|
26122
26256
|
const previous = this.client.quote.get(this.priorId);
|
|
26123
26257
|
const next = await this.prepare(input);
|
|
26258
|
+
next.priorId = this.priorId;
|
|
26124
26259
|
for (;; ) {
|
|
26125
26260
|
const snapshot = await next.status();
|
|
26126
26261
|
if (["validated", "waiting", "reserving", "active", "filled"].includes(snapshot.status))
|
|
@@ -26137,6 +26272,10 @@ class QuoteReplacement {
|
|
|
26137
26272
|
const snapshot = await previous.status();
|
|
26138
26273
|
if (["cancelled", "filled", "rejected"].includes(snapshot.status))
|
|
26139
26274
|
break;
|
|
26275
|
+
if (snapshot.status === "active") {
|
|
26276
|
+
await this.rotate(next);
|
|
26277
|
+
return next;
|
|
26278
|
+
}
|
|
26140
26279
|
if (["active", "closing"].includes(snapshot.status)) {
|
|
26141
26280
|
if (snapshot.status === "active" || snapshot.attachedOrders === 0)
|
|
26142
26281
|
await previous.cancel();
|
|
@@ -26228,6 +26367,7 @@ function stateName(value) {
|
|
|
26228
26367
|
|
|
26229
26368
|
class MakerClient {
|
|
26230
26369
|
options;
|
|
26370
|
+
marketView;
|
|
26231
26371
|
hedges;
|
|
26232
26372
|
automation;
|
|
26233
26373
|
provider;
|
|
@@ -26593,7 +26733,7 @@ class MakerClient {
|
|
|
26593
26733
|
throw new Error("Only SOL-USD is supported");
|
|
26594
26734
|
if (!Number.isSafeInteger(input.validForSeconds) || input.validForSeconds < 1 || input.validForSeconds > market.config.maxQuoteLifetimeSeconds)
|
|
26595
26735
|
throw new RangeError("Quote duration is outside market policy");
|
|
26596
|
-
const tiers = market.config.quoteTiers.map((size) => BigInt(size.toString()))
|
|
26736
|
+
const tiers = quoteSizes2(market.config.quoteTiers.map((size) => BigInt(size.toString())), input.maxCapacity).map((size) => ({
|
|
26597
26737
|
size,
|
|
26598
26738
|
bidE8: input.bidE8,
|
|
26599
26739
|
askE8: input.askE8
|
|
@@ -26705,10 +26845,13 @@ class MakerClient {
|
|
|
26705
26845
|
summary: async (options = { timeoutMs: 30000 }) => {
|
|
26706
26846
|
if (this.options.wallet.publicKey.equals(this.options.maker))
|
|
26707
26847
|
return (await this.owned()).position.summary(options);
|
|
26708
|
-
const view = await this.requestMarketView();
|
|
26848
|
+
const view = this.marketView ?? await this.requestMarketView();
|
|
26849
|
+
this.marketView = view;
|
|
26709
26850
|
const result = await view.wait(options);
|
|
26710
|
-
if (result.status !== "pending")
|
|
26851
|
+
if (result.status !== "pending") {
|
|
26852
|
+
this.marketView = undefined;
|
|
26711
26853
|
await view.deferClose();
|
|
26854
|
+
}
|
|
26712
26855
|
return result;
|
|
26713
26856
|
}
|
|
26714
26857
|
};
|
|
@@ -26745,7 +26888,7 @@ class MakerClient {
|
|
|
26745
26888
|
validateQuote(input, market) {
|
|
26746
26889
|
const now = Math.floor(Date.now() / 1000);
|
|
26747
26890
|
const reference = BigInt(market.oraclePriceE8.toString());
|
|
26748
|
-
const { preparationBps } =
|
|
26891
|
+
const { preparationBps } = quoteBounds4(market.config.maxQuoteDeviationBps);
|
|
26749
26892
|
if (!["draft", "active"].includes(stateName(market.status)))
|
|
26750
26893
|
throw new Error("Market does not accept quotes");
|
|
26751
26894
|
if (input.tiers.length === 0 || input.tiers.length > 4) {
|
|
@@ -26765,7 +26908,7 @@ class MakerClient {
|
|
|
26765
26908
|
throw new Error("Quote prices exceed the preparation deviation limit");
|
|
26766
26909
|
});
|
|
26767
26910
|
u642(input.maxCapacity, "maxCapacity");
|
|
26768
|
-
if (input.maxCapacity < input.tiers.
|
|
26911
|
+
if (input.maxCapacity < input.tiers[0].size || input.maxCapacity > BigInt(market.config.maxAccountNotional.toString()) || input.maxCapacity > BigInt(market.config.maxMarketNotional.toString())) {
|
|
26769
26912
|
throw new Error("Quote exceeds market capacity");
|
|
26770
26913
|
}
|
|
26771
26914
|
if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= now || input.expiresAt - now > market.config.maxQuoteLifetimeSeconds) {
|
package/dist/node.js
CHANGED
|
@@ -154,6 +154,11 @@ class BotStore {
|
|
|
154
154
|
for (const value of [state.quote, state.replacement?.prior])
|
|
155
155
|
if (value !== undefined && !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value))
|
|
156
156
|
throw new Error("Maker bot checkpoint quote is invalid");
|
|
157
|
+
if (state.inventoryPendingSince !== undefined && (!Number.isSafeInteger(state.inventoryPendingSince) || state.inventoryPendingSince <= 0))
|
|
158
|
+
throw new Error("Inventory checkpoint time is invalid");
|
|
159
|
+
for (const risk of [state.quoteRisk, state.replacement?.risk])
|
|
160
|
+
if (risk && (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(risk.quote) || typeof risk.notional !== "bigint" || risk.notional < 0n || typeof risk.price !== "bigint" || risk.price <= 0n || typeof risk.version !== "bigint" || risk.version < 0n || typeof risk.externalPosition !== "bigint" || !Number.isSafeInteger(risk.hedgeNonce) || risk.hedgeNonce < 0 || risk.balanceNonce !== undefined && !/^[0-9a-f]{32}$/.test(risk.balanceNonce)))
|
|
161
|
+
throw new Error("Quote risk checkpoint is invalid");
|
|
157
162
|
if (state.quoteWrite && (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(state.quoteWrite.quote) || typeof state.quoteWrite.version !== "bigint" || typeof state.quoteWrite.release !== "boolean" || state.quoteWrite.version < 0n))
|
|
158
163
|
throw new Error("Quote write checkpoint is invalid");
|
|
159
164
|
for (const request of [state.request, state.replacement?.request])
|
|
@@ -206,7 +211,7 @@ class BotStore {
|
|
|
206
211
|
|
|
207
212
|
// src/bot/policy.ts
|
|
208
213
|
import { positionPnl } from "@depthhq/sdk";
|
|
209
|
-
import { quoteBounds } from "@depthhq/bindings";
|
|
214
|
+
import { quoteBounds, quoteSizes } from "@depthhq/bindings";
|
|
210
215
|
|
|
211
216
|
// src/hedge/policy.ts
|
|
212
217
|
function validateHedgePolicy(policy) {
|
|
@@ -267,6 +272,13 @@ var abs = (value) => value < 0n ? -value : value;
|
|
|
267
272
|
var notional = (size, priceE8) => (abs(size) * priceE8 + 99999999999n) / 100000000000n;
|
|
268
273
|
|
|
269
274
|
// src/bot/policy.ts
|
|
275
|
+
function executionCosts(policy, pricing) {
|
|
276
|
+
const horizon = policy.fundingHours ?? 1;
|
|
277
|
+
return {
|
|
278
|
+
bid: Math.ceil((pricing?.takerFeeBps ?? 0) + Math.max(0, -(pricing?.hourlyFundingBps ?? 0)) * horizon),
|
|
279
|
+
ask: Math.ceil((pricing?.takerFeeBps ?? 0) + Math.max(0, pricing?.hourlyFundingBps ?? 0) * horizon)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
270
282
|
function validateBotPolicy(policy) {
|
|
271
283
|
if (policy.maxCapacity <= 0n || policy.maxInventoryNotional <= 0n || policy.maxCapacity > policy.maxInventoryNotional)
|
|
272
284
|
throw new RangeError("Maker capacity must fit a positive inventory limit");
|
|
@@ -296,7 +308,7 @@ function quoteHealth(market, now) {
|
|
|
296
308
|
if (BigInt(market.oraclePriceE8.toString()) < 100000000n)
|
|
297
309
|
return "oracle price invalid";
|
|
298
310
|
}
|
|
299
|
-
function botQuote(policy, market, position, nonce, now, pricing) {
|
|
311
|
+
function botQuote(policy, market, position, nonce, now, pricing, reservationCredit = 0n) {
|
|
300
312
|
validateBotPolicy(policy);
|
|
301
313
|
const reason = quoteHealth(market, now);
|
|
302
314
|
if (reason)
|
|
@@ -304,12 +316,14 @@ function botQuote(policy, market, position, nonce, now, pricing) {
|
|
|
304
316
|
if (position.status !== "ready" || position.position === undefined || position.collateral === undefined || position.reserved === undefined)
|
|
305
317
|
throw new Error("Maker private position is not ready");
|
|
306
318
|
const price = BigInt(market.oraclePriceE8.toString());
|
|
307
|
-
const exposure = (position.position < 0n ? -position.position : position.position) * price / 100000000000n;
|
|
319
|
+
const exposure = ((position.position < 0n ? -position.position : position.position) * price + 99999999999n) / 100000000000n;
|
|
308
320
|
const marginBps = BigInt(market.config.initialMarginBps);
|
|
321
|
+
if (reservationCredit < 0n || reservationCredit > position.reserved)
|
|
322
|
+
throw new Error("Replacement margin credit exceeds the private reservation");
|
|
309
323
|
if (marginBps <= 0n)
|
|
310
324
|
throw new Error("Maker margin policy is invalid");
|
|
311
325
|
const pnl = position.position === 0n ? 0n : positionPnl(position, price, BigInt(market.fundingIndex.toString())).net;
|
|
312
|
-
const free = position.collateral + pnl - position.reserved - (exposure * marginBps + 9999n) / 10000n;
|
|
326
|
+
const free = position.collateral + pnl - position.reserved - (exposure * marginBps + 9999n) / 10000n + reservationCredit;
|
|
313
327
|
const limits = [
|
|
314
328
|
policy.maxCapacity,
|
|
315
329
|
policy.maxInventoryNotional - exposure,
|
|
@@ -318,7 +332,7 @@ function botQuote(policy, market, position, nonce, now, pricing) {
|
|
|
318
332
|
BigInt(market.config.maxMarketNotional.toString())
|
|
319
333
|
];
|
|
320
334
|
const capacity = limits.reduce((minimum, value) => value < minimum ? value : minimum);
|
|
321
|
-
const tiers = market.config.quoteTiers.map((value) => BigInt(value.toString()))
|
|
335
|
+
const tiers = quoteSizes(market.config.quoteTiers.map((value) => BigInt(value.toString())), capacity);
|
|
322
336
|
if (!tiers.length)
|
|
323
337
|
return;
|
|
324
338
|
const half = Math.ceil(policy.spreadBps / 2);
|
|
@@ -335,9 +349,9 @@ function botQuote(policy, market, position, nonce, now, pricing) {
|
|
|
335
349
|
const boundedSkew = skewBps < BigInt(policy.inventorySkewBps ?? 0) ? skewBps : BigInt(policy.inventorySkewBps ?? 0);
|
|
336
350
|
const skew = price * boundedSkew * (netPosition > 0n ? -1n : 1n) / 10000n;
|
|
337
351
|
const costs = BigInt(half + (policy.executionCostBps ?? 0));
|
|
338
|
-
const
|
|
339
|
-
const bidCosts = costs + BigInt(
|
|
340
|
-
const askCosts = costs + BigInt(
|
|
352
|
+
const execution = executionCosts(policy, pricing);
|
|
353
|
+
const bidCosts = costs + BigInt(execution.bid);
|
|
354
|
+
const askCosts = costs + BigInt(execution.ask);
|
|
341
355
|
const quotes = tiers.map((size) => {
|
|
342
356
|
const units = (size * 100000000000n + price - 1n) / price;
|
|
343
357
|
const sell = pricing ? executionPrice(pricing.book.bids, units) : price;
|
|
@@ -369,6 +383,7 @@ class MakerBot {
|
|
|
369
383
|
hedge;
|
|
370
384
|
state;
|
|
371
385
|
position;
|
|
386
|
+
verifiedPosition;
|
|
372
387
|
dirty = true;
|
|
373
388
|
busy = false;
|
|
374
389
|
stopping = false;
|
|
@@ -378,6 +393,7 @@ class MakerBot {
|
|
|
378
393
|
lastCleanup = 0;
|
|
379
394
|
lastFills = 0;
|
|
380
395
|
inventoryVersion;
|
|
396
|
+
reservationView;
|
|
381
397
|
constructor(maker, policy, store, hedge) {
|
|
382
398
|
this.maker = maker;
|
|
383
399
|
this.policy = policy;
|
|
@@ -456,8 +472,18 @@ class MakerBot {
|
|
|
456
472
|
let ready = "ready" in account.status && "ready" in confirmed.status && account.version.toString() === confirmed.version.toString();
|
|
457
473
|
const currentVersion = BigInt(confirmed.version.toString());
|
|
458
474
|
const candidate = write && this.position && "ready" in confirmed.status && currentVersion === write.version + 1n && (this.inventoryVersion ?? this.position.version) >= write.version && (this.inventoryVersion ?? this.position.version) <= currentVersion;
|
|
459
|
-
const
|
|
460
|
-
const
|
|
475
|
+
const completedQuote = write && "ready" in confirmed.status && currentVersion === write.version + 1n && await this.maker.quoteState(new PublicKey(write.quote));
|
|
476
|
+
const completedWrite = candidate && completedQuote ? completedQuote.status : undefined;
|
|
477
|
+
const ownCommit = write && completedWrite && (write.release ? "cancelled" in completedWrite || "filled" in completedWrite : ("active" in completedWrite));
|
|
478
|
+
const riskWrite = [
|
|
479
|
+
this.state.quoteRisk,
|
|
480
|
+
this.state.replacement?.risk
|
|
481
|
+
].find((risk) => risk?.quote === write?.quote);
|
|
482
|
+
if (completedQuote && "active" in completedQuote.status && riskWrite) {
|
|
483
|
+
riskWrite.version = currentVersion;
|
|
484
|
+
riskWrite.balanceNonce = Buffer.from(completedQuote.balanceNonce).toString("hex");
|
|
485
|
+
await this.store.save(this.state);
|
|
486
|
+
}
|
|
461
487
|
if (ownCommit) {
|
|
462
488
|
this.inventoryVersion = currentVersion;
|
|
463
489
|
ready = true;
|
|
@@ -466,9 +492,9 @@ class MakerBot {
|
|
|
466
492
|
await this.store.save(this.state);
|
|
467
493
|
}
|
|
468
494
|
}
|
|
469
|
-
if (ready && (!this.position || this.position.status !== "ready" || this.position.position === undefined || (this.inventoryVersion ?? this.position.version) !== currentVersion)) {
|
|
495
|
+
if ("ready" in confirmed.status && (!this.position || this.position.status !== "ready" || this.position.position === undefined || (this.inventoryVersion ?? this.position.version) !== currentVersion && this.position.version !== currentVersion)) {
|
|
470
496
|
const prior = this.position;
|
|
471
|
-
this.position = await this.maker.position.summary({ timeoutMs:
|
|
497
|
+
this.position = await this.maker.position.summary({ timeoutMs: 0 });
|
|
472
498
|
const fresh = await this.maker.read("MarketAccount", this.maker.marketAccount(), "finalized");
|
|
473
499
|
const latest = await this.maker.read("MarketAccount", this.maker.marketAccount());
|
|
474
500
|
ready = "ready" in fresh.status && "ready" in latest.status && fresh.version.toString() === latest.version.toString() && this.position.status === "ready" && this.position.version === BigInt(fresh.version.toString());
|
|
@@ -482,13 +508,50 @@ class MakerBot {
|
|
|
482
508
|
this.dirty = false;
|
|
483
509
|
}
|
|
484
510
|
}
|
|
485
|
-
if (ready)
|
|
511
|
+
if (ready && this.position?.status === "ready") {
|
|
512
|
+
this.pricingDirty ||= this.verifiedPosition !== undefined && (this.verifiedPosition.position !== this.position.position || this.verifiedPosition.collateral !== this.position.collateral);
|
|
513
|
+
this.verifiedPosition = this.position;
|
|
486
514
|
this.dirty = false;
|
|
515
|
+
}
|
|
487
516
|
const snapshot = await this.hedge.tick(this.state.hedge, () => this.saveHedge(), ready ? this.position?.position : undefined);
|
|
488
517
|
if (snapshot.phase === "pending" || snapshot.phase === "blocked") {
|
|
518
|
+
const risk = this.state.quoteRisk;
|
|
519
|
+
const inventoryWait = snapshot.phase === "pending" && !this.state.hedge?.pending && (snapshot.reason === "Waiting for finalized Depth inventory" || snapshot.reason === "Waiting for the external account to include the confirmed hedge" || snapshot.reason === "Hedge submitted; reconciling confirmed exposure");
|
|
520
|
+
if (inventoryWait && risk && risk.quote === this.state.quote && this.hedge.policy.ratioBps === 1e4) {
|
|
521
|
+
const [book2, external] = await Promise.all([
|
|
522
|
+
this.hedge.adapter.book(),
|
|
523
|
+
this.hedge.adapter.account()
|
|
524
|
+
]);
|
|
525
|
+
validateBook(book2, Date.now(), this.hedge.policy.maxPriceAgeMs);
|
|
526
|
+
if (!Number.isSafeInteger(external.timestamp) || external.timestamp > Date.now() + 1000 || Date.now() - external.timestamp > this.hedge.policy.maxPriceAgeMs || external.openOrders.length || !Number.isInteger(external.leverage) || external.leverage < 1 || external.availableMargin < 0n || external.leverage > this.hedge.policy.maxLeverage)
|
|
527
|
+
throw new Error("External account is unavailable for quote continuity");
|
|
528
|
+
const market = await this.maker.read("Market", this.maker.marketAddress);
|
|
529
|
+
const price = BigInt(market.oraclePriceE8.toString());
|
|
530
|
+
const expected = risk.externalPosition + this.state.hedge.history.filter((fill) => fill.nonce > risk.hedgeNonce).reduce((total, fill) => total + (fill.buy ? fill.filledSize : -fill.filledSize), 0n);
|
|
531
|
+
const quote = await this.maker.quoteState(new PublicKey(risk.quote));
|
|
532
|
+
const balanceNonce = Buffer.from(quote.balanceNonce).toString("hex");
|
|
533
|
+
const unchanged = currentVersion === risk.version;
|
|
534
|
+
const ownFill = "ready" in confirmed.status && currentVersion === risk.version + 1n && risk.balanceNonce !== undefined && balanceNonce !== risk.balanceNonce;
|
|
535
|
+
if (ownFill) {
|
|
536
|
+
risk.version = currentVersion;
|
|
537
|
+
risk.balanceNonce = balanceNonce;
|
|
538
|
+
this.state.inventoryPendingSince ??= Date.now();
|
|
539
|
+
await this.store.save(this.state);
|
|
540
|
+
}
|
|
541
|
+
const bounded = (unchanged || ownFill) && external.position === expected && risk.notional * price <= this.hedge.policy.maxUnhedgedNotional * risk.price;
|
|
542
|
+
if (market.pendingOrders > 0 && bounded)
|
|
543
|
+
return snapshot.reason;
|
|
544
|
+
if (bounded && unchanged && this.state.inventoryPendingSince === undefined && this.state.hedge.unhedgedSince === undefined)
|
|
545
|
+
return snapshot.reason;
|
|
546
|
+
this.state.inventoryPendingSince ??= Date.now();
|
|
547
|
+
await this.store.save(this.state);
|
|
548
|
+
if (bounded && Date.now() - this.state.inventoryPendingSince < this.hedge.policy.maxUnhedgedMs)
|
|
549
|
+
return snapshot.reason;
|
|
550
|
+
}
|
|
489
551
|
await this.cancel(true);
|
|
490
552
|
return snapshot.reason ?? "hedge reconciliation pending";
|
|
491
553
|
}
|
|
554
|
+
this.state.inventoryPendingSince = undefined;
|
|
492
555
|
const [book, costs] = await Promise.all([
|
|
493
556
|
this.hedge.adapter.book(),
|
|
494
557
|
this.hedge.adapter.costs()
|
|
@@ -497,7 +560,9 @@ class MakerBot {
|
|
|
497
560
|
const before = this.pricing.book.bids[0].priceE8 + this.pricing.book.asks[0].priceE8;
|
|
498
561
|
const after = book.bids[0].priceE8 + book.asks[0].priceE8;
|
|
499
562
|
const change = after > before ? after - before : before - after;
|
|
500
|
-
|
|
563
|
+
const priorCosts = executionCosts(this.policy, this.pricing);
|
|
564
|
+
const nextCosts = executionCosts(this.policy, costs);
|
|
565
|
+
this.pricingDirty ||= change * 10000n >= before * BigInt(this.policy.repriceBps) || priorCosts.bid !== nextCosts.bid || priorCosts.ask !== nextCosts.ask;
|
|
501
566
|
}
|
|
502
567
|
this.pricing = {
|
|
503
568
|
book,
|
|
@@ -561,6 +626,7 @@ class MakerBot {
|
|
|
561
626
|
state.quote = flow.nextId.toBase58();
|
|
562
627
|
state.request = state.replacement.request;
|
|
563
628
|
state.referencePrice = state.replacement.referencePrice;
|
|
629
|
+
state.quoteRisk = state.replacement.risk;
|
|
564
630
|
state.replacement = undefined;
|
|
565
631
|
await this.store.save(state);
|
|
566
632
|
} else if (!prior && !next && (!state.replacement.request || state.replacement.request.expiresAt + 180 < Math.floor(Date.now() / 1000))) {
|
|
@@ -636,7 +702,8 @@ class MakerBot {
|
|
|
636
702
|
"waiting",
|
|
637
703
|
"validated",
|
|
638
704
|
"validating",
|
|
639
|
-
"closing"
|
|
705
|
+
"closing",
|
|
706
|
+
"reserving"
|
|
640
707
|
].includes(snapshot.status) ? await this.quoteWrite(handle2, true) : snapshot;
|
|
641
708
|
successorPending = !terminal.has(result.status);
|
|
642
709
|
}
|
|
@@ -669,7 +736,14 @@ class MakerBot {
|
|
|
669
736
|
await this.store.save(state);
|
|
670
737
|
return true;
|
|
671
738
|
}
|
|
672
|
-
if ([
|
|
739
|
+
if ([
|
|
740
|
+
"active",
|
|
741
|
+
"waiting",
|
|
742
|
+
"validated",
|
|
743
|
+
"validating",
|
|
744
|
+
"closing",
|
|
745
|
+
"reserving"
|
|
746
|
+
].includes(quote.status))
|
|
673
747
|
await this.quoteWrite(handle, true);
|
|
674
748
|
return false;
|
|
675
749
|
}
|
|
@@ -677,13 +751,15 @@ class MakerBot {
|
|
|
677
751
|
const state = this.state;
|
|
678
752
|
const replacement = state.replacement;
|
|
679
753
|
const flow = this.maker.quote.replacement(new PublicKey(replacement.prior), replacement.nonce);
|
|
754
|
+
if (now - market.oracleTimestamp.toNumber() > market.config.maxOracleAgeSeconds - Math.min(Math.floor(market.config.maxOracleAgeSeconds / 2), this.policy.lifetimeSeconds))
|
|
755
|
+
return this.snapshot("pending", "waiting for a fresh oracle update");
|
|
680
756
|
if (!replacement.request) {
|
|
681
757
|
const account = await this.maker.read("MarketAccount", this.maker.marketAccount());
|
|
682
758
|
if (!("ready" in account.status) || market.pendingOrders)
|
|
683
759
|
return this.snapshot("pending", "settlement in progress");
|
|
684
760
|
if (!this.position || this.position.status !== "ready" || this.position.version !== BigInt(account.version.toString())) {
|
|
685
761
|
this.position = await this.maker.position.summary({
|
|
686
|
-
timeoutMs:
|
|
762
|
+
timeoutMs: 0
|
|
687
763
|
});
|
|
688
764
|
if (this.position.status !== "ready" || this.position.version !== BigInt(account.version.toString()))
|
|
689
765
|
return this.snapshot("pending", "maker view changed");
|
|
@@ -693,10 +769,23 @@ class MakerBot {
|
|
|
693
769
|
}
|
|
694
770
|
this.inventoryVersion = this.position.version;
|
|
695
771
|
this.dirty = false;
|
|
696
|
-
|
|
772
|
+
const previous2 = await this.maker.quote.get(new PublicKey(replacement.prior)).status();
|
|
773
|
+
const policy = this.quotePolicy(market);
|
|
774
|
+
const planned = botQuote(policy, market, this.position, replacement.nonce, now, this.pricing);
|
|
775
|
+
let credit = 0n;
|
|
776
|
+
if (previous2.status === "active" && (!planned || planned.maxCapacity < policy.maxCapacity)) {
|
|
777
|
+
this.reservationView ??= this.maker.quote.get(new PublicKey(replacement.prior));
|
|
778
|
+
const margin = await this.reservationView.reservation();
|
|
779
|
+
if (margin === undefined)
|
|
780
|
+
return this.snapshot("pending", "reading remaining quote margin");
|
|
781
|
+
credit = margin;
|
|
782
|
+
this.reservationView = undefined;
|
|
783
|
+
}
|
|
784
|
+
replacement.request = botQuote(policy, market, this.position, replacement.nonce, now, this.pricing, credit);
|
|
697
785
|
if (!replacement.request)
|
|
698
786
|
return this.snapshot("pending", "inventory or collateral limit");
|
|
699
787
|
replacement.referencePrice = BigInt(market.oraclePriceE8.toString());
|
|
788
|
+
replacement.risk = this.quoteRisk(flow.nextId, replacement.request, market);
|
|
700
789
|
await this.store.save(state);
|
|
701
790
|
}
|
|
702
791
|
await flow.begin();
|
|
@@ -706,16 +795,39 @@ class MakerBot {
|
|
|
706
795
|
if (snapshot.status === "validating" && replacement.request.expiresAt > now)
|
|
707
796
|
return this.snapshot("pending", "validating successor quote");
|
|
708
797
|
if (["rejected", "cancelled"].includes(snapshot.status) || replacement.request.expiresAt <= now) {
|
|
709
|
-
await
|
|
710
|
-
|
|
798
|
+
await flow.discard();
|
|
799
|
+
state.replacement = undefined;
|
|
800
|
+
this.reservationView = undefined;
|
|
801
|
+
await this.store.save(state);
|
|
802
|
+
return this.snapshot("pending", "retrying expired successor");
|
|
711
803
|
}
|
|
712
804
|
const prior = this.maker.quote.get(new PublicKey(replacement.prior));
|
|
713
805
|
const previous = await prior.status();
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
806
|
+
if (previous.status === "active" && ["validated", "reserving"].includes(snapshot.status)) {
|
|
807
|
+
const price = BigInt(market.oraclePriceE8.toString());
|
|
808
|
+
const size = this.position.position;
|
|
809
|
+
const exposure = ((size < 0n ? -size : size) * price + 99999999999n) / 100000000000n;
|
|
810
|
+
const risk = this.quoteRisk(next.id, replacement.request, market);
|
|
811
|
+
if (snapshot.status === "validated" && (exposure + snapshot.maxCapacity > this.policy.maxInventoryNotional || risk && risk.notional > this.hedge.policy.maxUnhedgedNotional)) {
|
|
812
|
+
await flow.discard();
|
|
813
|
+
state.replacement = undefined;
|
|
814
|
+
await this.store.save(state);
|
|
815
|
+
return this.snapshot("pending", "repricing changed inventory");
|
|
816
|
+
}
|
|
817
|
+
if (snapshot.status === "validated")
|
|
818
|
+
replacement.risk = risk;
|
|
819
|
+
await this.store.save(state);
|
|
820
|
+
const account = await this.maker.read("MarketAccount", this.maker.marketAccount());
|
|
821
|
+
await flow.rotate(next);
|
|
822
|
+
if (next.reserveSignature) {
|
|
823
|
+
state.quoteWrite = {
|
|
824
|
+
quote: next.id.toBase58(),
|
|
825
|
+
version: BigInt(account.version.toString()),
|
|
826
|
+
release: false
|
|
827
|
+
};
|
|
828
|
+
await this.store.save(state);
|
|
829
|
+
}
|
|
830
|
+
return this.snapshot("pending", "rotating quote reservation");
|
|
719
831
|
}
|
|
720
832
|
if (!terminal.has(previous.status)) {
|
|
721
833
|
if (["active", "waiting", "validated", "validating", "closing"].includes(previous.status))
|
|
@@ -725,6 +837,7 @@ class MakerBot {
|
|
|
725
837
|
state.quote = flow.nextId.toBase58();
|
|
726
838
|
state.request = replacement.request;
|
|
727
839
|
state.referencePrice = replacement.referencePrice;
|
|
840
|
+
state.quoteRisk = replacement.risk;
|
|
728
841
|
state.replacement = undefined;
|
|
729
842
|
this.pricingDirty = false;
|
|
730
843
|
await this.store.save(state);
|
|
@@ -765,6 +878,8 @@ class MakerBot {
|
|
|
765
878
|
await this.hedge.tick(state.hedge, () => this.saveHedge(), undefined);
|
|
766
879
|
return this.snapshot(stopped && !state.hedge?.pending ? "stopped" : "pending", reason);
|
|
767
880
|
}
|
|
881
|
+
if (state.quote)
|
|
882
|
+
await this.renew(market, now);
|
|
768
883
|
await this.fills();
|
|
769
884
|
const hedgeReason = await this.hedgeTick();
|
|
770
885
|
if (hedgeReason)
|
|
@@ -802,12 +917,29 @@ class MakerBot {
|
|
|
802
917
|
await this.cancel();
|
|
803
918
|
return this.snapshot("pending", "releasing quote");
|
|
804
919
|
} else if (quote.status === "active") {
|
|
920
|
+
if (this.dirty && !this.hedge) {
|
|
921
|
+
const account2 = await this.maker.read("MarketAccount", this.maker.marketAccount());
|
|
922
|
+
if ("ready" in account2.status) {
|
|
923
|
+
const before = this.position;
|
|
924
|
+
this.position = await this.maker.position.summary({
|
|
925
|
+
timeoutMs: 0
|
|
926
|
+
});
|
|
927
|
+
if (this.position.status === "ready" && this.position.version === BigInt(account2.version.toString())) {
|
|
928
|
+
this.dirty = false;
|
|
929
|
+
this.pricingDirty ||= before !== undefined && before.position !== this.position.position;
|
|
930
|
+
this.inventoryVersion = this.position.version;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (this.dirty)
|
|
934
|
+
return this.snapshot("pending", "reading maker inventory");
|
|
935
|
+
}
|
|
805
936
|
const price = BigInt(market.oraclePriceE8.toString());
|
|
806
937
|
const difference = state.referencePrice === undefined ? undefined : (price > state.referencePrice ? price - state.referencePrice : state.referencePrice - price) * 10000n;
|
|
807
938
|
const current = await this.maker.quoteState(handle2.id);
|
|
808
|
-
const
|
|
809
|
-
const
|
|
810
|
-
|
|
939
|
+
const reference = BigInt(current.oracleReferenceE8.toString());
|
|
940
|
+
const drift = price > reference ? price - reference : reference - price;
|
|
941
|
+
const compatible = current.marketRevision.eq(market.revision) && drift * 10000n <= reference * BigInt(Math.min(Math.floor(market.config.maxQuoteDeviationBps / 4), 25));
|
|
942
|
+
if (!this.dirty && !this.pricingDirty && compatible && current.expiry.toNumber() > now && difference !== undefined && difference < BigInt(this.policy.repriceBps) * state.referencePrice)
|
|
811
943
|
return this.snapshot("active");
|
|
812
944
|
state.replacement = { prior: state.quote, nonce: nonce() };
|
|
813
945
|
this.pricingDirty = false;
|
|
@@ -824,7 +956,7 @@ class MakerBot {
|
|
|
824
956
|
return this.snapshot("idle", "maker account busy");
|
|
825
957
|
if (this.dirty || !this.position || this.position.version !== BigInt(account.version.toString())) {
|
|
826
958
|
this.position = await this.maker.position.summary({
|
|
827
|
-
timeoutMs:
|
|
959
|
+
timeoutMs: 0
|
|
828
960
|
});
|
|
829
961
|
if (this.position.status !== "ready" || this.position.version !== BigInt(account.version.toString()))
|
|
830
962
|
return this.snapshot("idle", "maker view changed");
|
|
@@ -832,12 +964,13 @@ class MakerBot {
|
|
|
832
964
|
return this.snapshot("idle", "maker view refreshed");
|
|
833
965
|
}
|
|
834
966
|
if (!state.request) {
|
|
835
|
-
const request2 = botQuote(this.
|
|
967
|
+
const request2 = botQuote(this.quotePolicy(market), market, this.position, nonce(), now, this.pricing);
|
|
836
968
|
if (!request2)
|
|
837
969
|
return this.snapshot("idle", "inventory or collateral limit");
|
|
838
970
|
state.request = request2;
|
|
839
971
|
this.pricingDirty = false;
|
|
840
972
|
state.referencePrice = BigInt(market.oraclePriceE8.toString());
|
|
973
|
+
state.quoteRisk = this.quoteRisk(this.requestId(), request2, market);
|
|
841
974
|
await this.store.save(state);
|
|
842
975
|
}
|
|
843
976
|
const request = state.request;
|
|
@@ -863,6 +996,55 @@ class MakerBot {
|
|
|
863
996
|
this.busy = false;
|
|
864
997
|
}
|
|
865
998
|
}
|
|
999
|
+
async renew(market, now) {
|
|
1000
|
+
const state = this.state;
|
|
1001
|
+
if (this.hedge && (state.hedge?.pending || state.inventoryPendingSince !== undefined && Date.now() - state.inventoryPendingSince >= this.hedge.policy.maxUnhedgedMs))
|
|
1002
|
+
return;
|
|
1003
|
+
const handle = this.maker.quote.get(new PublicKey(state.quote));
|
|
1004
|
+
const quote = await this.maker.quoteState(handle.id);
|
|
1005
|
+
if (!("active" in quote.status) || quote.expiry.toNumber() <= now)
|
|
1006
|
+
return;
|
|
1007
|
+
const duration = Math.min(this.policy.lifetimeSeconds, market.config.maxQuoteLifetimeSeconds);
|
|
1008
|
+
if (quote.expiry.toNumber() - now > Math.max(this.policy.refreshSeconds, duration - this.policy.refreshSeconds))
|
|
1009
|
+
return;
|
|
1010
|
+
const price = BigInt(market.oraclePriceE8.toString());
|
|
1011
|
+
const reference = BigInt(quote.oracleReferenceE8.toString());
|
|
1012
|
+
const drift = price > reference ? price - reference : reference - price;
|
|
1013
|
+
if (!quote.marketRevision.eq(market.revision) || reference <= 0n || drift * 10000n > reference * BigInt(Math.min(Math.floor(market.config.maxQuoteDeviationBps / 4), 25)))
|
|
1014
|
+
return;
|
|
1015
|
+
const renewed = await handle.renew({ validForSeconds: duration });
|
|
1016
|
+
if (state.request)
|
|
1017
|
+
state.request.expiresAt = renewed.expiresAt;
|
|
1018
|
+
await this.store.save(state);
|
|
1019
|
+
}
|
|
1020
|
+
quotePolicy(market) {
|
|
1021
|
+
if (!this.hedge || !this.pricing || this.position?.position === undefined)
|
|
1022
|
+
return this.policy;
|
|
1023
|
+
const price = BigInt(market.oraclePriceE8.toString());
|
|
1024
|
+
const size = this.position.position + this.pricing.externalPosition;
|
|
1025
|
+
const risk = ((size < 0n ? -size : size) * price + 99999999999n) / 100000000000n;
|
|
1026
|
+
const remaining = this.hedge.policy.maxUnhedgedNotional * 9n / 10n - risk;
|
|
1027
|
+
const capacity = remaining * 10000n / BigInt(1e4 + market.config.maxQuoteDeviationBps);
|
|
1028
|
+
return {
|
|
1029
|
+
...this.policy,
|
|
1030
|
+
maxCapacity: capacity < this.policy.maxCapacity ? capacity > 0n ? capacity : 1n : this.policy.maxCapacity
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
quoteRisk(id, request, market) {
|
|
1034
|
+
if (!this.hedge || !this.pricing || this.position?.position === undefined)
|
|
1035
|
+
return;
|
|
1036
|
+
const price = BigInt(market.oraclePriceE8.toString());
|
|
1037
|
+
const size = this.position.position + this.pricing.externalPosition;
|
|
1038
|
+
const notional2 = ((size < 0n ? -size : size) * price + 99999999999n) / 100000000000n + (request.maxCapacity * BigInt(1e4 + market.config.maxQuoteDeviationBps) + 9999n) / 10000n;
|
|
1039
|
+
return {
|
|
1040
|
+
quote: id.toBase58(),
|
|
1041
|
+
notional: notional2,
|
|
1042
|
+
price,
|
|
1043
|
+
version: this.position.version,
|
|
1044
|
+
externalPosition: this.pricing.externalPosition,
|
|
1045
|
+
hedgeNonce: this.state.hedge.nonce
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
866
1048
|
stop() {
|
|
867
1049
|
this.stopping = true;
|
|
868
1050
|
}
|
package/dist/quote/quote.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type QuoteSnapshot = {
|
|
|
5
5
|
status: string;
|
|
6
6
|
maxCapacity: bigint;
|
|
7
7
|
attachedOrders: number;
|
|
8
|
+
expiresAt: number;
|
|
8
9
|
replacementNonce?: bigint;
|
|
9
10
|
};
|
|
10
11
|
export declare class QuoteHandle {
|
|
@@ -13,10 +14,15 @@ export declare class QuoteHandle {
|
|
|
13
14
|
private readonly client;
|
|
14
15
|
reserveSignature?: string;
|
|
15
16
|
releaseSignature?: string;
|
|
17
|
+
priorId?: PublicKey;
|
|
16
18
|
private balanceView?;
|
|
17
19
|
constructor(id: PublicKey, prepareSignature: string | undefined, client: MakerClient);
|
|
18
20
|
status(): Promise<QuoteSnapshot>;
|
|
21
|
+
renew(input: {
|
|
22
|
+
validForSeconds: number;
|
|
23
|
+
}): Promise<QuoteSnapshot>;
|
|
19
24
|
capacity(): Promise<bigint>;
|
|
25
|
+
reservation(): Promise<bigint | undefined>;
|
|
20
26
|
confirm(options?: {
|
|
21
27
|
timeoutMs?: number;
|
|
22
28
|
pollMs?: number;
|
|
@@ -17,6 +17,8 @@ export declare class QuoteReplacement {
|
|
|
17
17
|
status(): Promise<ReplacementSnapshot>;
|
|
18
18
|
prepare(input: QuoteRequest): Promise<QuoteHandle>;
|
|
19
19
|
begin(): Promise<void>;
|
|
20
|
+
rotate(handle?: QuoteHandle): Promise<QuoteSnapshot>;
|
|
21
|
+
discard(): Promise<void>;
|
|
20
22
|
resume(input: QuoteRequest, options?: {
|
|
21
23
|
timeoutMs?: number;
|
|
22
24
|
pollMs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@depthhq/maker-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"@solana/web3.js": "1.99.0",
|
|
30
30
|
"buffer": "6.0.3",
|
|
31
31
|
"viem": "2.38.0",
|
|
32
|
-
"@depthhq/bindings": "0.1.
|
|
33
|
-
"@depthhq/sdk": "0.1.
|
|
32
|
+
"@depthhq/bindings": "0.1.4",
|
|
33
|
+
"@depthhq/sdk": "0.1.8"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "node ../../scripts/build-package.mjs maker-sdk",
|